[ty] Do not infer types for unannotated implicit instance attribtues

This commit is contained in:
David Peter 2025-10-06 15:23:01 +02:00
parent 42b297bf44
commit 618fa922c7
11 changed files with 176 additions and 204 deletions

View File

@ -1640,9 +1640,9 @@ quux.<CURSOR>
); );
assert_snapshot!(test.completions_without_builtins_with_types(), @r" assert_snapshot!(test.completions_without_builtins_with_types(), @r"
bar :: Unknown | Literal[2] bar :: Unknown
baz :: Unknown | Literal[3] baz :: Unknown
foo :: Unknown | Literal[1] foo :: Unknown
__annotations__ :: dict[str, Any] __annotations__ :: dict[str, Any]
__class__ :: type[Quux] __class__ :: type[Quux]
__delattr__ :: bound method Quux.__delattr__(name: str, /) -> None __delattr__ :: bound method Quux.__delattr__(name: str, /) -> None
@ -1685,9 +1685,9 @@ quux.b<CURSOR>
); );
assert_snapshot!(test.completions_without_builtins_with_types(), @r" assert_snapshot!(test.completions_without_builtins_with_types(), @r"
bar :: Unknown | Literal[2] bar :: Unknown
baz :: Unknown | Literal[3] baz :: Unknown
foo :: Unknown | Literal[1] foo :: Unknown
__annotations__ :: dict[str, Any] __annotations__ :: dict[str, Any]
__class__ :: type[Quux] __class__ :: type[Quux]
__delattr__ :: bound method Quux.__delattr__(name: str, /) -> None __delattr__ :: bound method Quux.__delattr__(name: str, /) -> None

View File

@ -25,7 +25,8 @@ class C:
c_instance = C(1) 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"]` # TODO: Same here. This should be `Unknown | Literal[1, "a"]`
reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown 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. # something that we might want to change in the future.
# #
# See https://github.com/astral-sh/ruff/issues/15960 for a related discussion. # 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 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 # 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_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 #### Variable defined in non-`__init__` method
@ -175,12 +178,14 @@ class C:
c_instance = C(1) 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"]` # TODO: Should be `Unknown | Literal[1, "a"]`
reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown 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 reveal_type(c_instance.declared_only) # revealed: bytes
@ -224,7 +229,8 @@ class C:
c_instance = 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.y) # revealed: int
reveal_type(c_instance.z) # revealed: int reveal_type(c_instance.z) # revealed: int
``` ```
@ -238,8 +244,10 @@ class C:
c_instance = C() c_instance = C()
reveal_type(c_instance.a) # revealed: Unknown | Literal[1] # TODO: Should be `Unknown | Literal[1]`
reveal_type(c_instance.b) # revealed: 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 #### Augmented assignments
@ -256,7 +264,8 @@ class C:
# TODO: Mypy and pyright do not support this, but it would be great if we could # 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). # 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 #### 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.c1) # revealed: Unknown | int
reveal_type(c_instance.d1) # revealed: Unknown | str 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 # TODO: Should be `Unknown | int`
reveal_type(c_instance.d2) # revealed: Unknown | str reveal_type(c_instance.c2) # revealed: Unknown
# TODO: Should be `Unknown | str`
reveal_type(c_instance.d2) # revealed: Unknown
``` ```
#### Starred assignments #### Starred assignments
@ -296,8 +309,10 @@ class C:
self.a, *self.b = (1, 2, 3) self.a, *self.b = (1, 2, 3)
c_instance = C() c_instance = C()
reveal_type(c_instance.a) # revealed: Unknown | Literal[1] # TODO: Should be `Unknown | Literal[1]`
reveal_type(c_instance.b) # revealed: Unknown | list[Literal[2, 3]] 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) #### Attributes defined in for-loop (unpacking)
@ -333,8 +348,10 @@ class C:
for self.z in NonIterable(): for self.z in NonIterable():
pass pass
reveal_type(C().x) # revealed: Unknown | int # TODO: Should be `Unknown | int`
reveal_type(C().y) # revealed: Unknown | str reveal_type(C().x) # revealed: Unknown
# TODO: Should be `Unknown | str`
reveal_type(C().y) # revealed: Unknown
``` ```
#### Attributes defined in `with` statements #### Attributes defined in `with` statements
@ -354,7 +371,8 @@ class C:
c_instance = 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 #### Attributes defined in `with` statements, but with unpacking
@ -374,8 +392,10 @@ class C:
c_instance = C() c_instance = C()
reveal_type(c_instance.x) # revealed: Unknown | int | None # TODO: Should be `Unknown | int | None`
reveal_type(c_instance.y) # revealed: Unknown | int reveal_type(c_instance.x) # revealed: Unknown
# TODO: Should be `Unknown | int`
reveal_type(c_instance.y) # revealed: Unknown
``` ```
#### Attributes defined in comprehensions #### Attributes defined in comprehensions
@ -462,8 +482,10 @@ c_instance = C()
reveal_type(c_instance.a1) # revealed: str | None reveal_type(c_instance.a1) # revealed: str | None
reveal_type(c_instance.a2) # revealed: str | None reveal_type(c_instance.a2) # revealed: str | None
reveal_type(c_instance.b1) # revealed: Unknown | Literal[1] # TODO: Should be `Unknown | Literal[1]`
reveal_type(c_instance.b2) # revealed: 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 #### Methods that does not use `self` as a first parameter
@ -506,6 +528,7 @@ class C:
# error: [unresolved-attribute] # error: [unresolved-attribute]
reveal_type(C.x) # revealed: Unknown reveal_type(C.x) # revealed: Unknown
# TODO: Should be `Unknown | Literal[1]`
# error: [unresolved-attribute] # error: [unresolved-attribute]
reveal_type(C().x) # revealed: Unknown reveal_type(C().x) # revealed: Unknown
@ -536,7 +559,8 @@ class C:
def f(self) -> None: def f(self) -> None:
self.x = 1 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: And if `staticmethod` is fully qualified, that should also be recognized:
@ -601,14 +625,18 @@ class C:
self.e = e self.e = e
# TODO: this would ideally be `Unknown | Literal[1]` # 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 # TODO: this would ideally raise an `unresolved-attribute` error
reveal_type(C(True).b) # revealed: Unknown | Literal[2] # TODO: Should be `Unknown | Literal[2]`
reveal_type(C(True).c) # revealed: Unknown | Literal[3] | str 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 # Ideally, this would just be `Unknown | Literal[5]`, but we currently do not
# attempt to analyze control flow within methods more closely. All reachable # attempt to analyze control flow within methods more closely. All reachable
# attribute assignments are considered, so `self.x = 4` is also included: # 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] # error: [unresolved-attribute]
reveal_type(C(True).e) # revealed: Unknown 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. # This is because, it is not possible to access a partially-initialized object by normal means.
self.y = 2 self.y = 2
reveal_type(C(False).x) # revealed: Unknown | Literal[1] # TODO: Should be `Unknown | Literal[1]`
reveal_type(C(False).y) # revealed: Unknown | Literal[2] reveal_type(C(False).x) # revealed: Unknown
# TODO: Should be `Unknown | Literal[2]`
reveal_type(C(False).y) # revealed: Unknown
class C: class C:
def __init__(self, b: bytes) -> None: def __init__(self, b: bytes) -> None:
@ -640,8 +670,10 @@ class C:
self.s = s self.s = s
reveal_type(C(b"abc").b) # revealed: Unknown | bytes # TODO: Should be `Unknown | bytes`
reveal_type(C(b"abc").s) # revealed: Unknown | str reveal_type(C(b"abc").b) # revealed: Unknown
# TODO: Should be `Unknown | str`
reveal_type(C(b"abc").s) # revealed: Unknown
class C: class C:
def __init__(self, iter) -> None: def __init__(self, iter) -> None:
@ -654,8 +686,10 @@ class C:
# but we consider the subsequent attributes to be definitely-bound. # but we consider the subsequent attributes to be definitely-bound.
self.y = 2 self.y = 2
reveal_type(C([]).x) # revealed: Unknown | Literal[1] # TODO: Should be `Unknown | Literal[1]`
reveal_type(C([]).y) # revealed: Unknown | Literal[2] 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 #### 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 # for a more realistic example, let's actually call the method
C.class_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" C.pure_class_variable = "overwritten on class"
reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"] reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"]
c_instance = C() 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. # TODO: should raise an error.
c_instance.pure_class_variable = "value set on instance" c_instance.pure_class_variable = "value set on instance"
@ -1335,11 +1371,13 @@ def _(flag: bool):
else: else:
self.y = "b" 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 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" Foo().y = "c"
``` ```
@ -2223,7 +2261,8 @@ class C:
def copy(self, other: "C"): def copy(self, other: "C"):
self.x = other.x 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: 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"): def copy(self, other: "A"):
self.x = other.x self.x = other.x
reveal_type(B().x) # revealed: Unknown | Literal[1] # TODO: Should be `Unknown | Literal[1]`
reveal_type(A().x) # revealed: 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: This case additionally tests our union/intersection simplification logic:
@ -2311,7 +2352,8 @@ class Toggle:
self.y = True self.y = True
reveal_type(Toggle().x) # revealed: Literal[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 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"): def increment(self: "Counter"):
self.count = self.count + 1 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 ### Builtin types attributes
@ -2426,7 +2469,8 @@ class C:
def f(self, other: "C"): def f(self, other: "C"):
self.x = (other.x, 1) 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 ## References

View File

@ -92,7 +92,8 @@ reveal_type(this_fails[0]) # revealed: Unknown
However, the attached dunder method *can* be called if accessed directly: However, the attached dunder method *can* be called if accessed directly:
```py ```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: The instance-level method is also not called when the class-level method is present:

View File

@ -28,6 +28,49 @@ class Point:
self.x, self.y = other.x, other.y self.x, self.y = other.x, other.y
p = Point() p = Point()
reveal_type(p.x) # revealed: Unknown | int # TODO: Should be `Unknown | int`
reveal_type(p.y) # revealed: 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 <https://github.com/astral-sh/ty/issues/1111>:
```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
``` ```

View File

@ -127,7 +127,8 @@ c = C()
reveal_type(c.data_descriptor) # revealed: Unknown | Literal["data"] 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"] reveal_type(C.data_descriptor) # revealed: Unknown | Literal["data"]
@ -172,7 +173,8 @@ def f1(flag: bool):
def f(self): def f(self):
self.attr = "normal" 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: # Assigning to the attribute also causes no `possibly-unbound` diagnostic:
C1().attr = 1 C1().attr = 1
@ -187,7 +189,8 @@ class C2:
self.attr = "normal" self.attr = "normal"
attr = NonDataDescriptor() 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 # Assignments always go to the instance attribute in this case
C2().attr = 1 C2().attr = 1

View File

@ -36,7 +36,8 @@ class _:
def _(): def _():
reveal_type(a.x) # revealed: int | None reveal_type(a.x) # revealed: int | None
reveal_type(a.y) # revealed: Unknown | 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: if False:
a = A() a = A()
@ -48,7 +49,8 @@ if True:
a = A() a = A()
reveal_type(a.x) # revealed: int | None reveal_type(a.x) # revealed: int | None
reveal_type(a.y) # revealed: Unknown | 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.x = 0
a.y = 0 a.y = 0
@ -61,7 +63,8 @@ class _:
a = A() a = A()
reveal_type(a.x) # revealed: int | None reveal_type(a.x) # revealed: int | None
reveal_type(a.y) # revealed: Unknown | 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: def cond() -> bool:
return True return True
@ -77,7 +80,8 @@ class _:
a = A() a = A()
reveal_type(a.x) # revealed: int | None reveal_type(a.x) # revealed: int | None
reveal_type(a.y) # revealed: Unknown | 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 _: class _:
a = A() a = A()
@ -85,7 +89,8 @@ class _:
class Inner: class Inner:
reveal_type(a.x) # revealed: int | None reveal_type(a.x) # revealed: int | None
reveal_type(a.y) # revealed: Unknown | 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() a = A()
# error: [unresolved-attribute] # error: [unresolved-attribute]

View File

@ -155,11 +155,13 @@ class Foo:
foo = Foo() foo = Foo()
reveal_type(foo) # revealed: 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) foo1 = Foo(1)
reveal_type(foo1) # revealed: Foo 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 ## Version specific

View File

@ -685,7 +685,8 @@ class HalfUnknownQux:
def __init__(self, x: int) -> None: def __init__(self, x: int) -> None:
self.x = x 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(not is_subtype_of(HalfUnknownQux, HasX))
static_assert(is_assignable_to(HalfUnknownQux, HasX)) static_assert(is_assignable_to(HalfUnknownQux, HasX))

View File

@ -97,7 +97,8 @@ reveal_type(C().FINAL_A) # revealed: int
reveal_type(C().FINAL_B) # revealed: Literal[1] reveal_type(C().FINAL_B) # revealed: Literal[1]
reveal_type(C().FINAL_C) # revealed: int reveal_type(C().FINAL_C) # revealed: int
reveal_type(C().FINAL_D) # revealed: Literal[1] 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 ## Not modifiable

View File

@ -61,7 +61,6 @@ pub use crate::types::ide_support::{
definitions_for_keyword_argument, definitions_for_name, find_active_signature_from_details, definitions_for_keyword_argument, definitions_for_name, find_active_signature_from_details,
inlay_hint_function_argument_details, inlay_hint_function_argument_details,
}; };
use crate::types::infer::infer_unpack_types;
use crate::types::mro::{Mro, MroError, MroIterator}; use crate::types::mro::{Mro, MroError, MroIterator};
pub(crate) use crate::types::narrow::infer_narrowing_constraint; pub(crate) use crate::types::narrow::infer_narrowing_constraint;
use crate::types::signatures::{ParameterForm, walk_signature}; use crate::types::signatures::{ParameterForm, walk_signature};
@ -5209,15 +5208,6 @@ impl<'db> Type<'db> {
.unwrap_or_else(|err| err.fallback_enter_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), /// 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. /// return the return type of its `__enter__` or `__aenter__` method, which is bound to any potential targets.
/// ///

View File

@ -4,7 +4,7 @@ use super::TypeVarVariance;
use super::{ use super::{
BoundTypeVarInstance, IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, BoundTypeVarInstance, IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator,
SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, 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::FxOrderMap;
use crate::module_resolver::KnownModule; use crate::module_resolver::KnownModule;
@ -31,7 +31,7 @@ use crate::types::{
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, determine_upper_bound, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, determine_upper_bound,
infer_definition_types, infer_definition_types, todo_type,
}; };
use crate::{ use crate::{
Db, FxIndexMap, FxOrderSet, Program, Db, FxIndexMap, FxOrderSet, Program,
@ -41,10 +41,7 @@ use crate::{
known_module_symbol, place_from_bindings, place_from_declarations, known_module_symbol, place_from_bindings, place_from_declarations,
}, },
semantic_index::{ semantic_index::{
attribute_assignments, attribute_assignments, definition::DefinitionKind, place_table, scope::ScopeId,
definition::{DefinitionKind, TargetKind},
place_table,
scope::ScopeId,
semantic_index, use_def_map, semantic_index, use_def_map,
}, },
types::{ types::{
@ -3089,131 +3086,16 @@ impl<'db> ClassLiteral<'db> {
// unreachable (because of the `continue` above), but there is // unreachable (because of the `continue` above), but there is
// nothing to do here. // nothing to do here.
} }
DefinitionKind::Assignment(assign) => { DefinitionKind::Assignment(_)
match assign.target_kind() { | DefinitionKind::For(_)
TargetKind::Sequence(_, unpack) => { | DefinitionKind::WithItem(_)
// We found an unpacking assignment like: | DefinitionKind::Comprehension(_) => {
// // TODO: Type inference for unannotated implicit instance attributes leads
// .., self.name, .. = <value> // to combinatorial explosion of runtime in some cases, so it is currently
// (.., self.name, ..) = <value> // disabled. See https://github.com/astral-sh/ty/issues/1111 for details.
// [.., self.name, ..] = <value> let inferred_ty = todo_type!("implicit instance attribute");
let unpacked = infer_unpack_types(db, unpack); union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
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 = <value>
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 <iterable>:
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 <iterable>:
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 <context_manager> 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 <context_manager> 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 <iterable>]
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 <iterable>]
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);
}
}
} }
DefinitionKind::AugmentedAssignment(_) => { DefinitionKind::AugmentedAssignment(_) => {
// TODO: // TODO: