From 618fa922c7cb058ca6fce79524004a406216b553 Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 6 Oct 2025 15:23:01 +0200 Subject: [PATCH] [ty] Do not infer types for unannotated implicit instance attribtues --- crates/ty_ide/src/completion.rs | 12 +- .../resources/mdtest/attributes.md | 130 ++++++++++------ .../resources/mdtest/call/dunder.md | 3 +- .../resources/mdtest/cycle.md | 47 +++++- .../resources/mdtest/descriptor_protocol.md | 9 +- .../resources/mdtest/narrow/assignment.md | 15 +- .../resources/mdtest/overloads.md | 6 +- .../resources/mdtest/protocols.md | 3 +- .../resources/mdtest/type_qualifiers/final.md | 3 +- crates/ty_python_semantic/src/types.rs | 10 -- crates/ty_python_semantic/src/types/class.rs | 142 ++---------------- 11 files changed, 176 insertions(+), 204 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 32c3ef02e1..736dd85a69 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1640,9 +1640,9 @@ quux. ); assert_snapshot!(test.completions_without_builtins_with_types(), @r" - bar :: Unknown | Literal[2] - baz :: Unknown | Literal[3] - foo :: Unknown | Literal[1] + bar :: Unknown + baz :: Unknown + foo :: Unknown __annotations__ :: dict[str, Any] __class__ :: type[Quux] __delattr__ :: bound method Quux.__delattr__(name: str, /) -> None @@ -1685,9 +1685,9 @@ quux.b ); assert_snapshot!(test.completions_without_builtins_with_types(), @r" - bar :: Unknown | Literal[2] - baz :: Unknown | Literal[3] - foo :: Unknown | Literal[1] + bar :: Unknown + baz :: Unknown + foo :: Unknown __annotations__ :: dict[str, Any] __class__ :: type[Quux] __delattr__ :: bound method Quux.__delattr__(name: str, /) -> None diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 8f5742bcc7..ad55b968be 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -25,7 +25,8 @@ class C: c_instance = C(1) -reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"] +# TODO: Should be `Unknown | Literal[1, "a"]` +reveal_type(c_instance.inferred_from_value) # revealed: Unknown # TODO: Same here. This should be `Unknown | Literal[1, "a"]` reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown @@ -35,7 +36,8 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown # something that we might want to change in the future. # # See https://github.com/astral-sh/ruff/issues/15960 for a related discussion. -reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None +# TODO: Should be `Unknown | int | None` +reveal_type(c_instance.inferred_from_param) # revealed: Unknown reveal_type(c_instance.declared_only) # revealed: bytes @@ -153,7 +155,8 @@ reveal_type(c_instance.declared_in_body_defined_in_init) # revealed: str | None # which is planned in https://github.com/astral-sh/ruff/issues/14297 reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | str | None -reveal_type(c_instance.bound_in_body_and_init) # revealed: Unknown | None | Literal["a"] +# TODO: Should be `Unknown | None | Literal["a"]` +reveal_type(c_instance.bound_in_body_and_init) # revealed: Unknown | None ``` #### Variable defined in non-`__init__` method @@ -175,12 +178,14 @@ class C: c_instance = C(1) -reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"] +# TODO: Should be `Unknown | Literal[1, "a"]` +reveal_type(c_instance.inferred_from_value) # revealed: Unknown # TODO: Should be `Unknown | Literal[1, "a"]` reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown -reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None +# TODO: Should be `Unknown | int | None` +reveal_type(c_instance.inferred_from_param) # revealed: Unknown reveal_type(c_instance.declared_only) # revealed: bytes @@ -224,7 +229,8 @@ class C: c_instance = C() -reveal_type(c_instance.x) # revealed: Unknown | int | str +# TODO: Should be `Unknown | int | str` +reveal_type(c_instance.x) # revealed: Unknown reveal_type(c_instance.y) # revealed: int reveal_type(c_instance.z) # revealed: int ``` @@ -238,8 +244,10 @@ class C: c_instance = C() -reveal_type(c_instance.a) # revealed: Unknown | Literal[1] -reveal_type(c_instance.b) # revealed: Unknown | Literal[1] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(c_instance.a) # revealed: Unknown +# TODO: Should be `Unknown | Literal[1]` +reveal_type(c_instance.b) # revealed: Unknown ``` #### Augmented assignments @@ -256,7 +264,8 @@ class C: # TODO: Mypy and pyright do not support this, but it would be great if we could # infer `Unknown | str` here (`Weird` is not a possible type for the `w` attribute). -reveal_type(C().w) # revealed: Unknown | Weird +# TODO: Should be `Unknown | Weird` +reveal_type(C().w) # revealed: Unknown ``` #### Attributes defined in tuple unpackings @@ -280,12 +289,16 @@ reveal_type(c_instance.b1) # revealed: Unknown | Literal["a"] reveal_type(c_instance.c1) # revealed: Unknown | int reveal_type(c_instance.d1) # revealed: Unknown | str -reveal_type(c_instance.a2) # revealed: Unknown | Literal[1] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(c_instance.a2) # revealed: Unknown -reveal_type(c_instance.b2) # revealed: Unknown | Literal["a"] +# TODO: Should be `Unknown | Literal["a"]` +reveal_type(c_instance.b2) # revealed: Unknown -reveal_type(c_instance.c2) # revealed: Unknown | int -reveal_type(c_instance.d2) # revealed: Unknown | str +# TODO: Should be `Unknown | int` +reveal_type(c_instance.c2) # revealed: Unknown +# TODO: Should be `Unknown | str` +reveal_type(c_instance.d2) # revealed: Unknown ``` #### Starred assignments @@ -296,8 +309,10 @@ class C: self.a, *self.b = (1, 2, 3) c_instance = C() -reveal_type(c_instance.a) # revealed: Unknown | Literal[1] -reveal_type(c_instance.b) # revealed: Unknown | list[Literal[2, 3]] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(c_instance.a) # revealed: Unknown +# TODO: Should be `Unknown | list[Literal[2, 3]]` +reveal_type(c_instance.b) # revealed: Unknown ``` #### Attributes defined in for-loop (unpacking) @@ -333,8 +348,10 @@ class C: for self.z in NonIterable(): pass -reveal_type(C().x) # revealed: Unknown | int -reveal_type(C().y) # revealed: Unknown | str +# TODO: Should be `Unknown | int` +reveal_type(C().x) # revealed: Unknown +# TODO: Should be `Unknown | str` +reveal_type(C().y) # revealed: Unknown ``` #### Attributes defined in `with` statements @@ -354,7 +371,8 @@ class C: c_instance = C() -reveal_type(c_instance.x) # revealed: Unknown | int | None +# TODO: Should be `Unknown | int | None` +reveal_type(c_instance.x) # revealed: Unknown ``` #### Attributes defined in `with` statements, but with unpacking @@ -374,8 +392,10 @@ class C: c_instance = C() -reveal_type(c_instance.x) # revealed: Unknown | int | None -reveal_type(c_instance.y) # revealed: Unknown | int +# TODO: Should be `Unknown | int | None` +reveal_type(c_instance.x) # revealed: Unknown +# TODO: Should be `Unknown | int` +reveal_type(c_instance.y) # revealed: Unknown ``` #### Attributes defined in comprehensions @@ -462,8 +482,10 @@ c_instance = C() reveal_type(c_instance.a1) # revealed: str | None reveal_type(c_instance.a2) # revealed: str | None -reveal_type(c_instance.b1) # revealed: Unknown | Literal[1] -reveal_type(c_instance.b2) # revealed: Unknown | Literal[1] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(c_instance.b1) # revealed: Unknown +# TODO: Should be `Unknown | Literal[1]` +reveal_type(c_instance.b2) # revealed: Unknown ``` #### Methods that does not use `self` as a first parameter @@ -506,6 +528,7 @@ class C: # error: [unresolved-attribute] reveal_type(C.x) # revealed: Unknown +# TODO: Should be `Unknown | Literal[1]` # error: [unresolved-attribute] reveal_type(C().x) # revealed: Unknown @@ -536,7 +559,8 @@ class C: def f(self) -> None: self.x = 1 -reveal_type(C().x) # revealed: Unknown | Literal[1] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(C().x) # revealed: Unknown ``` And if `staticmethod` is fully qualified, that should also be recognized: @@ -601,14 +625,18 @@ class C: self.e = e # TODO: this would ideally be `Unknown | Literal[1]` -reveal_type(C(True).a) # revealed: Unknown | Literal[1, "a"] +# TODO: Should be `Unknown | Literal[1, "a"]` +reveal_type(C(True).a) # revealed: Unknown # TODO: this would ideally raise an `unresolved-attribute` error -reveal_type(C(True).b) # revealed: Unknown | Literal[2] -reveal_type(C(True).c) # revealed: Unknown | Literal[3] | str +# TODO: Should be `Unknown | Literal[2]` +reveal_type(C(True).b) # revealed: Unknown +# TODO: Should be `Unknown | Literal[3] | str` +reveal_type(C(True).c) # revealed: Unknown # Ideally, this would just be `Unknown | Literal[5]`, but we currently do not # attempt to analyze control flow within methods more closely. All reachable # attribute assignments are considered, so `self.x = 4` is also included: -reveal_type(C(True).d) # revealed: Unknown | Literal[4, 5] +# TODO: Should be `Unknown | Literal[4, 5]` +reveal_type(C(True).d) # revealed: Unknown # error: [unresolved-attribute] reveal_type(C(True).e) # revealed: Unknown ``` @@ -626,8 +654,10 @@ class C: # This is because, it is not possible to access a partially-initialized object by normal means. self.y = 2 -reveal_type(C(False).x) # revealed: Unknown | Literal[1] -reveal_type(C(False).y) # revealed: Unknown | Literal[2] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(C(False).x) # revealed: Unknown +# TODO: Should be `Unknown | Literal[2]` +reveal_type(C(False).y) # revealed: Unknown class C: def __init__(self, b: bytes) -> None: @@ -640,8 +670,10 @@ class C: self.s = s -reveal_type(C(b"abc").b) # revealed: Unknown | bytes -reveal_type(C(b"abc").s) # revealed: Unknown | str +# TODO: Should be `Unknown | bytes` +reveal_type(C(b"abc").b) # revealed: Unknown +# TODO: Should be `Unknown | str` +reveal_type(C(b"abc").s) # revealed: Unknown class C: def __init__(self, iter) -> None: @@ -654,8 +686,10 @@ class C: # but we consider the subsequent attributes to be definitely-bound. self.y = 2 -reveal_type(C([]).x) # revealed: Unknown | Literal[1] -reveal_type(C([]).y) # revealed: Unknown | Literal[2] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(C([]).x) # revealed: Unknown +# TODO: Should be `Unknown | Literal[2]` +reveal_type(C([]).y) # revealed: Unknown ``` #### Diagnostics are reported for the right-hand side of attribute assignments @@ -745,13 +779,15 @@ class C: # for a more realistic example, let's actually call the method C.class_method() -reveal_type(C.pure_class_variable) # revealed: Unknown | Literal["value set in class method"] +# TODO: Should be `Unknown | Literal["value set in class method"]` +reveal_type(C.pure_class_variable) # revealed: Unknown C.pure_class_variable = "overwritten on class" reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"] c_instance = C() -reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"] +# TODO: Should be `Unknown | Literal["value set in class method"]` +reveal_type(c_instance.pure_class_variable) # revealed: Unknown # TODO: should raise an error. c_instance.pure_class_variable = "value set on instance" @@ -1335,11 +1371,13 @@ def _(flag: bool): else: self.y = "b" - reveal_type(Foo().x) # revealed: Unknown | Literal[1] + # TODO: Should be `Unknown | Literal[1]` + reveal_type(Foo().x) # revealed: Unknown Foo().x = 2 - reveal_type(Foo().y) # revealed: Unknown | Literal["a", "b"] + # TODO: Should be `Unknown | Literal["a", "b"]` + reveal_type(Foo().y) # revealed: Unknown Foo().y = "c" ``` @@ -2223,7 +2261,8 @@ class C: def copy(self, other: "C"): self.x = other.x -reveal_type(C().x) # revealed: Unknown | Literal[1] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(C().x) # revealed: Unknown ``` If the only assignment to a name is cyclic, we just infer `Unknown` for that attribute: @@ -2279,8 +2318,10 @@ class B: def copy(self, other: "A"): self.x = other.x -reveal_type(B().x) # revealed: Unknown | Literal[1] -reveal_type(A().x) # revealed: Unknown | Literal[1] +# TODO: Should be `Unknown | Literal[1]` +reveal_type(B().x) # revealed: Unknown +# TODO: Should be `Unknown | Literal[1]` +reveal_type(A().x) # revealed: Unknown ``` This case additionally tests our union/intersection simplification logic: @@ -2311,7 +2352,8 @@ class Toggle: self.y = True reveal_type(Toggle().x) # revealed: Literal[True] -reveal_type(Toggle().y) # revealed: Unknown | Literal[True] +# TODO: Should be `Unknown | int` +reveal_type(Toggle().y) # revealed: Unknown ``` Make sure that the growing union of literals `Literal[0, 1, 2, ...]` collapses to `int` during @@ -2325,7 +2367,8 @@ class Counter: def increment(self: "Counter"): self.count = self.count + 1 -reveal_type(Counter().count) # revealed: Unknown | int +# TODO: Should be `Unknown | int` +reveal_type(Counter().count) # revealed: Unknown ``` ### Builtin types attributes @@ -2426,7 +2469,8 @@ class C: def f(self, other: "C"): self.x = (other.x, 1) -reveal_type(C().x) # revealed: Unknown | tuple[Divergent, Literal[1]] +# TODO: Should be `Unknown | tuple[Divergent, Literal[1]]` +reveal_type(C().x) # revealed: Unknown ``` ## References diff --git a/crates/ty_python_semantic/resources/mdtest/call/dunder.md b/crates/ty_python_semantic/resources/mdtest/call/dunder.md index 09b83f0035..a840ca4c2d 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/dunder.md +++ b/crates/ty_python_semantic/resources/mdtest/call/dunder.md @@ -92,7 +92,8 @@ reveal_type(this_fails[0]) # revealed: Unknown However, the attached dunder method *can* be called if accessed directly: ```py -reveal_type(this_fails.__getitem__(this_fails, 0)) # revealed: Unknown | str +# TODO: Should be `Unknown | str` +reveal_type(this_fails.__getitem__(this_fails, 0)) # revealed: Unknown ``` The instance-level method is also not called when the class-level method is present: diff --git a/crates/ty_python_semantic/resources/mdtest/cycle.md b/crates/ty_python_semantic/resources/mdtest/cycle.md index 0bd3b5b2a6..23eb477e32 100644 --- a/crates/ty_python_semantic/resources/mdtest/cycle.md +++ b/crates/ty_python_semantic/resources/mdtest/cycle.md @@ -28,6 +28,49 @@ class Point: self.x, self.y = other.x, other.y p = Point() -reveal_type(p.x) # revealed: Unknown | int -reveal_type(p.y) # revealed: Unknown | int +# TODO: Should be `Unknown | int` +reveal_type(p.x) # revealed: Unknown +# TODO: Should be `Unknown | int` +reveal_type(p.y) # revealed: Unknown +``` + +## Implicit instance attributes + +This is a regression test for : + +```py +def combine(*args) -> int: + return 0 + +class C: + def __init__(self: "C"): + self.x1 = 0 + self.x2 = 0 + self.x3 = 0 + self.x4 = 0 + self.x5 = 0 + + def f1(self: "C"): + self.x1 = combine(self.x2, self.x3, self.x4, self.x5) + self.x2 = combine(self.x1, self.x3, self.x4, self.x5) + self.x3 = combine(self.x1, self.x2, self.x4, self.x5) + self.x4 = combine(self.x1, self.x2, self.x3, self.x5) + self.x5 = combine(self.x1, self.x2, self.x3, self.x4) + + def f2(self: "C"): + self.x1 = combine(self.x2, self.x3, self.x4, self.x5) + self.x2 = combine(self.x1, self.x3, self.x4, self.x5) + self.x3 = combine(self.x1, self.x2, self.x4, self.x5) + self.x4 = combine(self.x1, self.x2, self.x3, self.x5) + self.x5 = combine(self.x1, self.x2, self.x3, self.x4) + + def f3(self: "C"): + self.x1 = combine(self.x2, self.x3, self.x4, self.x5) + self.x2 = combine(self.x1, self.x3, self.x4, self.x5) + self.x3 = combine(self.x1, self.x2, self.x4, self.x5) + self.x4 = combine(self.x1, self.x2, self.x3, self.x5) + self.x5 = combine(self.x1, self.x2, self.x3, self.x4) + +# TODO: should be `Unknown | int` +reveal_type(C().x1) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md index 9210c84483..1a393caec0 100644 --- a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md +++ b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md @@ -127,7 +127,8 @@ c = C() reveal_type(c.data_descriptor) # revealed: Unknown | Literal["data"] -reveal_type(c.non_data_descriptor) # revealed: Unknown | Literal["non-data", 1] +# TODO: Should be `Unknown | Literal["non-data", 1]` +reveal_type(c.non_data_descriptor) # revealed: Unknown | Literal["non-data"] reveal_type(C.data_descriptor) # revealed: Unknown | Literal["data"] @@ -172,7 +173,8 @@ def f1(flag: bool): def f(self): self.attr = "normal" - reveal_type(C1().attr) # revealed: Unknown | Literal["data", "normal"] + # TODO: Should be `Unknown | Literal["data", "normal"]` + reveal_type(C1().attr) # revealed: Unknown | Literal["data"] # Assigning to the attribute also causes no `possibly-unbound` diagnostic: C1().attr = 1 @@ -187,7 +189,8 @@ class C2: self.attr = "normal" attr = NonDataDescriptor() -reveal_type(C2().attr) # revealed: Unknown | Literal["non-data", "normal"] +# TODO: Should be `Unknown | Literal["non-data", "normal"]` +reveal_type(C2().attr) # revealed: Unknown | Literal["non-data"] # Assignments always go to the instance attribute in this case C2().attr = 1 diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md index f692c59835..c617c494f4 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md @@ -36,7 +36,8 @@ class _: def _(): reveal_type(a.x) # revealed: int | None reveal_type(a.y) # revealed: Unknown | None - reveal_type(a.z) # revealed: Unknown | None + # TODO: Should be `Unknown | None` + reveal_type(a.z) # revealed: Unknown if False: a = A() @@ -48,7 +49,8 @@ if True: a = A() reveal_type(a.x) # revealed: int | None reveal_type(a.y) # revealed: Unknown | None -reveal_type(a.z) # revealed: Unknown | None +# TODO: Should be `Unknown | None` +reveal_type(a.z) # revealed: Unknown a.x = 0 a.y = 0 @@ -61,7 +63,8 @@ class _: a = A() reveal_type(a.x) # revealed: int | None reveal_type(a.y) # revealed: Unknown | None - reveal_type(a.z) # revealed: Unknown | None + # TODO: Should be `Unknown | None` + reveal_type(a.z) # revealed: Unknown def cond() -> bool: return True @@ -77,7 +80,8 @@ class _: a = A() reveal_type(a.x) # revealed: int | None reveal_type(a.y) # revealed: Unknown | None - reveal_type(a.z) # revealed: Unknown | None + # TODO: Should be `Unknown | None` + reveal_type(a.z) # revealed: Unknown class _: a = A() @@ -85,7 +89,8 @@ class _: class Inner: reveal_type(a.x) # revealed: int | None reveal_type(a.y) # revealed: Unknown | None - reveal_type(a.z) # revealed: Unknown | None + # TODO: Should be `Unknown | None` + reveal_type(a.z) # revealed: Unknown a = A() # error: [unresolved-attribute] diff --git a/crates/ty_python_semantic/resources/mdtest/overloads.md b/crates/ty_python_semantic/resources/mdtest/overloads.md index 8e8e9e524b..48089be4db 100644 --- a/crates/ty_python_semantic/resources/mdtest/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/overloads.md @@ -155,11 +155,13 @@ class Foo: foo = Foo() reveal_type(foo) # revealed: Foo -reveal_type(foo.x) # revealed: Unknown | int | None +# TODO: Should be `Unknown | int | None` +reveal_type(foo.x) # revealed: Unknown foo1 = Foo(1) reveal_type(foo1) # revealed: Foo -reveal_type(foo1.x) # revealed: Unknown | int | None +# TODO: Should be `Unknown | int | None` +reveal_type(foo1.x) # revealed: Unknown ``` ## Version specific diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 663b093e17..d0c0806ed4 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -685,7 +685,8 @@ class HalfUnknownQux: def __init__(self, x: int) -> None: self.x = x -reveal_type(HalfUnknownQux(1).x) # revealed: Unknown | int +# TODO: Should be `Unknown | int` +reveal_type(HalfUnknownQux(1).x) # revealed: Unknown static_assert(not is_subtype_of(HalfUnknownQux, HasX)) static_assert(is_assignable_to(HalfUnknownQux, HasX)) diff --git a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md index 7c68534e10..e2b0a20d1c 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md @@ -97,7 +97,8 @@ reveal_type(C().FINAL_A) # revealed: int reveal_type(C().FINAL_B) # revealed: Literal[1] reveal_type(C().FINAL_C) # revealed: int reveal_type(C().FINAL_D) # revealed: Literal[1] -reveal_type(C().FINAL_E) # revealed: Literal[1] +# TODO: Should be `Literal[1]` +reveal_type(C().FINAL_E) # revealed: @Todo(implicit instance attribute) ``` ## Not modifiable diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9d0e04bbba..efe726a393 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -61,7 +61,6 @@ pub use crate::types::ide_support::{ definitions_for_keyword_argument, definitions_for_name, find_active_signature_from_details, inlay_hint_function_argument_details, }; -use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; use crate::types::signatures::{ParameterForm, walk_signature}; @@ -5209,15 +5208,6 @@ impl<'db> Type<'db> { .unwrap_or_else(|err| err.fallback_enter_type(db)) } - /// Returns the type bound from a context manager with type `self`. - /// - /// This method should only be used outside of type checking because it omits any errors. - /// For type checking, use [`try_enter_with_mode`](Self::try_enter_with_mode) instead. - fn aenter(self, db: &'db dyn Db) -> Type<'db> { - self.try_enter_with_mode(db, EvaluationMode::Async) - .unwrap_or_else(|err| err.fallback_enter_type(db)) - } - /// Given the type of an object that is used as a context manager (i.e. in a `with` statement), /// return the return type of its `__enter__` or `__aenter__` method, which is bound to any potential targets. /// diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index ae44c0cebd..8bd90614f8 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -4,7 +4,7 @@ use super::TypeVarVariance; use super::{ BoundTypeVarInstance, IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, - function::FunctionType, infer_expression_type, infer_unpack_types, + function::FunctionType, infer_expression_type, }; use crate::FxOrderMap; use crate::module_resolver::KnownModule; @@ -31,7 +31,7 @@ use crate::types::{ NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, determine_upper_bound, - infer_definition_types, + infer_definition_types, todo_type, }; use crate::{ Db, FxIndexMap, FxOrderSet, Program, @@ -41,10 +41,7 @@ use crate::{ known_module_symbol, place_from_bindings, place_from_declarations, }, semantic_index::{ - attribute_assignments, - definition::{DefinitionKind, TargetKind}, - place_table, - scope::ScopeId, + attribute_assignments, definition::DefinitionKind, place_table, scope::ScopeId, semantic_index, use_def_map, }, types::{ @@ -3089,131 +3086,16 @@ impl<'db> ClassLiteral<'db> { // unreachable (because of the `continue` above), but there is // nothing to do here. } - DefinitionKind::Assignment(assign) => { - match assign.target_kind() { - TargetKind::Sequence(_, unpack) => { - // We found an unpacking assignment like: - // - // .., self.name, .. = - // (.., self.name, ..) = - // [.., self.name, ..] = + DefinitionKind::Assignment(_) + | DefinitionKind::For(_) + | DefinitionKind::WithItem(_) + | DefinitionKind::Comprehension(_) => { + // TODO: Type inference for unannotated implicit instance attributes leads + // to combinatorial explosion of runtime in some cases, so it is currently + // disabled. See https://github.com/astral-sh/ty/issues/1111 for details. + let inferred_ty = todo_type!("implicit instance attribute"); - let unpacked = infer_unpack_types(db, unpack); - - let inferred_ty = unpacked.expression_type(assign.target(&module)); - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - TargetKind::Single => { - // We found an un-annotated attribute assignment of the form: - // - // self.name = - - let inferred_ty = infer_expression_type( - db, - index.expression(assign.value(&module)), - TypeContext::default(), - ); - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - } - } - DefinitionKind::For(for_stmt) => { - match for_stmt.target_kind() { - TargetKind::Sequence(_, unpack) => { - // We found an unpacking assignment like: - // - // for .., self.name, .. in : - - let unpacked = infer_unpack_types(db, unpack); - let inferred_ty = - unpacked.expression_type(for_stmt.target(&module)); - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - TargetKind::Single => { - // We found an attribute assignment like: - // - // for self.name in : - - let iterable_ty = infer_expression_type( - db, - index.expression(for_stmt.iterable(&module)), - TypeContext::default(), - ); - // TODO: Potential diagnostics resulting from the iterable are currently not reported. - let inferred_ty = - iterable_ty.iterate(db).homogeneous_element_type(db); - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - } - } - DefinitionKind::WithItem(with_item) => { - match with_item.target_kind() { - TargetKind::Sequence(_, unpack) => { - // We found an unpacking assignment like: - // - // with as .., self.name, ..: - - let unpacked = infer_unpack_types(db, unpack); - let inferred_ty = - unpacked.expression_type(with_item.target(&module)); - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - TargetKind::Single => { - // We found an attribute assignment like: - // - // with as self.name: - - let context_ty = infer_expression_type( - db, - index.expression(with_item.context_expr(&module)), - TypeContext::default(), - ); - let inferred_ty = if with_item.is_async() { - context_ty.aenter(db) - } else { - context_ty.enter(db) - }; - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - } - } - DefinitionKind::Comprehension(comprehension) => { - match comprehension.target_kind() { - TargetKind::Sequence(_, unpack) => { - // We found an unpacking assignment like: - // - // [... for .., self.name, .. in ] - - let unpacked = infer_unpack_types(db, unpack); - - let inferred_ty = - unpacked.expression_type(comprehension.target(&module)); - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - TargetKind::Single => { - // We found an attribute assignment like: - // - // [... for self.name in ] - - let iterable_ty = infer_expression_type( - db, - index.expression(comprehension.iterable(&module)), - TypeContext::default(), - ); - // TODO: Potential diagnostics resulting from the iterable are currently not reported. - let inferred_ty = - iterable_ty.iterate(db).homogeneous_element_type(db); - - union_of_inferred_types = union_of_inferred_types.add(inferred_ty); - } - } + union_of_inferred_types = union_of_inferred_types.add(inferred_ty); } DefinitionKind::AugmentedAssignment(_) => { // TODO: