diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 4c72e62469..830ee2c2c0 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1247,7 +1247,7 @@ quux. __init_subclass__ :: bound method object.__init_subclass__() -> None __module__ :: str __ne__ :: bound method object.__ne__(value: object, /) -> bool - __new__ :: bound method object.__new__() -> Self + __new__ :: bound method object.__new__() -> Self@object __reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...] __reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __repr__ :: bound method object.__repr__() -> str @@ -1292,7 +1292,7 @@ quux.b __init_subclass__ :: bound method object.__init_subclass__() -> None __module__ :: str __ne__ :: bound method object.__ne__(value: object, /) -> bool - __new__ :: bound method object.__new__() -> Self + __new__ :: bound method object.__new__() -> Self@object __reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...] __reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __repr__ :: bound method object.__repr__() -> str @@ -1346,7 +1346,7 @@ C. __mro__ :: tuple[, ] __name__ :: str __ne__ :: def __ne__(self, value: object, /) -> bool - __new__ :: def __new__(cls) -> Self + __new__ :: def __new__(cls) -> Self@object __or__ :: bound method .__or__(value: Any, /) -> UnionType __prepare__ :: bound method .__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object] __qualname__ :: str @@ -1522,7 +1522,7 @@ Quux. __mro__ :: tuple[, ] __name__ :: str __ne__ :: def __ne__(self, value: object, /) -> bool - __new__ :: def __new__(cls) -> Self + __new__ :: def __new__(cls) -> Self@object __or__ :: bound method .__or__(value: Any, /) -> UnionType __prepare__ :: bound method .__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object] __qualname__ :: str @@ -1574,8 +1574,8 @@ Answer. __bool__ :: bound method .__bool__() -> Literal[True] __class__ :: __contains__ :: bound method .__contains__(value: object) -> bool - __copy__ :: def __copy__(self) -> Self - __deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self + __copy__ :: def __copy__(self) -> Self@Enum + __deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self@Enum __delattr__ :: def __delattr__(self, name: str, /) -> None __dict__ :: MappingProxyType[str, Any] __dictoffset__ :: int @@ -1585,28 +1585,28 @@ Answer. __flags__ :: int __format__ :: def __format__(self, format_spec: str) -> str __getattribute__ :: def __getattribute__(self, name: str, /) -> Any - __getitem__ :: bound method .__getitem__(name: str) -> _EnumMemberT + __getitem__ :: bound method .__getitem__(name: str) -> _EnumMemberT@__getitem__ __getstate__ :: def __getstate__(self) -> object __hash__ :: def __hash__(self) -> int __init__ :: def __init__(self) -> None __init_subclass__ :: def __init_subclass__(cls) -> None __instancecheck__ :: bound method .__instancecheck__(instance: Any, /) -> bool __itemsize__ :: int - __iter__ :: bound method .__iter__() -> Iterator[_EnumMemberT] + __iter__ :: bound method .__iter__() -> Iterator[_EnumMemberT@__iter__] __len__ :: bound method .__len__() -> int __members__ :: MappingProxyType[str, Unknown] __module__ :: str __mro__ :: tuple[, , ] __name__ :: str __ne__ :: def __ne__(self, value: object, /) -> bool - __new__ :: def __new__(cls, value: object) -> Self + __new__ :: def __new__(cls, value: object) -> Self@Enum __or__ :: bound method .__or__(value: Any, /) -> UnionType __order__ :: str __prepare__ :: bound method .__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict __qualname__ :: str __reduce__ :: def __reduce__(self) -> str | tuple[Any, ...] __repr__ :: def __repr__(self) -> str - __reversed__ :: bound method .__reversed__() -> Iterator[_EnumMemberT] + __reversed__ :: bound method .__reversed__() -> Iterator[_EnumMemberT@__reversed__] __ror__ :: bound method .__ror__(value: Any, /) -> UnionType __setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None __signature__ :: bound method .__signature__() -> str diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 024422af3b..29de21eb22 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -371,10 +371,10 @@ mod tests { ); assert_snapshot!(test.hover(), @r" - T + T@Alias --------------------------------------------- ```python - T + T@Alias ``` --------------------------------------------- info[hover]: Hovered content is diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 8a99fac64c..d74f67c430 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -16,7 +16,7 @@ from typing import Self class Shape: def set_scale(self: Self, scale: float) -> Self: - reveal_type(self) # revealed: Self + reveal_type(self) # revealed: Self@Shape return self def nested_type(self: Self) -> list[Self]: @@ -24,7 +24,7 @@ class Shape: def nested_func(self: Self) -> Self: def inner() -> Self: - reveal_type(self) # revealed: Self + reveal_type(self) # revealed: Self@Shape return self return inner() @@ -38,13 +38,13 @@ reveal_type(Shape().nested_func()) # revealed: Shape class Circle(Shape): def set_scale(self: Self, scale: float) -> Self: - reveal_type(self) # revealed: Self + reveal_type(self) # revealed: Self@Circle return self class Outer: class Inner: def foo(self: Self) -> Self: - reveal_type(self) # revealed: Self + reveal_type(self) # revealed: Self@Inner return self ``` @@ -151,7 +151,7 @@ from typing import Self class Shape: def union(self: Self, other: Self | None): - reveal_type(other) # revealed: Self | None + reveal_type(other) # revealed: Self@Shape | None return self ``` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index 254ed90eea..b4e5327d7a 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -26,7 +26,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P. class Foo: def method(self, x: Self): - reveal_type(x) # revealed: Self + reveal_type(x) # revealed: Self@Foo ``` ## Type expressions diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 46c1296508..36a2254a05 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -15,8 +15,10 @@ S = TypeVar("S") class SingleTypevar(Generic[T]): ... class MultipleTypevars(Generic[T, S]): ... -reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T] -reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S] +# revealed: tuple[T@SingleTypevar] +reveal_type(generic_context(SingleTypevar)) +# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] +reveal_type(generic_context(MultipleTypevars)) ``` Inheriting from `Generic` multiple times yields a `duplicate-base` diagnostic, just like any other @@ -49,9 +51,12 @@ class InheritedGeneric(MultipleTypevars[T, S]): ... class InheritedGenericPartiallySpecialized(MultipleTypevars[T, int]): ... class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ... -reveal_type(generic_context(InheritedGeneric)) # revealed: tuple[T, S] -reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: tuple[T] -reveal_type(generic_context(InheritedGenericFullySpecialized)) # revealed: None +# revealed: tuple[T@InheritedGeneric, S@InheritedGeneric] +reveal_type(generic_context(InheritedGeneric)) +# revealed: tuple[T@InheritedGenericPartiallySpecialized] +reveal_type(generic_context(InheritedGenericPartiallySpecialized)) +# revealed: None +reveal_type(generic_context(InheritedGenericFullySpecialized)) ``` If you don't specialize a generic base class, we use the default specialization, which maps each @@ -78,9 +83,12 @@ class ExplicitInheritedGenericPartiallySpecializedExtraTypevar(MultipleTypevars[ # error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes" class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ... -reveal_type(generic_context(ExplicitInheritedGeneric)) # revealed: tuple[T, S] -reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized)) # revealed: tuple[T] -reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar)) # revealed: tuple[T, S] +# revealed: tuple[T@ExplicitInheritedGeneric, S@ExplicitInheritedGeneric] +reveal_type(generic_context(ExplicitInheritedGeneric)) +# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecialized] +reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized)) +# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecializedExtraTypevar, S@ExplicitInheritedGenericPartiallySpecializedExtraTypevar] +reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar)) ``` ## Specializing generic classes explicitly @@ -446,18 +454,18 @@ class C(Generic[T]): def generic_method(self, t: T, u: U) -> U: return u -reveal_type(generic_context(C)) # revealed: tuple[T] +reveal_type(generic_context(C)) # revealed: tuple[T@C] reveal_type(generic_context(C.method)) # revealed: None -reveal_type(generic_context(C.generic_method)) # revealed: tuple[U] +reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(C[int])) # revealed: None reveal_type(generic_context(C[int].method)) # revealed: None -reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U] +reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method] c: C[int] = C[int]() reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] reveal_type(generic_context(c)) # revealed: None reveal_type(generic_context(c.method)) # revealed: None -reveal_type(generic_context(c.generic_method)) # revealed: tuple[U] +reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method] ``` ## Specializations propagate @@ -540,7 +548,8 @@ class WithOverloadedMethod(Generic[T]): def method(self, x: S | T) -> S | T: return x -reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int] +# revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int] +reveal_type(WithOverloadedMethod[int].method) ``` ## Cyclic class definitions diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index 65872213f2..5526e77782 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -226,7 +226,7 @@ from typing import TypeVar T = TypeVar("T", bound=int) def good_param(x: T) -> None: - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@good_param ``` If the function is annotated as returning the typevar, this means that the upper bound is _not_ @@ -239,7 +239,7 @@ def good_return(x: T) -> T: return x def bad_return(x: T) -> T: - # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T@bad_return`, found `int`" return x + 1 ``` @@ -257,7 +257,7 @@ def different_types(cond: bool, t: T, s: S) -> T: if cond: return t else: - # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T@different_types`, found `S@different_types`" return s def same_types(cond: bool, t1: T, t2: T) -> T: @@ -279,7 +279,7 @@ T = TypeVar("T", int, str) def same_constrained_types(t1: T, t2: T) -> T: # TODO: no error - # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T` and `T`" + # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`" return t1 + t2 ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index cf3cff8177..20cc708f3c 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -182,7 +182,7 @@ from typing import Callable, TypeVar T = TypeVar("T", bound=Callable[[], int]) def bound(f: T): - reveal_type(f) # revealed: T + reveal_type(f) # revealed: T@bound reveal_type(f()) # revealed: int ``` @@ -192,7 +192,7 @@ Same with a constrained typevar, as long as all constraints are callable: T = TypeVar("T", Callable[[], int], Callable[[], str]) def constrained(f: T): - reveal_type(f) # revealed: T + reveal_type(f) # revealed: T@constrained reveal_type(f()) # revealed: int | str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index a6a4071df1..5f2cf74585 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -16,8 +16,10 @@ from ty_extensions import generic_context class SingleTypevar[T]: ... class MultipleTypevars[T, S]: ... -reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T] -reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S] +# revealed: tuple[T@SingleTypevar] +reveal_type(generic_context(SingleTypevar)) +# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] +reveal_type(generic_context(MultipleTypevars)) ``` You cannot use the same typevar more than once. @@ -43,9 +45,12 @@ class InheritedGeneric[U, V](MultipleTypevars[U, V]): ... class InheritedGenericPartiallySpecialized[U](MultipleTypevars[U, int]): ... class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ... -reveal_type(generic_context(InheritedGeneric)) # revealed: tuple[U, V] -reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: tuple[U] -reveal_type(generic_context(InheritedGenericFullySpecialized)) # revealed: None +# revealed: tuple[U@InheritedGeneric, V@InheritedGeneric] +reveal_type(generic_context(InheritedGeneric)) +# revealed: tuple[U@InheritedGenericPartiallySpecialized] +reveal_type(generic_context(InheritedGenericPartiallySpecialized)) +# revealed: None +reveal_type(generic_context(InheritedGenericFullySpecialized)) ``` If you don't specialize a generic base class, we use the default specialization, which maps each @@ -406,18 +411,18 @@ class C[T]: # TODO: error def cannot_shadow_class_typevar[T](self, t: T): ... -reveal_type(generic_context(C)) # revealed: tuple[T] +reveal_type(generic_context(C)) # revealed: tuple[T@C] reveal_type(generic_context(C.method)) # revealed: None -reveal_type(generic_context(C.generic_method)) # revealed: tuple[U] +reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(C[int])) # revealed: None reveal_type(generic_context(C[int].method)) # revealed: None -reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U] +reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method] c: C[int] = C[int]() reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] reveal_type(generic_context(c)) # revealed: None reveal_type(generic_context(c.method)) # revealed: None -reveal_type(generic_context(c.generic_method)) # revealed: tuple[U] +reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method] ``` ## Specializations propagate @@ -466,7 +471,8 @@ class WithOverloadedMethod[T]: def method[S](self, x: S | T) -> S | T: return x -reveal_type(WithOverloadedMethod[int].method) # revealed: Overload[(self, x: int) -> int, (self, x: S) -> S | int] +# revealed: Overload[(self, x: int) -> int, (self, x: S@method) -> S@method | int] +reveal_type(WithOverloadedMethod[int].method) ``` ## Cyclic class definitions diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index aad1bb55b9..fa08ca1da5 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -202,7 +202,7 @@ in the function. ```py def good_param[T: int](x: T) -> None: - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@good_param ``` If the function is annotated as returning the typevar, this means that the upper bound is _not_ @@ -215,7 +215,7 @@ def good_return[T: int](x: T) -> T: return x def bad_return[T: int](x: T) -> T: - # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `int`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T@bad_return`, found `int`" return x + 1 ``` @@ -228,7 +228,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T: if cond: return t else: - # error: [invalid-return-type] "Return type does not match returned value: expected `T`, found `S`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T@different_types`, found `S@different_types`" return s def same_types[T](cond: bool, t1: T, t2: T) -> T: @@ -246,7 +246,7 @@ methods that are compatible with the return type, so the `return` expression is ```py def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T: # TODO: no error - # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T` and `T`" + # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`" return t1 + t2 ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md index 1ea17def0a..4542247215 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md @@ -104,13 +104,11 @@ different uses of the same typevar. ```py def f[T](x: T, y: T) -> None: - # TODO: revealed: T@f - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@f class C[T]: def m(self, x: T) -> None: - # TODO: revealed: T@c - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@C ``` ## Subtyping and assignability @@ -452,19 +450,19 @@ class Unrelated: ... def unbounded_unconstrained[T](t: T) -> None: def _(x: T | Super) -> None: - reveal_type(x) # revealed: T | Super + reveal_type(x) # revealed: T@unbounded_unconstrained | Super def _(x: T | Base) -> None: - reveal_type(x) # revealed: T | Base + reveal_type(x) # revealed: T@unbounded_unconstrained | Base def _(x: T | Sub) -> None: - reveal_type(x) # revealed: T | Sub + reveal_type(x) # revealed: T@unbounded_unconstrained | Sub def _(x: T | Unrelated) -> None: - reveal_type(x) # revealed: T | Unrelated + reveal_type(x) # revealed: T@unbounded_unconstrained | Unrelated def _(x: T | Any) -> None: - reveal_type(x) # revealed: T | Any + reveal_type(x) # revealed: T@unbounded_unconstrained | Any ``` The union of a bounded typevar with its bound is that bound. (The typevar is guaranteed to be @@ -480,13 +478,13 @@ def bounded[T: Base](t: T) -> None: reveal_type(x) # revealed: Base def _(x: T | Sub) -> None: - reveal_type(x) # revealed: T | Sub + reveal_type(x) # revealed: T@bounded | Sub def _(x: T | Unrelated) -> None: - reveal_type(x) # revealed: T | Unrelated + reveal_type(x) # revealed: T@bounded | Unrelated def _(x: T | Any) -> None: - reveal_type(x) # revealed: T | Any + reveal_type(x) # revealed: T@bounded | Any ``` The union of a constrained typevar with a type depends on how that type relates to the constraints. @@ -503,13 +501,13 @@ def constrained[T: (Base, Sub)](t: T) -> None: reveal_type(x) # revealed: Base def _(x: T | Sub) -> None: - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@constrained def _(x: T | Unrelated) -> None: - reveal_type(x) # revealed: T | Unrelated + reveal_type(x) # revealed: T@constrained | Unrelated def _(x: T | Any) -> None: - reveal_type(x) # revealed: T | Any + reveal_type(x) # revealed: T@constrained | Any ``` ## Intersections involving typevars @@ -528,19 +526,19 @@ class Unrelated: ... def unbounded_unconstrained[T](t: T) -> None: def _(x: Intersection[T, Super]) -> None: - reveal_type(x) # revealed: T & Super + reveal_type(x) # revealed: T@unbounded_unconstrained & Super def _(x: Intersection[T, Base]) -> None: - reveal_type(x) # revealed: T & Base + reveal_type(x) # revealed: T@unbounded_unconstrained & Base def _(x: Intersection[T, Sub]) -> None: - reveal_type(x) # revealed: T & Sub + reveal_type(x) # revealed: T@unbounded_unconstrained & Sub def _(x: Intersection[T, Unrelated]) -> None: - reveal_type(x) # revealed: T & Unrelated + reveal_type(x) # revealed: T@unbounded_unconstrained & Unrelated def _(x: Intersection[T, Any]) -> None: - reveal_type(x) # revealed: T & Any + reveal_type(x) # revealed: T@unbounded_unconstrained & Any ``` The intersection of a bounded typevar with its bound or a supertype of its bound is the typevar @@ -552,19 +550,19 @@ from its bound is `Never`. ```py def bounded[T: Base](t: T) -> None: def _(x: Intersection[T, Super]) -> None: - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@bounded def _(x: Intersection[T, Base]) -> None: - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@bounded def _(x: Intersection[T, Sub]) -> None: - reveal_type(x) # revealed: T & Sub + reveal_type(x) # revealed: T@bounded & Sub def _(x: Intersection[T, None]) -> None: reveal_type(x) # revealed: Never def _(x: Intersection[T, Any]) -> None: - reveal_type(x) # revealed: T & Any + reveal_type(x) # revealed: T@bounded & Any ``` Constrained typevars can be modeled using a hypothetical `OneOf` connector, where the typevar must @@ -586,7 +584,7 @@ can simplify the intersection as a whole to that constraint. def constrained[T: (Base, Sub, Unrelated)](t: T) -> None: def _(x: Intersection[T, Base]) -> None: # With OneOf this would be OneOf[Base, Sub] - reveal_type(x) # revealed: T & Base + reveal_type(x) # revealed: T@constrained & Base def _(x: Intersection[T, Unrelated]) -> None: reveal_type(x) # revealed: Unrelated @@ -598,7 +596,7 @@ def constrained[T: (Base, Sub, Unrelated)](t: T) -> None: reveal_type(x) # revealed: Never def _(x: Intersection[T, Any]) -> None: - reveal_type(x) # revealed: T & Any + reveal_type(x) # revealed: T@constrained & Any ``` We can simplify the intersection similarly when removing a type from a constrained typevar, since @@ -613,19 +611,19 @@ def remove_constraint[T: (int, str, bool)](t: T) -> None: def _(x: Intersection[T, Not[str]]) -> None: # With OneOf this would be OneOf[int, bool] - reveal_type(x) # revealed: T & ~str + reveal_type(x) # revealed: T@remove_constraint & ~str def _(x: Intersection[T, Not[bool]]) -> None: - reveal_type(x) # revealed: T & ~bool + reveal_type(x) # revealed: T@remove_constraint & ~bool def _(x: Intersection[T, Not[int], Not[str]]) -> None: reveal_type(x) # revealed: Never def _(x: Intersection[T, Not[None]]) -> None: - reveal_type(x) # revealed: T + reveal_type(x) # revealed: T@remove_constraint def _(x: Intersection[T, Not[Any]]) -> None: - reveal_type(x) # revealed: T & Any + reveal_type(x) # revealed: T@remove_constraint & Any ``` The intersection of a typevar with any other type is assignable to (and if fully static, a subtype @@ -710,7 +708,7 @@ A typevar bound to a Callable type is callable: from typing import Callable def bound[T: Callable[[], int]](f: T): - reveal_type(f) # revealed: T + reveal_type(f) # revealed: T@bound reveal_type(f()) # revealed: int ``` @@ -718,7 +716,7 @@ Same with a constrained typevar, as long as all constraints are callable: ```py def constrained[T: (Callable[[], int], Callable[[], str])](f: T): - reveal_type(f) # revealed: T + reveal_type(f) # revealed: T@constrained reveal_type(f()) # revealed: int | str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md index b3c8408b5c..c625db415b 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md @@ -152,9 +152,9 @@ already solved and specialized when the class was specialized: from ty_extensions import generic_context legacy.m("string", None) # error: [invalid-argument-type] -reveal_type(legacy.m) # revealed: bound method Legacy[int].m(x: int, y: S) -> S -reveal_type(generic_context(Legacy)) # revealed: tuple[T] -reveal_type(generic_context(legacy.m)) # revealed: tuple[S] +reveal_type(legacy.m) # revealed: bound method Legacy[int].m(x: int, y: S@m) -> S@m +reveal_type(generic_context(Legacy)) # revealed: tuple[T@Legacy] +reveal_type(generic_context(legacy.m)) # revealed: tuple[S@m] ``` With PEP 695 syntax, it is clearer that the method uses a separate typevar: diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 1a73deb594..7226e8ffa1 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -536,19 +536,19 @@ T = TypeVar("T") class peekable(Generic[T], Iterator[T]): ... -# revealed: tuple[, , , typing.Protocol, typing.Generic, ] +# revealed: tuple[, , , typing.Protocol, typing.Generic, ] reveal_type(peekable.__mro__) class peekable2(Iterator[T], Generic[T]): ... -# revealed: tuple[, , , typing.Protocol, typing.Generic, ] +# revealed: tuple[, , , typing.Protocol, typing.Generic, ] reveal_type(peekable2.__mro__) class Base: ... class Intermediate(Base, Generic[T]): ... class Sub(Intermediate[T], Base): ... -# revealed: tuple[, , , typing.Generic, ] +# revealed: tuple[, , , typing.Generic, ] reveal_type(Sub.__mro__) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index dfc81a73f0..175e42fc51 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -150,9 +150,9 @@ class Person(NamedTuple): reveal_type(Person._field_defaults) # revealed: dict[str, Any] reveal_type(Person._fields) # revealed: tuple[str, ...] -reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self +reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self@NamedTupleFallback reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] -reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self +reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@NamedTupleFallback # TODO: should be `Person` once we support `Self` reveal_type(Person._make(("Alice", 42))) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md b/crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md index 934fd3f605..5a61c9e8d2 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/complex_target.md @@ -136,19 +136,19 @@ def f(a: int | None): reveal_type(c.y) # revealed: int | None def g[T](c: C[T]): - reveal_type(c.x) # revealed: T - reveal_type(c.y) # revealed: T - reveal_type(c) # revealed: C[T] + reveal_type(c.x) # revealed: T@g + reveal_type(c.y) # revealed: T@g + reveal_type(c) # revealed: C[T@g] if isinstance(c.x, int): - reveal_type(c.x) # revealed: T & int - reveal_type(c.y) # revealed: T - reveal_type(c) # revealed: C[T] + reveal_type(c.x) # revealed: T@g & int + reveal_type(c.y) # revealed: T@g + reveal_type(c) # revealed: C[T@g] if isinstance(c.x, int) and isinstance(c.y, int): - reveal_type(c.x) # revealed: T & int - reveal_type(c.y) # revealed: T & int + reveal_type(c.x) # revealed: T@g & int + reveal_type(c.y) # revealed: T@g & int # TODO: Probably better if inferred as `C[T & int]` (mypy and pyright don't support this) - reveal_type(c) # revealed: C[T] + reveal_type(c) # revealed: C[T@g] ``` ### With intermediate scopes diff --git a/crates/ty_python_semantic/resources/mdtest/overloads.md b/crates/ty_python_semantic/resources/mdtest/overloads.md index 650dd9da44..08bd0c3021 100644 --- a/crates/ty_python_semantic/resources/mdtest/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/overloads.md @@ -299,7 +299,7 @@ def func[T](x: T) -> T: ... def func[T](x: T | None = None) -> T | None: return x -reveal_type(func) # revealed: Overload[() -> None, (x: T) -> T] +reveal_type(func) # revealed: Overload[() -> None, (x: T@func) -> T@func] reveal_type(func()) # revealed: None reveal_type(func(1)) # revealed: Literal[1] reveal_type(func("")) # revealed: Literal[""] diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 6e4f4ed0e1..3b4b18c2aa 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -393,7 +393,7 @@ from typing import SupportsIndex, SupportsAbs reveal_protocol_interface(Foo) # error: [revealed-type] "Revealed protocol interface: `{"__index__": MethodMember(`(self) -> int`)}`" reveal_protocol_interface(SupportsIndex) -# error: [revealed-type] "Revealed protocol interface: `{"__abs__": MethodMember(`(self) -> _T_co`)}`" +# error: [revealed-type] "Revealed protocol interface: `{"__abs__": MethodMember(`(self) -> _T_co@SupportsAbs`)}`" reveal_protocol_interface(SupportsAbs) # error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`" diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/builtin.md b/crates/ty_python_semantic/resources/mdtest/scopes/builtin.md index 0df01f7883..5da5d9f357 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/builtin.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/builtin.md @@ -11,7 +11,7 @@ def _(flag: bool) -> None: abs = 1 chr: int = 1 - reveal_type(abs) # revealed: Literal[1] | (def abs(x: SupportsAbs[_T], /) -> _T) + reveal_type(abs) # revealed: Literal[1] | (def abs(x: SupportsAbs[_T@abs], /) -> _T@abs) reveal_type(chr) # revealed: Literal[1] | (def chr(i: SupportsIndex, /) -> str) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_Unresolvable_MROs_in…_(e2b355c09a967862).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_Unresolvable_MROs_in…_(e2b355c09a967862).snap index 81c10d0361..c7590f4255 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_Unresolvable_MROs_in…_(e2b355c09a967862).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_Unresolvable_MROs_in…_(e2b355c09a967862).snap @@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md # Diagnostics ``` -error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], , ]` +error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], , ]` --> src/mdtest_snippet.py:7:1 | 5 | class Foo(Protocol): ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap index a5913a7d5f..9c3379586a 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap @@ -85,7 +85,7 @@ info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `T` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `T@m` --> src/mdtest_snippet.py:18:16 | 17 | # error: [invalid-return-type] diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap index b3244bc8cb..05b560e021 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap @@ -86,7 +86,7 @@ error[call-non-callable]: Object of type `Literal[5]` is not callable | ^^^^ | info: Union variant `Literal[5]` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `call-non-callable` is enabled by default ``` @@ -101,7 +101,7 @@ error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable ( | ^^^^ | info: Union variant `PossiblyNotCallable` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `call-non-callable` is enabled by default ``` @@ -116,7 +116,7 @@ error[missing-argument]: No argument provided for required parameter `b` of func | ^^^^ | info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `missing-argument` is enabled by default ``` @@ -152,7 +152,7 @@ info: Overload implementation defined here 28 | return x + y if x and y else None | info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `no-matching-overload` is enabled by default ``` @@ -176,7 +176,7 @@ info: Function defined here 8 | return 0 | info: Union variant `def f2(name: str) -> int` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `invalid-argument-type` is enabled by default ``` @@ -199,8 +199,8 @@ info: Type variable defined here | ^^^^^^ 14 | return 0 | -info: Union variant `def f4(x: T) -> int` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Union variant `def f4(x: T@f4) -> int` is incompatible with this call site +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `invalid-argument-type` is enabled by default ``` @@ -227,7 +227,7 @@ info: Matching overload defined here info: Non-matching overloads for function `f5`: info: () -> None info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `invalid-argument-type` is enabled by default ``` @@ -242,7 +242,7 @@ error[too-many-positional-arguments]: Too many positional arguments to function | ^ | info: Union variant `def f1() -> int` is incompatible with this call site -info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` +info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` info: rule `too-many-positional-arguments` is enabled by default ``` diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index 6d1ce38299..f10630b1d7 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -60,6 +60,35 @@ impl<'db> Definition<'db> { FileRange::new(self.file(db), self.kind(db).target_range(module)) } + /// Returns the name of the item being defined, if applicable. + pub fn name(self, db: &'db dyn Db) -> Option { + let file = self.file(db); + let module = parsed_module(db, file).load(db); + let kind = self.kind(db); + match kind { + DefinitionKind::Function(def) => { + let node = def.node(&module); + Some(node.name.as_str().to_string()) + } + DefinitionKind::Class(def) => { + let node = def.node(&module); + Some(node.name.as_str().to_string()) + } + DefinitionKind::TypeAlias(def) => { + let node = def.node(&module); + Some( + node.name + .as_name_expr() + .expect("type alias name should be a NameExpr") + .id + .as_str() + .to_string(), + ) + } + _ => None, + } + } + /// Extract a docstring from this definition, if applicable. /// This method returns a docstring for function and class definitions. /// The docstring is extracted from the first statement in the body if it's a string literal. diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 76cb7f52c7..c3f747bcee 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -701,6 +701,7 @@ impl<'db> Type<'db> { Name::new_static("T_all"), None, None, + None, variance, None, TypeVarKind::Pep695, @@ -5128,6 +5129,7 @@ impl<'db> Type<'db> { db, Name::new(format!("{}'instance", typevar.name(db))), None, + None, Some(bound_or_constraints), typevar.variance(db), None, @@ -5295,10 +5297,12 @@ impl<'db> Type<'db> { let instance = Type::ClassLiteral(class).to_instance(db).expect( "nearest_enclosing_class must return type that can be instantiated", ); + let class_definition = class.definition(db); Ok(Type::TypeVar(TypeVarInstance::new( db, ast::name::Name::new_static("Self"), - Some(class.definition(db)), + Some(class_definition), + Some(class_definition), Some(TypeVarBoundOrConstraints::UpperBound(instance)), TypeVarVariance::Invariant, None, @@ -5573,6 +5577,9 @@ impl<'db> Type<'db> { partial.get(db, typevar).unwrap_or(self) } TypeMapping::PromoteLiterals => self, + TypeMapping::BindLegacyTypevars(binding_context) => { + Type::TypeVar(typevar.with_binding_context(db, *binding_context)) + } } Type::FunctionLiteral(function) => { @@ -5660,7 +5667,8 @@ impl<'db> Type<'db> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) => match type_mapping { TypeMapping::Specialization(_) | - TypeMapping::PartialSpecialization(_) => self, + TypeMapping::PartialSpecialization(_) | + TypeMapping::BindLegacyTypevars(_) => self, TypeMapping::PromoteLiterals => self.literal_fallback_instance(db) .expect("literal type should have fallback instance type"), } @@ -6009,6 +6017,9 @@ pub enum TypeMapping<'a, 'db> { /// Promotes any literal types to their corresponding instance types (e.g. `Literal["string"]` /// to `str`) PromoteLiterals, + /// Binds a legacy typevar with the generic context (class, function, type alias) that it is + /// being used in. + BindLegacyTypevars(Definition<'db>), } fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( @@ -6023,7 +6034,7 @@ fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( TypeMapping::PartialSpecialization(specialization) => { walk_partial_specialization(db, specialization, visitor); } - TypeMapping::PromoteLiterals => {} + TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) => {} } } @@ -6037,6 +6048,9 @@ impl<'db> TypeMapping<'_, 'db> { TypeMapping::PartialSpecialization(partial.to_owned()) } TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals, + TypeMapping::BindLegacyTypevars(binding_context) => { + TypeMapping::BindLegacyTypevars(*binding_context) + } } } @@ -6049,6 +6063,9 @@ impl<'db> TypeMapping<'_, 'db> { TypeMapping::PartialSpecialization(partial.normalized_impl(db, visitor)) } TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals, + TypeMapping::BindLegacyTypevars(binding_context) => { + TypeMapping::BindLegacyTypevars(*binding_context) + } } } } @@ -6582,6 +6599,35 @@ pub struct TypeVarInstance<'db> { /// The type var's definition (None if synthesized) pub definition: Option>, + /// The definition of the generic class, function, or type alias that binds this typevar. This + /// is `None` for a legacy typevar outside of a context that can bind it. + /// + /// For a legacy typevar, the binding context might be missing: + /// + /// ```py + /// T = TypeVar("T") # [1] + /// def generic_function(t: T) -> T: ... # [2] + /// ``` + /// + /// Here, we will create two `TypeVarInstance`s for the typevar `T`. Both will have `[1]` as + /// their [`definition`][Self::definition]. The first represents the variable when it is first + /// created, and not yet used, so it's `binding_context` will be `None`. The second represents + /// when the typevar is used in `generic_function`, and its `binding_context` will be `[2]` + /// (that is, the definition of `generic_function`). + /// + /// For a PEP 695 typevar, there will always be a binding context, since you can only define + /// one as part of creating the generic context that uses it: + /// + /// ```py + /// def generic_function[T](t: T) -> T: ... + /// ``` + /// + /// Here, we will create a single `TypeVarInstance`. Its [`definition`][Self::definition] will + /// be the `T` in `[T]` (i.e., the definition of the typevar in the syntactic construct that + /// creates the generic context that uses it). Its `binding_context` will be the definition of + /// `generic_function`. + binding_context: Option>, + /// The upper bound or constraint on the type of this TypeVar bound_or_constraints: Option>, @@ -6611,6 +6657,25 @@ fn walk_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( } impl<'db> TypeVarInstance<'db> { + pub(crate) fn with_binding_context( + self, + db: &'db dyn Db, + binding_context: Definition<'db>, + ) -> Self { + Self::new( + db, + self.name(db), + self.definition(db), + Some(binding_context), + self.bound_or_constraints(db), + self.variance(db), + self.default_ty(db).map(|ty| { + ty.apply_type_mapping(db, &TypeMapping::BindLegacyTypevars(binding_context)) + }), + self.kind(db), + ) + } + pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool { matches!(self.kind(db), TypeVarKind::Legacy) } @@ -6640,6 +6705,7 @@ impl<'db> TypeVarInstance<'db> { db, self.name(db), self.definition(db), + self.binding_context(db), self.bound_or_constraints(db) .map(|b| b.normalized_impl(db, visitor)), self.variance(db), @@ -6653,6 +6719,7 @@ impl<'db> TypeVarInstance<'db> { db, self.name(db), self.definition(db), + self.binding_context(db), self.bound_or_constraints(db) .map(|b| b.materialize(db, variance)), self.variance(db), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index a676439cd3..f3b9639434 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1273,7 +1273,21 @@ impl<'db> ClassLiteral<'db> { class_stmt .bases() .iter() - .map(|base_node| definition_expression_type(db, class_definition, base_node)) + .map( + |base_node| match definition_expression_type(db, class_definition, base_node) { + Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(generic_context)) => { + Type::KnownInstance(KnownInstanceType::SubscriptedGeneric( + generic_context.with_binding_context(db, class_definition), + )) + } + Type::KnownInstance(KnownInstanceType::SubscriptedProtocol( + generic_context, + )) => Type::KnownInstance(KnownInstanceType::SubscriptedProtocol( + generic_context.with_binding_context(db, class_definition), + )), + ty => ty, + }, + ) .collect() } @@ -4098,11 +4112,15 @@ impl KnownClass { }; let containing_assignment = index.expect_single_definition(target); + // A freshly created legacy TypeVar does not have a binding context until it is + // used in a base class list, function parameter list, or type alias. + let binding_context = None; overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar( TypeVarInstance::new( db, &target.id, Some(containing_assignment), + binding_context, bound_or_constraint, variance, *default, diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 4f78e70994..57cd9f139b 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -205,7 +205,16 @@ impl Display for DisplayRepresentation<'_> { ) } Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f), - Type::TypeVar(typevar) => f.write_str(typevar.name(self.db)), + Type::TypeVar(typevar) => { + f.write_str(typevar.name(self.db))?; + if let Some(binding_context) = typevar + .binding_context(self.db) + .and_then(|def| def.name(self.db)) + { + write!(f, "@{binding_context}")?; + } + Ok(()) + } Type::AlwaysTruthy => f.write_str("AlwaysTruthy"), Type::AlwaysFalsy => f.write_str("AlwaysFalsy"), Type::BoundSuper(bound_super) => { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index f765bfdc66..56f5b2e99b 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -14,13 +14,13 @@ use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::{ KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints, - TypeVarInstance, TypeVarVariance, UnionType, binding_type, declaration_type, + TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type, }; use crate::{Db, FxOrderSet}; /// Returns an iterator of any generic context introduced by the given scope or any enclosing /// scope. -fn enclosing_generic_contexts<'db>( +pub(crate) fn enclosing_generic_contexts<'db>( db: &'db dyn Db, module: &ParsedModuleRef, index: &SemanticIndex<'db>, @@ -175,6 +175,19 @@ impl<'db> GenericContext<'db> { Some(Self::new(db, variables)) } + pub(crate) fn with_binding_context( + self, + db: &'db dyn Db, + binding_context: Definition<'db>, + ) -> Self { + let variables: FxOrderSet<_> = self + .variables(db) + .iter() + .map(|typevar| typevar.with_binding_context(db, binding_context)) + .collect(); + Self::new(db, variables) + } + pub(crate) fn len(self, db: &'db dyn Db) -> usize { self.variables(db).len() } @@ -241,6 +254,22 @@ impl<'db> GenericContext<'db> { self.variables(db).is_subset(other.variables(db)) } + pub(crate) fn binds_legacy_typevar( + self, + db: &'db dyn Db, + typevar: TypeVarInstance<'db>, + ) -> Option> { + assert!(typevar.kind(db) == TypeVarKind::Legacy); + let typevar_def = typevar.definition(db); + self.variables(db) + .iter() + .find(|self_typevar| { + self_typevar.kind(db) == TypeVarKind::Legacy + && self_typevar.definition(db) == typevar_def + }) + .copied() + } + /// Creates a specialization of this generic context. Panics if the length of `types` does not /// match the number of typevars in the generic context. You must provide a specific type for /// each typevar; no defaults are used. (Use [`specialize_partial`](Self::specialize_partial) diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index e4e75b6966..2db0ed7d32 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -110,7 +110,7 @@ use crate::types::enums::is_enum_class; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, }; -use crate::types::generics::GenericContext; +use crate::types::generics::{GenericContext, enclosing_generic_contexts}; use crate::types::mro::MroErrorKind; use crate::types::signatures::{CallableSignature, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; @@ -803,6 +803,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> { /// [`check_overloaded_functions`]: TypeInferenceBuilder::check_overloaded_functions called_functions: FxHashSet>, + /// Whether we are in a context that binds unbound legacy typevars. + legacy_typevar_binding_context: Option>, + /// The deferred state of inferring types of certain expressions within the region. /// /// This is different from [`InferenceRegion::Deferred`] which works on the entire definition @@ -847,6 +850,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { expressions: FxHashMap::default(), bindings: VecMap::default(), declarations: VecMap::default(), + legacy_typevar_binding_context: None, deferred: VecSet::default(), cycle_fallback: false, } @@ -1713,9 +1717,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { match definition.kind(self.db()) { DefinitionKind::Function(function) => { - self.infer_function_deferred(function.node(self.module())); + self.infer_function_deferred(definition, function.node(self.module())); + } + DefinitionKind::Class(class) => { + self.infer_class_deferred(definition, class.node(self.module())); } - DefinitionKind::Class(class) => self.infer_class_deferred(class.node(self.module())), _ => {} } } @@ -2207,6 +2213,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_type_parameters(type_params); if let Some(arguments) = class.arguments.as_deref() { + // Note: We do not install a new `legacy_typevar_binding_context`; since this class has + // PEP 695 typevars, it should not also bind any legacy typevars via inheriting from + // `typing.Generic` or `typing.Protocol`. let mut call_arguments = CallArguments::from_arguments(self.db(), arguments, |argument, splatted_value| { let ty = self.infer_expression(splatted_value); @@ -2228,6 +2237,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .as_deref() .expect("function type params scope without type params"); + // Note: We do not install a new `legacy_typevar_binding_context`; since this function has + // PEP 695 typevars, it should not also bind any legacy typevars by referencing them in its + // parameter or return type annotations. self.infer_return_type_annotation( function.returns.as_deref(), DeferredExpressionState::None, @@ -2590,11 +2602,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if self.defer_annotations() { self.deferred.insert(definition); } else { + let previous_legacy_typevar_binding_context = + self.legacy_typevar_binding_context.replace(definition); self.infer_return_type_annotation( returns.as_deref(), DeferredExpressionState::None, ); self.infer_parameters(parameters); + self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context; } } @@ -3006,25 +3021,38 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if self.in_stub() || class_node.bases().iter().any(contains_string_literal) { self.deferred.insert(definition); } else { + let previous_legacy_typevar_binding_context = + self.legacy_typevar_binding_context.replace(definition); for base in class_node.bases() { self.infer_expression(base); } + self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context; } } } - fn infer_function_deferred(&mut self, function: &ast::StmtFunctionDef) { + fn infer_function_deferred( + &mut self, + definition: Definition<'db>, + function: &ast::StmtFunctionDef, + ) { + let previous_legacy_typevar_binding_context = + self.legacy_typevar_binding_context.replace(definition); self.infer_return_type_annotation( function.returns.as_deref(), DeferredExpressionState::Deferred, ); self.infer_parameters(function.parameters.as_ref()); + self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context; } - fn infer_class_deferred(&mut self, class: &ast::StmtClassDef) { + fn infer_class_deferred(&mut self, definition: Definition<'db>, class: &ast::StmtClassDef) { + let previous_legacy_typevar_binding_context = + self.legacy_typevar_binding_context.replace(definition); for base in class.bases() { self.infer_expression(base); } + self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context; } fn infer_type_alias_definition( @@ -3330,6 +3358,28 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { bound, default, } = node; + + // Find the binding context for the PEP 695 typevars defined in this scope. The typevar + // scope should have a child containing the class, function, or type alias definition. Find + // that scope and use its definition as the binding context. + let typevar_scope = definition.file_scope(self.db()); + let child_scopes = self.index.child_scopes(typevar_scope); + let binding_context = child_scopes + .filter_map(|(_, binding_scope)| match binding_scope.node() { + NodeWithScopeKind::Class(class) => { + Some(DefinitionNodeKey::from(class.node(self.context.module()))) + } + NodeWithScopeKind::Function(function) => Some(DefinitionNodeKey::from( + function.node(self.context.module()), + )), + NodeWithScopeKind::TypeAlias(alias) => { + Some(DefinitionNodeKey::from(alias.node(self.context.module()))) + } + _ => None, + }) + .map(|key| self.index.expect_single_definition(key)) + .next(); + let bound_or_constraint = match bound.as_deref() { Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => { if elts.len() < 2 { @@ -3374,6 +3424,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.db(), &name.id, Some(definition), + binding_context, bound_or_constraint, TypeVarVariance::Invariant, // TODO: infer this default_ty, @@ -6351,6 +6402,52 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node)); resolved + .map_type(|ty| { + // If the expression resolves to a legacy typevar, we will have the TypeVarInstance + // that was created when the typevar was created, which will not have an associated + // binding context. If this expression appears inside of a generic context that + // binds that typevar, we need to update the TypeVarInstance to include that + // binding context. To do that, we walk the enclosing scopes, looking for the + // nearest generic context that binds the typevar. + // + // If the legacy typevar is still unbound after that search, and we are in a + // context that binds unbound legacy typevars (i.e., the signature of a generic + // function), bind it with that context. + let find_legacy_typevar_binding = |typevar: TypeVarInstance<'db>| { + enclosing_generic_contexts( + self.db(), + self.context.module(), + self.index, + self.scope().file_scope_id(self.db()), + ) + .find_map(|enclosing_context| { + enclosing_context.binds_legacy_typevar(self.db(), typevar) + }) + .or_else(|| { + self.legacy_typevar_binding_context + .map(|legacy_typevar_binding_context| { + typevar + .with_binding_context(self.db(), legacy_typevar_binding_context) + }) + }) + }; + + match ty { + Type::TypeVar(typevar) if typevar.is_legacy(self.db()) => { + find_legacy_typevar_binding(typevar) + .map(Type::TypeVar) + .unwrap_or(ty) + } + Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) + if typevar.is_legacy(self.db()) => + { + find_legacy_typevar_binding(typevar) + .map(|typevar| Type::KnownInstance(KnownInstanceType::TypeVar(typevar))) + .unwrap_or(ty) + } + _ => ty, + } + }) // Not found in the module's explicitly declared global symbols? // Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc. // These are looked up as attributes on `types.ModuleType`. @@ -8933,6 +9030,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { cycle_fallback, // builder only state + legacy_typevar_binding_context: _, deferred_state: _, called_functions: _, index: _, @@ -8992,6 +9090,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { cycle_fallback, // builder only state + legacy_typevar_binding_context: _, deferred_state: _, called_functions: _, index: _, @@ -9054,6 +9153,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { declarations: _, // Builder only state + legacy_typevar_binding_context: _, deferred_state: _, called_functions: _, index: _, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 856cc15fff..eef3ac6781 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1790,7 +1790,7 @@ mod tests { a_annotated_ty.unwrap().display(&db).to_string(), "Unknown | A | B" ); - assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T"); + assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f"); } #[test] @@ -1835,7 +1835,7 @@ mod tests { assert_eq!(b_name, "b"); // Parameter resolution deferred: assert_eq!(a_annotated_ty.unwrap().display(&db).to_string(), "A | B"); - assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T"); + assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f"); } #[test] diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 5b12ae252a..88eedf2139 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -93,6 +93,7 @@ impl<'db> SubclassOfType<'db> { db, Name::new_static("T_all"), None, + None, Some(TypeVarBoundOrConstraints::UpperBound( KnownClass::Type.to_instance(db), )),