diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 3252178cf9..4f6e1c9c72 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -256,6 +256,25 @@ class Foo(x): ... reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] ``` +## `__bases__` is a union of a dynamic type and valid bases + +If a dynamic type such as `Any` or `Unknown` is one of the elements in the union, and all other +types *would be* valid class bases, we do not emit an `invalid-base` diagnostic and use the dynamic +type as a base to prevent further downstream errors. + +```py +from typing import Any + +def _(flag: bool, any: Any): + if flag: + Base = any + else: + class Base: ... + + class Foo(Base): ... + reveal_type(Foo.__mro__) # revealed: tuple[, Any, ] +``` + ## `__bases__` includes multiple `Union`s ```py diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_`__bases__`_lists_wi…_(ea7ebc83ec359b54).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_`__bases__`_lists_wi…_(ea7ebc83ec359b54).snap index ec87630178..fceb6462c8 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_`__bases__`_lists_wi…_(ea7ebc83ec359b54).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_`__bases__`_lists_wi…_(ea7ebc83ec359b54).snap @@ -140,43 +140,6 @@ info[revealed-type]: Revealed type ``` -``` -error[duplicate-base]: Duplicate base class `Eggs` - --> src/mdtest_snippet.py:16:7 - | -14 | # error: [duplicate-base] "Duplicate base class `Spam`" -15 | # error: [duplicate-base] "Duplicate base class `Eggs`" -16 | class Ham( - | _______^ -17 | | Spam, -18 | | Eggs, -19 | | Bar, -20 | | Baz, -21 | | Spam, -22 | | Eggs, -23 | | ): ... - | |_^ -24 | -25 | # fmt: on - | -info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:18:5 - | -16 | class Ham( -17 | Spam, -18 | Eggs, - | ---- Class `Eggs` first included in bases list here -19 | Bar, -20 | Baz, -21 | Spam, -22 | Eggs, - | ^^^^ Class `Eggs` later repeated here -23 | ): ... - | -info: rule `duplicate-base` is enabled by default - -``` - ``` error[duplicate-base]: Duplicate base class `Spam` --> src/mdtest_snippet.py:16:7 @@ -215,6 +178,43 @@ info: rule `duplicate-base` is enabled by default ``` +``` +error[duplicate-base]: Duplicate base class `Eggs` + --> src/mdtest_snippet.py:16:7 + | +14 | # error: [duplicate-base] "Duplicate base class `Spam`" +15 | # error: [duplicate-base] "Duplicate base class `Eggs`" +16 | class Ham( + | _______^ +17 | | Spam, +18 | | Eggs, +19 | | Bar, +20 | | Baz, +21 | | Spam, +22 | | Eggs, +23 | | ): ... + | |_^ +24 | +25 | # fmt: on + | +info: The definition of class `Ham` will raise `TypeError` at runtime + --> src/mdtest_snippet.py:18:5 + | +16 | class Ham( +17 | Spam, +18 | Eggs, + | ---- Class `Eggs` first included in bases list here +19 | Bar, +20 | Baz, +21 | Spam, +22 | Eggs, + | ^^^^ Class `Eggs` later repeated here +23 | ): ... + | +info: rule `duplicate-base` is enabled by default + +``` + ``` info[revealed-type]: Revealed type --> src/mdtest_snippet.py:27:13 diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index bcd2af97cb..073e77ca11 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -123,7 +123,32 @@ impl<'db> ClassBase<'db> { Some(valid_element) } } - Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? + Type::Union(union) => { + // We do not support full unions of MROs (yet). Until we do, + // support the cases where one of the types in the union is + // a dynamic type such as `Any` or `Unknown`, and all other + // types *would be* valid class bases. In this case, we can + // "fold" the other potential bases into the dynamic type, + // and return `Any`/`Unknown` as the class base to prevent + // invalid-base diagnostics and further downstream errors. + let Some(Type::Dynamic(dynamic)) = union + .elements(db) + .iter() + .find(|elem| matches!(elem, Type::Dynamic(_))) + else { + return None; + }; + + if union + .elements(db) + .iter() + .all(|elem| ClassBase::try_from_type(db, *elem).is_some()) + { + Some(ClassBase::Dynamic(*dynamic)) + } else { + None + } + } Type::NominalInstance(_) => None, // TODO -- handle `__mro_entries__`? Type::PropertyInstance(_) => None, Type::Never