mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-20 04:29:47 +00:00
[ty] Infer type for implicit self parameters in method bodies (#20922)
## Summary Infer a type of `Self` for unannotated `self` parameters in methods of classes. part of https://github.com/astral-sh/ty/issues/159 closes https://github.com/astral-sh/ty/issues/1081 ## Conformance tests changes ```diff +enums_member_values.py:85:9: error[invalid-assignment] Object of type `int` is not assignable to attribute `_value_` of type `str` ``` A true positive ✔️ ```diff -generics_self_advanced.py:35:9: error[type-assertion-failure] Argument does not have asserted type `Self@method2` -generics_self_basic.py:14:9: error[type-assertion-failure] Argument does not have asserted type `Self@set_scale ``` Two false positives going away ✔️ ```diff +generics_syntax_infer_variance.py:82:9: error[invalid-assignment] Cannot assign to final attribute `x` on type `Self@__init__` ``` This looks like a true positive to me, even if it's not marked with `# E` ✔️ ```diff +protocols_explicit.py:56:9: error[invalid-assignment] Object of type `tuple[int, int, str]` is not assignable to attribute `rgb` of type `tuple[int, int, int]` ``` True positive ✔️ ``` +protocols_explicit.py:85:9: error[invalid-attribute-access] Cannot assign to ClassVar `cm1` from an instance of type `Self@__init__` ``` This looks like a true positive to me, even if it's not marked with `# E`. But this is consistent with our understanding of `ClassVar`, I think. ✔️ ```py +qualifiers_final_annotation.py:52:9: error[invalid-assignment] Cannot assign to final attribute `ID4` on type `Self@__init__` +qualifiers_final_annotation.py:65:9: error[invalid-assignment] Cannot assign to final attribute `ID7` on type `Self@method1` ``` New true positives ✔️ ```py +qualifiers_final_annotation.py:52:9: error[invalid-assignment] Cannot assign to final attribute `ID4` on type `Self@__init__` +qualifiers_final_annotation.py:57:13: error[invalid-assignment] Cannot assign to final attribute `ID6` on type `Self@__init__` +qualifiers_final_annotation.py:59:13: error[invalid-assignment] Cannot assign to final attribute `ID6` on type `Self@__init__` ``` This is a new false positive, but that's a pre-existing issue on main (if you annotate with `Self`): https://play.ty.dev/3ee1c56d-7e13-43bb-811a-7a81e236e6ab ❌ => reported as https://github.com/astral-sh/ty/issues/1409 ## Ecosystem * There are 5931 new `unresolved-attribute` and 3292 new `possibly-missing-attribute` attribute errors, way too many to look at all of them. I randomly sampled 15 of these errors and found: * 13 instances where there was simply no such attribute that we could plausibly see. Sometimes [I didn't find it anywhere](8644d886c6/openlibrary/plugins/openlibrary/tests/test_listapi.py (L33)). Sometimes it was set externally on the object. Sometimes there was some [`setattr` dynamicness going on](a49f6b927d/setuptools/wheel.py (L88-L94)). I would consider all of them to be true positives. * 1 instance where [attribute was set on `obj` in `__new__`](9e87b44fd4/sympy/tensor/array/array_comprehension.py (L45C1-L45C36)), which we don't support yet * 1 instance [where the attribute was defined via `__slots__` ](e250ec0fc8/lib/spack/spack/vendor/pyrsistent/_pdeque.py (L48C5-L48C14)) * I see 44 instances [of the false positive above](https://github.com/astral-sh/ty/issues/1409) with `Final` instance attributes being set in `__init__`. I don't think this should block this PR. ## Test Plan New Markdown tests. --------- Co-authored-by: Shaygan Hooshyari <sh.hooshyari@gmail.com>
This commit is contained in:
parent
76a55314e4
commit
589e8ac0d9
16 changed files with 325 additions and 210 deletions
|
|
@ -667,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY313,
|
python_version: PythonVersion::PY313,
|
||||||
},
|
},
|
||||||
100,
|
110,
|
||||||
);
|
);
|
||||||
|
|
||||||
bench_project(&benchmark, criterion);
|
bench_project(&benchmark, criterion);
|
||||||
|
|
@ -684,7 +684,7 @@ fn anyio(criterion: &mut Criterion) {
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY313,
|
python_version: PythonVersion::PY313,
|
||||||
},
|
},
|
||||||
100,
|
150,
|
||||||
);
|
);
|
||||||
|
|
||||||
bench_project(&benchmark, criterion);
|
bench_project(&benchmark, criterion);
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ static TANJUN: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY312,
|
python_version: PythonVersion::PY312,
|
||||||
},
|
},
|
||||||
100,
|
320,
|
||||||
);
|
);
|
||||||
|
|
||||||
static STATIC_FRAME: Benchmark = Benchmark::new(
|
static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||||
|
|
@ -226,7 +226,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-08-09",
|
max_dep_date: "2025-08-09",
|
||||||
python_version: PythonVersion::PY311,
|
python_version: PythonVersion::PY311,
|
||||||
},
|
},
|
||||||
630,
|
750,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
|
||||||
|
|
@ -1957,14 +1957,34 @@ class Quux:
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIXME: This should list completions on `self`, which should
|
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||||
// include, at least, `foo` and `bar`. At time of writing
|
bar
|
||||||
// (2025-06-04), the type of `self` is inferred as `Unknown` in
|
baz
|
||||||
// this context. This in turn prevents us from getting a list
|
foo
|
||||||
// of available attributes.
|
__annotations__
|
||||||
//
|
__class__
|
||||||
// See: https://github.com/astral-sh/ty/issues/159
|
__delattr__
|
||||||
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
__dict__
|
||||||
|
__dir__
|
||||||
|
__doc__
|
||||||
|
__eq__
|
||||||
|
__format__
|
||||||
|
__getattribute__
|
||||||
|
__getstate__
|
||||||
|
__hash__
|
||||||
|
__init__
|
||||||
|
__init_subclass__
|
||||||
|
__module__
|
||||||
|
__ne__
|
||||||
|
__new__
|
||||||
|
__reduce__
|
||||||
|
__reduce_ex__
|
||||||
|
__repr__
|
||||||
|
__setattr__
|
||||||
|
__sizeof__
|
||||||
|
__str__
|
||||||
|
__subclasshook__
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1798,23 +1798,23 @@ class BoundedContainer[T: int, U = str]:
|
||||||
"T" @ 554..555: TypeParameter
|
"T" @ 554..555: TypeParameter
|
||||||
"value2" @ 557..563: Parameter
|
"value2" @ 557..563: Parameter
|
||||||
"U" @ 565..566: TypeParameter
|
"U" @ 565..566: TypeParameter
|
||||||
"self" @ 577..581: Variable
|
"self" @ 577..581: TypeParameter
|
||||||
"value1" @ 582..588: Variable
|
"value1" @ 582..588: Variable
|
||||||
"T" @ 590..591: TypeParameter
|
"T" @ 590..591: TypeParameter
|
||||||
"value1" @ 594..600: Parameter
|
"value1" @ 594..600: Parameter
|
||||||
"self" @ 609..613: Variable
|
"self" @ 609..613: TypeParameter
|
||||||
"value2" @ 614..620: Variable
|
"value2" @ 614..620: Variable
|
||||||
"U" @ 622..623: TypeParameter
|
"U" @ 622..623: TypeParameter
|
||||||
"value2" @ 626..632: Parameter
|
"value2" @ 626..632: Parameter
|
||||||
"get_first" @ 642..651: Method [definition]
|
"get_first" @ 642..651: Method [definition]
|
||||||
"self" @ 652..656: SelfParameter
|
"self" @ 652..656: SelfParameter
|
||||||
"T" @ 661..662: TypeParameter
|
"T" @ 661..662: TypeParameter
|
||||||
"self" @ 679..683: Variable
|
"self" @ 679..683: TypeParameter
|
||||||
"value1" @ 684..690: Variable
|
"value1" @ 684..690: Variable
|
||||||
"get_second" @ 700..710: Method [definition]
|
"get_second" @ 700..710: Method [definition]
|
||||||
"self" @ 711..715: SelfParameter
|
"self" @ 711..715: SelfParameter
|
||||||
"U" @ 720..721: TypeParameter
|
"U" @ 720..721: TypeParameter
|
||||||
"self" @ 738..742: Variable
|
"self" @ 738..742: TypeParameter
|
||||||
"value2" @ 743..749: Variable
|
"value2" @ 743..749: Variable
|
||||||
"BoundedContainer" @ 798..814: Class [definition]
|
"BoundedContainer" @ 798..814: Class [definition]
|
||||||
"T" @ 815..816: TypeParameter [definition]
|
"T" @ 815..816: TypeParameter [definition]
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ In instance methods, the first parameter (regardless of its name) is assumed to
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[environment]
|
[environment]
|
||||||
python-version = "3.11"
|
python-version = "3.12"
|
||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
@ -64,16 +64,30 @@ from typing import Self
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def implicit_self(self) -> Self:
|
def implicit_self(self) -> Self:
|
||||||
# TODO: This should be Self@implicit_self
|
reveal_type(self) # revealed: Self@implicit_self
|
||||||
reveal_type(self) # revealed: Unknown
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def a_method(self) -> int:
|
def implicit_self_generic[T](self, x: T) -> T:
|
||||||
def first_arg_is_not_self(a: int) -> int:
|
reveal_type(self) # revealed: Self@implicit_self_generic
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
def method_a(self) -> None:
|
||||||
|
def first_param_is_not_self(a: int):
|
||||||
reveal_type(a) # revealed: int
|
reveal_type(a) # revealed: int
|
||||||
return a
|
reveal_type(self) # revealed: Self@method_a
|
||||||
return first_arg_is_not_self(1)
|
|
||||||
|
def first_param_is_not_self_unannotated(a):
|
||||||
|
reveal_type(a) # revealed: Unknown
|
||||||
|
reveal_type(self) # revealed: Self@method_a
|
||||||
|
|
||||||
|
def first_param_is_also_not_self(self) -> None:
|
||||||
|
reveal_type(self) # revealed: Unknown
|
||||||
|
|
||||||
|
def first_param_is_explicit_self(this: Self) -> None:
|
||||||
|
reveal_type(this) # revealed: Self@method_a
|
||||||
|
reveal_type(self) # revealed: Self@method_a
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def a_classmethod(cls) -> Self:
|
def a_classmethod(cls) -> Self:
|
||||||
|
|
@ -127,19 +141,16 @@ The name `self` is not special in any way.
|
||||||
```py
|
```py
|
||||||
class B:
|
class B:
|
||||||
def name_does_not_matter(this) -> Self:
|
def name_does_not_matter(this) -> Self:
|
||||||
# TODO: Should reveal Self@name_does_not_matter
|
reveal_type(this) # revealed: Self@name_does_not_matter
|
||||||
reveal_type(this) # revealed: Unknown
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
|
||||||
def positional_only(self, /, x: int) -> Self:
|
def positional_only(self, /, x: int) -> Self:
|
||||||
# TODO: Should reveal Self@positional_only
|
reveal_type(self) # revealed: Self@positional_only
|
||||||
reveal_type(self) # revealed: Unknown
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def keyword_only(self, *, x: int) -> Self:
|
def keyword_only(self, *, x: int) -> Self:
|
||||||
# TODO: Should reveal Self@keyword_only
|
reveal_type(self) # revealed: Self@keyword_only
|
||||||
reveal_type(self) # revealed: Unknown
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -165,8 +176,7 @@ T = TypeVar("T")
|
||||||
|
|
||||||
class G(Generic[T]):
|
class G(Generic[T]):
|
||||||
def id(self) -> Self:
|
def id(self) -> Self:
|
||||||
# TODO: Should reveal Self@id
|
reveal_type(self) # revealed: Self@id
|
||||||
reveal_type(self) # revealed: Unknown
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
@ -252,6 +262,20 @@ class LinkedList:
|
||||||
reveal_type(LinkedList().next()) # revealed: LinkedList
|
reveal_type(LinkedList().next()) # revealed: LinkedList
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Attributes can also refer to a generic parameter:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
class C(Generic[T]):
|
||||||
|
foo: T
|
||||||
|
def method(self) -> None:
|
||||||
|
reveal_type(self) # revealed: Self@method
|
||||||
|
reveal_type(self.foo) # revealed: T@C
|
||||||
|
```
|
||||||
|
|
||||||
## Generic Classes
|
## Generic Classes
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
@ -342,31 +366,28 @@ b: Self
|
||||||
|
|
||||||
# TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self"
|
# TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self"
|
||||||
class Foo:
|
class Foo:
|
||||||
# TODO: rejected Self because self has a different type
|
# TODO: This `self: T` annotation should be rejected because `T` is not `Self`
|
||||||
def has_existing_self_annotation(self: T) -> Self:
|
def has_existing_self_annotation(self: T) -> Self:
|
||||||
return self # error: [invalid-return-type]
|
return self # error: [invalid-return-type]
|
||||||
|
|
||||||
def return_concrete_type(self) -> Self:
|
def return_concrete_type(self) -> Self:
|
||||||
# TODO: tell user to use "Foo" instead of "Self"
|
# TODO: We could emit a hint that suggests annotating with `Foo` instead of `Self`
|
||||||
# error: [invalid-return-type]
|
# error: [invalid-return-type]
|
||||||
return Foo()
|
return Foo()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
# TODO: reject because of staticmethod
|
# TODO: The usage of `Self` here should be rejected because this is a static method
|
||||||
def make() -> Self:
|
def make() -> Self:
|
||||||
# error: [invalid-return-type]
|
# error: [invalid-return-type]
|
||||||
return Foo()
|
return Foo()
|
||||||
|
|
||||||
class Bar(Generic[T]):
|
class Bar(Generic[T]): ...
|
||||||
foo: T
|
|
||||||
def bar(self) -> T:
|
|
||||||
return self.foo
|
|
||||||
|
|
||||||
# error: [invalid-type-form]
|
# error: [invalid-type-form]
|
||||||
class Baz(Bar[Self]): ...
|
class Baz(Bar[Self]): ...
|
||||||
|
|
||||||
class MyMetaclass(type):
|
class MyMetaclass(type):
|
||||||
# TODO: rejected
|
# TODO: reject the Self usage. because self cannot be used within a metaclass.
|
||||||
def __new__(cls) -> Self:
|
def __new__(cls) -> Self:
|
||||||
return super().__new__(cls)
|
return super().__new__(cls)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ class C:
|
||||||
c_instance = C(1)
|
c_instance = C(1)
|
||||||
|
|
||||||
reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"]
|
reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"]
|
||||||
|
reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown | Literal[1, "a"]
|
||||||
# TODO: Same here. This should be `Unknown | Literal[1, "a"]`
|
|
||||||
reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
|
|
||||||
|
|
||||||
# There is no special handling of attributes that are (directly) assigned to a declared parameter,
|
# There is no special handling of attributes that are (directly) assigned to a declared parameter,
|
||||||
# which means we union with `Unknown` here, since the attribute itself is not declared. This is
|
# which means we union with `Unknown` here, since the attribute itself is not declared. This is
|
||||||
|
|
@ -177,8 +175,7 @@ c_instance = C(1)
|
||||||
|
|
||||||
reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"]
|
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_other_attribute) # revealed: 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
|
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
|
||||||
|
|
||||||
|
|
@ -399,9 +396,19 @@ class TupleIterable:
|
||||||
|
|
||||||
class C:
|
class C:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
# TODO: Should not emit this diagnostic
|
||||||
|
# error: [unresolved-attribute]
|
||||||
[... for self.a in IntIterable()]
|
[... for self.a in IntIterable()]
|
||||||
|
# TODO: Should not emit this diagnostic
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
# error: [unresolved-attribute]
|
||||||
[... for (self.b, self.c) in TupleIterable()]
|
[... for (self.b, self.c) in TupleIterable()]
|
||||||
|
# TODO: Should not emit this diagnostic
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
# error: [unresolved-attribute]
|
||||||
[... for self.d in IntIterable() for self.e in IntIterable()]
|
[... for self.d in IntIterable() for self.e in IntIterable()]
|
||||||
|
# TODO: Should not emit this diagnostic
|
||||||
|
# error: [unresolved-attribute]
|
||||||
[[... for self.f in IntIterable()] for _ in IntIterable()]
|
[[... for self.f in IntIterable()] for _ in IntIterable()]
|
||||||
[[... for self.g in IntIterable()] for self in [D()]]
|
[[... for self.g in IntIterable()] for self in [D()]]
|
||||||
|
|
||||||
|
|
@ -598,6 +605,8 @@ class C:
|
||||||
self.c = c
|
self.c = c
|
||||||
if False:
|
if False:
|
||||||
def set_e(self, e: str) -> None:
|
def set_e(self, e: str) -> None:
|
||||||
|
# TODO: Should not emit this diagnostic
|
||||||
|
# error: [unresolved-attribute]
|
||||||
self.e = e
|
self.e = e
|
||||||
|
|
||||||
# TODO: this would ideally be `Unknown | Literal[1]`
|
# TODO: this would ideally be `Unknown | Literal[1]`
|
||||||
|
|
@ -685,7 +694,7 @@ class C:
|
||||||
pure_class_variable2: ClassVar = 1
|
pure_class_variable2: ClassVar = 1
|
||||||
|
|
||||||
def method(self):
|
def method(self):
|
||||||
# TODO: this should be an error
|
# error: [invalid-attribute-access] "Cannot assign to ClassVar `pure_class_variable1` from an instance of type `Self@method`"
|
||||||
self.pure_class_variable1 = "value set through instance"
|
self.pure_class_variable1 = "value set through instance"
|
||||||
|
|
||||||
reveal_type(C.pure_class_variable1) # revealed: str
|
reveal_type(C.pure_class_variable1) # revealed: str
|
||||||
|
|
@ -885,11 +894,9 @@ class Intermediate(Base):
|
||||||
# TODO: This should be an error (violates Liskov)
|
# TODO: This should be an error (violates Liskov)
|
||||||
self.redeclared_in_method_with_wider_type: object = object()
|
self.redeclared_in_method_with_wider_type: object = object()
|
||||||
|
|
||||||
# TODO: This should be an `invalid-assignment` error
|
self.overwritten_in_subclass_method = None # error: [invalid-assignment]
|
||||||
self.overwritten_in_subclass_method = None
|
|
||||||
|
|
||||||
# TODO: This should be an `invalid-assignment` error
|
self.pure_overwritten_in_subclass_method = None # error: [invalid-assignment]
|
||||||
self.pure_overwritten_in_subclass_method = None
|
|
||||||
|
|
||||||
self.pure_undeclared = "intermediate"
|
self.pure_undeclared = "intermediate"
|
||||||
|
|
||||||
|
|
@ -1839,6 +1846,7 @@ def external_getattribute(name) -> int:
|
||||||
|
|
||||||
class ThisFails:
|
class ThisFails:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# error: [invalid-assignment] "Implicit shadowing of function `__getattribute__`"
|
||||||
self.__getattribute__ = external_getattribute
|
self.__getattribute__ = external_getattribute
|
||||||
|
|
||||||
# error: [unresolved-attribute]
|
# error: [unresolved-attribute]
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ class C:
|
||||||
return str(key)
|
return str(key)
|
||||||
|
|
||||||
def f(self):
|
def f(self):
|
||||||
# TODO: This should emit an `invalid-assignment` diagnostic once we understand the type of `self`
|
# error: [invalid-assignment] "Implicit shadowing of function `__getitem__`"
|
||||||
self.__getitem__ = None
|
self.__getitem__ = None
|
||||||
|
|
||||||
# This is still fine, and simply calls the `__getitem__` method on the class
|
# This is still fine, and simply calls the `__getitem__` method on the class
|
||||||
|
|
|
||||||
|
|
@ -163,14 +163,13 @@ class A:
|
||||||
|
|
||||||
class B(A):
|
class B(A):
|
||||||
def __init__(self, a: int):
|
def __init__(self, a: int):
|
||||||
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
|
reveal_type(super()) # revealed: <super: <class 'B'>, B>
|
||||||
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
|
||||||
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
|
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
|
||||||
super().__init__(a)
|
super().__init__(a)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def f(cls):
|
def f(cls):
|
||||||
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
|
# TODO: Once `cls` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
|
||||||
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
||||||
super().f()
|
super().f()
|
||||||
|
|
||||||
|
|
@ -358,15 +357,15 @@ from __future__ import annotations
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def test(self):
|
def test(self):
|
||||||
reveal_type(super()) # revealed: <super: <class 'A'>, Unknown>
|
reveal_type(super()) # revealed: <super: <class 'A'>, A>
|
||||||
|
|
||||||
class B:
|
class B:
|
||||||
def test(self):
|
def test(self):
|
||||||
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
reveal_type(super()) # revealed: <super: <class 'B'>, B>
|
||||||
|
|
||||||
class C(A.B):
|
class C(A.B):
|
||||||
def test(self):
|
def test(self):
|
||||||
reveal_type(super()) # revealed: <super: <class 'C'>, Unknown>
|
reveal_type(super()) # revealed: <super: <class 'C'>, C>
|
||||||
|
|
||||||
def inner(t: C):
|
def inner(t: C):
|
||||||
reveal_type(super()) # revealed: <super: <class 'B'>, C>
|
reveal_type(super()) # revealed: <super: <class 'B'>, C>
|
||||||
|
|
@ -616,7 +615,7 @@ class A:
|
||||||
class B(A):
|
class B(A):
|
||||||
def __init__(self, a: int):
|
def __init__(self, a: int):
|
||||||
super().__init__(a)
|
super().__init__(a)
|
||||||
# TODO: Once `Self` is supported, this should raise `unresolved-attribute` error
|
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
|
||||||
super().a
|
super().a
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
|
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,7 @@ def f1(flag: bool):
|
||||||
attr = DataDescriptor()
|
attr = DataDescriptor()
|
||||||
|
|
||||||
def f(self):
|
def f(self):
|
||||||
|
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `attr` on type `Self@f` with custom `__set__` method"
|
||||||
self.attr = "normal"
|
self.attr = "normal"
|
||||||
|
|
||||||
reveal_type(C1().attr) # revealed: Unknown | Literal["data", "normal"]
|
reveal_type(C1().attr) # revealed: Unknown | Literal["data", "normal"]
|
||||||
|
|
|
||||||
|
|
@ -208,8 +208,7 @@ class SuperUser(User):
|
||||||
def now_called_robert(self):
|
def now_called_robert(self):
|
||||||
self.name = "Robert" # fine because overridden with a mutable attribute
|
self.name = "Robert" # fine because overridden with a mutable attribute
|
||||||
|
|
||||||
# TODO: this should cause us to emit an error as we're assigning to a read-only property
|
# error: 9 [invalid-assignment] "Cannot assign to read-only property `nickname` on object of type `Self@now_called_robert`"
|
||||||
# inherited from the `NamedTuple` superclass (requires https://github.com/astral-sh/ty/issues/159)
|
|
||||||
self.nickname = "Bob"
|
self.nickname = "Bob"
|
||||||
|
|
||||||
james = SuperUser(0, "James", 42, "Jimmy")
|
james = SuperUser(0, "James", 42, "Jimmy")
|
||||||
|
|
|
||||||
|
|
@ -21,144 +21,143 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
||||||
7 |
|
7 |
|
||||||
8 | class B(A):
|
8 | class B(A):
|
||||||
9 | def __init__(self, a: int):
|
9 | def __init__(self, a: int):
|
||||||
10 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
|
10 | reveal_type(super()) # revealed: <super: <class 'B'>, B>
|
||||||
11 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
11 | reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
|
||||||
12 | reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
|
12 | super().__init__(a)
|
||||||
13 | super().__init__(a)
|
13 |
|
||||||
14 |
|
14 | @classmethod
|
||||||
15 | @classmethod
|
15 | def f(cls):
|
||||||
16 | def f(cls):
|
16 | # TODO: Once `cls` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
|
||||||
17 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
|
17 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
||||||
18 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
18 | super().f()
|
||||||
19 | super().f()
|
19 |
|
||||||
20 |
|
20 | super(B, B(42)).__init__(42)
|
||||||
21 | super(B, B(42)).__init__(42)
|
21 | super(B, B).f()
|
||||||
22 | super(B, B).f()
|
22 | import enum
|
||||||
23 | import enum
|
23 | from typing import Any, Self, Never, Protocol, Callable
|
||||||
24 | from typing import Any, Self, Never, Protocol, Callable
|
24 | from ty_extensions import Intersection
|
||||||
25 | from ty_extensions import Intersection
|
25 |
|
||||||
26 |
|
26 | class BuilderMeta(type):
|
||||||
27 | class BuilderMeta(type):
|
27 | def __new__(
|
||||||
28 | def __new__(
|
28 | cls: type[Any],
|
||||||
29 | cls: type[Any],
|
29 | name: str,
|
||||||
30 | name: str,
|
30 | bases: tuple[type, ...],
|
||||||
31 | bases: tuple[type, ...],
|
31 | dct: dict[str, Any],
|
||||||
32 | dct: dict[str, Any],
|
32 | ) -> BuilderMeta:
|
||||||
33 | ) -> BuilderMeta:
|
33 | # revealed: <super: <class 'BuilderMeta'>, Any>
|
||||||
34 | # revealed: <super: <class 'BuilderMeta'>, Any>
|
34 | s = reveal_type(super())
|
||||||
35 | s = reveal_type(super())
|
35 | # revealed: Any
|
||||||
36 | # revealed: Any
|
36 | return reveal_type(s.__new__(cls, name, bases, dct))
|
||||||
37 | return reveal_type(s.__new__(cls, name, bases, dct))
|
37 |
|
||||||
38 |
|
38 | class BuilderMeta2(type):
|
||||||
39 | class BuilderMeta2(type):
|
39 | def __new__(
|
||||||
40 | def __new__(
|
40 | cls: type[BuilderMeta2],
|
||||||
41 | cls: type[BuilderMeta2],
|
41 | name: str,
|
||||||
42 | name: str,
|
42 | bases: tuple[type, ...],
|
||||||
43 | bases: tuple[type, ...],
|
43 | dct: dict[str, Any],
|
||||||
44 | dct: dict[str, Any],
|
44 | ) -> BuilderMeta2:
|
||||||
45 | ) -> BuilderMeta2:
|
45 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
||||||
46 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
46 | s = reveal_type(super())
|
||||||
47 | s = reveal_type(super())
|
47 | # TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
||||||
48 | # TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
48 | # revealed: Unknown
|
||||||
49 | # revealed: Unknown
|
49 | return reveal_type(s.__new__(cls, name, bases, dct))
|
||||||
50 | return reveal_type(s.__new__(cls, name, bases, dct))
|
50 |
|
||||||
51 |
|
51 | class Foo[T]:
|
||||||
52 | class Foo[T]:
|
52 | x: T
|
||||||
53 | x: T
|
53 |
|
||||||
54 |
|
54 | def method(self: Any):
|
||||||
55 | def method(self: Any):
|
55 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
56 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
56 |
|
||||||
57 |
|
57 | if isinstance(self, Foo):
|
||||||
58 | if isinstance(self, Foo):
|
58 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
59 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
59 |
|
||||||
60 |
|
60 | def method2(self: Foo[T]):
|
||||||
61 | def method2(self: Foo[T]):
|
61 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
62 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
62 | reveal_type(super())
|
||||||
63 | reveal_type(super())
|
63 |
|
||||||
64 |
|
64 | def method3(self: Foo):
|
||||||
65 | def method3(self: Foo):
|
65 | # revealed: <super: <class 'Foo'>, Foo[Unknown]>
|
||||||
66 | # revealed: <super: <class 'Foo'>, Foo[Unknown]>
|
66 | reveal_type(super())
|
||||||
67 | reveal_type(super())
|
67 |
|
||||||
68 |
|
68 | def method4(self: Self):
|
||||||
69 | def method4(self: Self):
|
69 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
70 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
70 | reveal_type(super())
|
||||||
71 | reveal_type(super())
|
71 |
|
||||||
72 |
|
72 | def method5[S: Foo[int]](self: S, other: S) -> S:
|
||||||
73 | def method5[S: Foo[int]](self: S, other: S) -> S:
|
73 | # revealed: <super: <class 'Foo'>, Foo[int]>
|
||||||
74 | # revealed: <super: <class 'Foo'>, Foo[int]>
|
74 | reveal_type(super())
|
||||||
75 | reveal_type(super())
|
75 | return self
|
||||||
76 | return self
|
76 |
|
||||||
77 |
|
77 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
|
||||||
78 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
|
78 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
|
||||||
79 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
|
79 | reveal_type(super())
|
||||||
80 | reveal_type(super())
|
80 | return self
|
||||||
81 | return self
|
81 |
|
||||||
82 |
|
82 | def method7[S](self: S, other: S) -> S:
|
||||||
83 | def method7[S](self: S, other: S) -> S:
|
83 | # error: [invalid-super-argument]
|
||||||
84 | # error: [invalid-super-argument]
|
84 | # revealed: Unknown
|
||||||
85 | # revealed: Unknown
|
85 | reveal_type(super())
|
||||||
86 | reveal_type(super())
|
86 | return self
|
||||||
87 | return self
|
87 |
|
||||||
88 |
|
88 | def method8[S: int](self: S, other: S) -> S:
|
||||||
89 | def method8[S: int](self: S, other: S) -> S:
|
89 | # error: [invalid-super-argument]
|
||||||
90 | # error: [invalid-super-argument]
|
90 | # revealed: Unknown
|
||||||
91 | # revealed: Unknown
|
91 | reveal_type(super())
|
||||||
92 | reveal_type(super())
|
92 | return self
|
||||||
93 | return self
|
93 |
|
||||||
94 |
|
94 | def method9[S: (int, str)](self: S, other: S) -> S:
|
||||||
95 | def method9[S: (int, str)](self: S, other: S) -> S:
|
95 | # error: [invalid-super-argument]
|
||||||
96 | # error: [invalid-super-argument]
|
96 | # revealed: Unknown
|
||||||
97 | # revealed: Unknown
|
97 | reveal_type(super())
|
||||||
98 | reveal_type(super())
|
98 | return self
|
||||||
99 | return self
|
99 |
|
||||||
100 |
|
100 | def method10[S: Callable[..., str]](self: S, other: S) -> S:
|
||||||
101 | def method10[S: Callable[..., str]](self: S, other: S) -> S:
|
101 | # error: [invalid-super-argument]
|
||||||
102 | # error: [invalid-super-argument]
|
102 | # revealed: Unknown
|
||||||
103 | # revealed: Unknown
|
103 | reveal_type(super())
|
||||||
104 | reveal_type(super())
|
104 | return self
|
||||||
105 | return self
|
105 |
|
||||||
106 |
|
106 | type Alias = Bar
|
||||||
107 | type Alias = Bar
|
107 |
|
||||||
108 |
|
108 | class Bar:
|
||||||
109 | class Bar:
|
109 | def method(self: Alias):
|
||||||
110 | def method(self: Alias):
|
110 | # revealed: <super: <class 'Bar'>, Bar>
|
||||||
111 | # revealed: <super: <class 'Bar'>, Bar>
|
111 | reveal_type(super())
|
||||||
112 | reveal_type(super())
|
112 |
|
||||||
113 |
|
113 | def pls_dont_call_me(self: Never):
|
||||||
114 | def pls_dont_call_me(self: Never):
|
114 | # revealed: <super: <class 'Bar'>, Unknown>
|
||||||
115 | # revealed: <super: <class 'Bar'>, Unknown>
|
115 | reveal_type(super())
|
||||||
116 | reveal_type(super())
|
116 |
|
||||||
117 |
|
117 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
|
||||||
118 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
|
118 | # revealed: <super: <class 'Bar'>, Bar>
|
||||||
119 | # revealed: <super: <class 'Bar'>, Bar>
|
119 | reveal_type(super())
|
||||||
120 | reveal_type(super())
|
120 |
|
||||||
121 |
|
121 | class P(Protocol):
|
||||||
122 | class P(Protocol):
|
122 | def method(self: P):
|
||||||
123 | def method(self: P):
|
123 | # revealed: <super: <class 'P'>, P>
|
||||||
124 | # revealed: <super: <class 'P'>, P>
|
124 | reveal_type(super())
|
||||||
125 | reveal_type(super())
|
125 |
|
||||||
126 |
|
126 | class E(enum.Enum):
|
||||||
127 | class E(enum.Enum):
|
127 | X = 1
|
||||||
128 | X = 1
|
128 |
|
||||||
129 |
|
129 | def method(self: E):
|
||||||
130 | def method(self: E):
|
130 | match self:
|
||||||
131 | match self:
|
131 | case E.X:
|
||||||
132 | case E.X:
|
132 | # revealed: <super: <class 'E'>, E>
|
||||||
133 | # revealed: <super: <class 'E'>, E>
|
133 | reveal_type(super())
|
||||||
134 | reveal_type(super())
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method7` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method7)` call
|
error[invalid-super-argument]: `S@method7` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method7)` call
|
||||||
--> src/mdtest_snippet.py:86:21
|
--> src/mdtest_snippet.py:85:21
|
||||||
|
|
|
|
||||||
84 | # error: [invalid-super-argument]
|
83 | # error: [invalid-super-argument]
|
||||||
85 | # revealed: Unknown
|
84 | # revealed: Unknown
|
||||||
86 | reveal_type(super())
|
85 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
87 | return self
|
86 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has `object` as its implicit upper bound
|
info: Type variable `S` has `object` as its implicit upper bound
|
||||||
info: `object` is not an instance or subclass of `<class 'Foo'>`
|
info: `object` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
|
@ -169,13 +168,13 @@ info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method8` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method8)` call
|
error[invalid-super-argument]: `S@method8` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method8)` call
|
||||||
--> src/mdtest_snippet.py:92:21
|
--> src/mdtest_snippet.py:91:21
|
||||||
|
|
|
|
||||||
90 | # error: [invalid-super-argument]
|
89 | # error: [invalid-super-argument]
|
||||||
91 | # revealed: Unknown
|
90 | # revealed: Unknown
|
||||||
92 | reveal_type(super())
|
91 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
93 | return self
|
92 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has upper bound `int`
|
info: Type variable `S` has upper bound `int`
|
||||||
info: `int` is not an instance or subclass of `<class 'Foo'>`
|
info: `int` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
|
@ -185,13 +184,13 @@ info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method9` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method9)` call
|
error[invalid-super-argument]: `S@method9` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method9)` call
|
||||||
--> src/mdtest_snippet.py:98:21
|
--> src/mdtest_snippet.py:97:21
|
||||||
|
|
|
|
||||||
96 | # error: [invalid-super-argument]
|
95 | # error: [invalid-super-argument]
|
||||||
97 | # revealed: Unknown
|
96 | # revealed: Unknown
|
||||||
98 | reveal_type(super())
|
97 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
99 | return self
|
98 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has constraints `int, str`
|
info: Type variable `S` has constraints `int, str`
|
||||||
info: `int | str` is not an instance or subclass of `<class 'Foo'>`
|
info: `int | str` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
|
@ -201,13 +200,13 @@ info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method10` is a type variable with an abstract/structural type as its bounds or constraints, in `super(<class 'Foo'>, S@method10)` call
|
error[invalid-super-argument]: `S@method10` is a type variable with an abstract/structural type as its bounds or constraints, in `super(<class 'Foo'>, S@method10)` call
|
||||||
--> src/mdtest_snippet.py:104:21
|
--> src/mdtest_snippet.py:103:21
|
||||||
|
|
|
|
||||||
102 | # error: [invalid-super-argument]
|
101 | # error: [invalid-super-argument]
|
||||||
103 | # revealed: Unknown
|
102 | # revealed: Unknown
|
||||||
104 | reveal_type(super())
|
103 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
105 | return self
|
104 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has upper bound `(...) -> str`
|
info: Type variable `S` has upper bound `(...) -> str`
|
||||||
info: rule `invalid-super-argument` is enabled by default
|
info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,8 @@ class C:
|
||||||
self.FINAL_C: Final[int] = 1
|
self.FINAL_C: Final[int] = 1
|
||||||
self.FINAL_D: Final = 1
|
self.FINAL_D: Final = 1
|
||||||
self.FINAL_E: Final
|
self.FINAL_E: Final
|
||||||
|
# TODO: Should not be an error
|
||||||
|
# error: [invalid-assignment] "Cannot assign to final attribute `FINAL_E` on type `Self@__init__`"
|
||||||
self.FINAL_E = 1
|
self.FINAL_E = 1
|
||||||
|
|
||||||
reveal_type(C.FINAL_A) # revealed: int
|
reveal_type(C.FINAL_A) # revealed: int
|
||||||
|
|
@ -184,6 +186,7 @@ class C(metaclass=Meta):
|
||||||
self.INSTANCE_FINAL_A: Final[int] = 1
|
self.INSTANCE_FINAL_A: Final[int] = 1
|
||||||
self.INSTANCE_FINAL_B: Final = 1
|
self.INSTANCE_FINAL_B: Final = 1
|
||||||
self.INSTANCE_FINAL_C: Final[int]
|
self.INSTANCE_FINAL_C: Final[int]
|
||||||
|
# error: [invalid-assignment] "Cannot assign to final attribute `INSTANCE_FINAL_C` on type `Self@__init__`"
|
||||||
self.INSTANCE_FINAL_C = 1
|
self.INSTANCE_FINAL_C = 1
|
||||||
|
|
||||||
# error: [invalid-assignment] "Cannot assign to final attribute `META_FINAL_A` on type `<class 'C'>`"
|
# error: [invalid-assignment] "Cannot assign to final attribute `META_FINAL_A` on type `<class 'C'>`"
|
||||||
|
|
@ -278,6 +281,8 @@ class C:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.LEGAL_H: Final[int] = 1
|
self.LEGAL_H: Final[int] = 1
|
||||||
self.LEGAL_I: Final[int]
|
self.LEGAL_I: Final[int]
|
||||||
|
# TODO: Should not be an error
|
||||||
|
# error: [invalid-assignment]
|
||||||
self.LEGAL_I = 1
|
self.LEGAL_I = 1
|
||||||
|
|
||||||
# error: [invalid-type-form] "`Final` is not allowed in function parameter annotations"
|
# error: [invalid-type-form] "`Final` is not allowed in function parameter annotations"
|
||||||
|
|
@ -390,6 +395,8 @@ class C:
|
||||||
DEFINED_IN_INIT: Final[int]
|
DEFINED_IN_INIT: Final[int]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# TODO: should not be an error
|
||||||
|
# error: [invalid-assignment]
|
||||||
self.DEFINED_IN_INIT = 1
|
self.DEFINED_IN_INIT = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ enum ReduceResult<'db> {
|
||||||
//
|
//
|
||||||
// For now (until we solve https://github.com/astral-sh/ty/issues/957), keep this number
|
// For now (until we solve https://github.com/astral-sh/ty/issues/957), keep this number
|
||||||
// below 200, which is the salsa fixpoint iteration limit.
|
// below 200, which is the salsa fixpoint iteration limit.
|
||||||
const MAX_UNION_LITERALS: usize = 199;
|
const MAX_UNION_LITERALS: usize = 190;
|
||||||
|
|
||||||
pub(crate) struct UnionBuilder<'db> {
|
pub(crate) struct UnionBuilder<'db> {
|
||||||
elements: Vec<UnionElement<'db>>,
|
elements: Vec<UnionElement<'db>>,
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,11 @@ pub(crate) fn bind_typevar<'db>(
|
||||||
/// Create a `typing.Self` type variable for a given class.
|
/// Create a `typing.Self` type variable for a given class.
|
||||||
pub(crate) fn typing_self<'db>(
|
pub(crate) fn typing_self<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
scope_id: ScopeId,
|
function_scope_id: ScopeId,
|
||||||
typevar_binding_context: Option<Definition<'db>>,
|
typevar_binding_context: Option<Definition<'db>>,
|
||||||
class: ClassLiteral<'db>,
|
class: ClassLiteral<'db>,
|
||||||
) -> Option<Type<'db>> {
|
) -> Option<Type<'db>> {
|
||||||
let index = semantic_index(db, scope_id.file(db));
|
let index = semantic_index(db, function_scope_id.file(db));
|
||||||
|
|
||||||
let identity = TypeVarIdentity::new(
|
let identity = TypeVarIdentity::new(
|
||||||
db,
|
db,
|
||||||
|
|
@ -110,7 +110,7 @@ pub(crate) fn typing_self<'db>(
|
||||||
bind_typevar(
|
bind_typevar(
|
||||||
db,
|
db,
|
||||||
index,
|
index,
|
||||||
scope_id.file_scope_id(db),
|
function_scope_id.file_scope_id(db),
|
||||||
typevar_binding_context,
|
typevar_binding_context,
|
||||||
typevar,
|
typevar,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ use crate::types::function::{
|
||||||
};
|
};
|
||||||
use crate::types::generics::{
|
use crate::types::generics::{
|
||||||
GenericContext, InferableTypeVars, LegacyGenericBase, SpecializationBuilder, bind_typevar,
|
GenericContext, InferableTypeVars, LegacyGenericBase, SpecializationBuilder, bind_typevar,
|
||||||
enclosing_generic_contexts,
|
enclosing_generic_contexts, typing_self,
|
||||||
};
|
};
|
||||||
use crate::types::infer::nearest_enclosing_function;
|
use crate::types::infer::nearest_enclosing_function;
|
||||||
use crate::types::instance::SliceLiteral;
|
use crate::types::instance::SliceLiteral;
|
||||||
|
|
@ -2495,6 +2495,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
} else {
|
} else {
|
||||||
let ty = if let Some(default_ty) = default_ty {
|
let ty = if let Some(default_ty) = default_ty {
|
||||||
UnionType::from_elements(self.db(), [Type::unknown(), default_ty])
|
UnionType::from_elements(self.db(), [Type::unknown(), default_ty])
|
||||||
|
} else if let Some(ty) = self.special_first_method_parameter_type(parameter) {
|
||||||
|
ty
|
||||||
} else {
|
} else {
|
||||||
Type::unknown()
|
Type::unknown()
|
||||||
};
|
};
|
||||||
|
|
@ -2535,6 +2537,65 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Special case for unannotated `cls` and `self` arguments to class methods and instance methods.
|
||||||
|
fn special_first_method_parameter_type(
|
||||||
|
&mut self,
|
||||||
|
parameter: &ast::Parameter,
|
||||||
|
) -> Option<Type<'db>> {
|
||||||
|
let db = self.db();
|
||||||
|
let file = self.file();
|
||||||
|
|
||||||
|
let function_scope_id = self.scope();
|
||||||
|
let function_scope = function_scope_id.scope(db);
|
||||||
|
let function = function_scope.node().as_function()?;
|
||||||
|
|
||||||
|
let parent_file_scope_id = function_scope.parent()?;
|
||||||
|
let mut parent_scope_id = parent_file_scope_id.to_scope_id(db, file);
|
||||||
|
|
||||||
|
// Skip type parameter scopes, if the method itself is generic.
|
||||||
|
if parent_scope_id.is_annotation(db) {
|
||||||
|
let parent_scope = parent_scope_id.scope(db);
|
||||||
|
parent_scope_id = parent_scope.parent()?.to_scope_id(db, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return early if this is not a method inside a class.
|
||||||
|
let class = parent_scope_id.scope(db).node().as_class()?;
|
||||||
|
|
||||||
|
let method_definition = self.index.expect_single_definition(function);
|
||||||
|
let DefinitionKind::Function(function_definition) = method_definition.kind(db) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if function_definition
|
||||||
|
.node(self.module())
|
||||||
|
.parameters
|
||||||
|
.index(parameter.name())
|
||||||
|
.is_none_or(|index| index != 0)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let method = infer_definition_types(db, method_definition)
|
||||||
|
.declaration_type(method_definition)
|
||||||
|
.inner_type()
|
||||||
|
.as_function_literal()?;
|
||||||
|
|
||||||
|
if method.is_classmethod(db) {
|
||||||
|
// TODO: set the type for `cls` argument
|
||||||
|
return None;
|
||||||
|
} else if method.is_staticmethod(db) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let class_definition = self.index.expect_single_definition(class);
|
||||||
|
let class_literal = infer_definition_types(db, class_definition)
|
||||||
|
.declaration_type(class_definition)
|
||||||
|
.inner_type()
|
||||||
|
.as_class_literal()?;
|
||||||
|
|
||||||
|
typing_self(db, self.scope(), Some(method_definition), class_literal)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set initial declared/inferred types for a `*args` variadic positional parameter.
|
/// Set initial declared/inferred types for a `*args` variadic positional parameter.
|
||||||
///
|
///
|
||||||
/// The annotated type is implicitly wrapped in a string-keyed dictionary.
|
/// The annotated type is implicitly wrapped in a string-keyed dictionary.
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ class FuzzResult:
|
||||||
case Executable.TY:
|
case Executable.TY:
|
||||||
panic_message = f"The following code triggers a {new}ty panic:"
|
panic_message = f"The following code triggers a {new}ty panic:"
|
||||||
case _ as unreachable:
|
case _ as unreachable:
|
||||||
assert_never(unreachable) # ty: ignore[type-assertion-failure]
|
assert_never(unreachable)
|
||||||
|
|
||||||
print(colored(panic_message, "red"))
|
print(colored(panic_message, "red"))
|
||||||
print()
|
print()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue