diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index f0ecdff1d5..d4b6184f72 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -9,14 +9,21 @@ def bool_instance() -> bool: flag = bool_instance() if flag: - class C: + class C1: x = 1 else: - class C: + class C1: x = 2 -reveal_type(C.x) # revealed: Literal[1, 2] +class C2: + if flag: + x = 3 + else: + x = 4 + +reveal_type(C1.x) # revealed: Literal[1, 2] +reveal_type(C2.x) # revealed: Literal[3, 4] ``` ## Inherited attributes @@ -53,3 +60,77 @@ reveal_type(A.__mro__) # `E` is earlier in the MRO than `F`, so we should use the type of `E.X` reveal_type(A.X) # revealed: Literal[42] ``` + +## Unions with possibly unbound paths + +### Definite boundness within a class + +In this example, the `x` attribute is not defined in the `C2` element of the union: + +```py +def bool_instance() -> bool: + return True + +class C1: + x = 1 + +class C2: ... + +class C3: + x = 3 + +flag1 = bool_instance() +flag2 = bool_instance() + +C = C1 if flag1 else C2 if flag2 else C3 + +# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound" +reveal_type(C.x) # revealed: Literal[1, 3] +``` + +### Possibly-unbound within a class + +We raise the same diagnostic if the attribute is possibly-unbound in at least one element of the +union: + +```py +def bool_instance() -> bool: + return True + +class C1: + x = 1 + +class C2: + if bool_instance(): + x = 2 + +class C3: + x = 3 + +flag1 = bool_instance() +flag2 = bool_instance() + +C = C1 if flag1 else C2 if flag2 else C3 + +# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound" +reveal_type(C.x) # revealed: Literal[1, 2, 3] +``` + +## Unions with all paths unbound + +If the symbol is unbound in all elements of the union, we detect that: + +```py +def bool_instance() -> bool: + return True + +class C1: ... +class C2: ... + +flag = bool_instance() + +C = C1 if flag else C2 + +# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`" +reveal_type(C.x) # revealed: Unknown +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 58c5c83c69..88f81f6834 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1108,7 +1108,6 @@ impl<'db> Type<'db> { possibly_unbound = true; } Symbol::Type(ty_member, member_boundness) => { - // TODO: raise a diagnostic if member_boundness indicates potential unboundness if member_boundness == Boundness::PossiblyUnbound { possibly_unbound = true; }