mirror of https://github.com/astral-sh/ruff
[ty] Generic types aliases (implicit and PEP 613) (#21553)
## Summary
Add support for generic PEP 613 type aliases and generic implicit type
aliases:
```py
from typing import TypeVar
T = TypeVar("T")
ListOrSet = list[T] | set[T]
def _(xs: ListOrSet[int]):
reveal_type(xs) # list[int] | set[int]
```
closes https://github.com/astral-sh/ty/issues/1643
closes https://github.com/astral-sh/ty/issues/1629
closes https://github.com/astral-sh/ty/issues/1596
closes https://github.com/astral-sh/ty/issues/573
closes https://github.com/astral-sh/ty/issues/221
## Typing conformance
```diff
-aliases_explicit.py:52:5: error[type-assertion-failure] Type `list[int]` does not match asserted type `@Todo(specialized generic alias in type expression)`
-aliases_explicit.py:53:5: error[type-assertion-failure] Type `tuple[str, ...] | list[str]` does not match asserted type `@Todo(Generic specialization of types.UnionType)`
-aliases_explicit.py:54:5: error[type-assertion-failure] Type `tuple[int, int, int, str]` does not match asserted type `@Todo(specialized generic alias in type expression)`
-aliases_explicit.py:56:5: error[type-assertion-failure] Type `(int, str, /) -> str` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
-aliases_explicit.py:59:5: error[type-assertion-failure] Type `int | str | None | list[list[int]]` does not match asserted type `int | str | None | list[@Todo(specialized generic alias in type expression)]`
```
New true negatives ✔️
```diff
+aliases_explicit.py:41:36: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
-aliases_explicit.py:57:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
+aliases_explicit.py:57:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `(...) -> Unknown`
```
These require `ParamSpec`
```diff
+aliases_explicit.py:67:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_explicit.py:68:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_explicit.py:69:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_explicit.py:70:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_explicit.py:71:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_explicit.py:102:20: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
```
New true positives ✔️
```diff
-aliases_implicit.py:63:5: error[type-assertion-failure] Type `list[int]` does not match asserted type `@Todo(specialized generic alias in type expression)`
-aliases_implicit.py:64:5: error[type-assertion-failure] Type `tuple[str, ...] | list[str]` does not match asserted type `@Todo(Generic specialization of types.UnionType)`
-aliases_implicit.py:65:5: error[type-assertion-failure] Type `tuple[int, int, int, str]` does not match asserted type `@Todo(specialized generic alias in type expression)`
-aliases_implicit.py:67:5: error[type-assertion-failure] Type `(int, str, /) -> str` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
-aliases_implicit.py:70:5: error[type-assertion-failure] Type `int | str | None | list[list[int]]` does not match asserted type `int | str | None | list[@Todo(specialized generic alias in type expression)]`
-aliases_implicit.py:71:5: error[type-assertion-failure] Type `list[bool]` does not match asserted type `@Todo(specialized generic alias in type expression)`
```
New true negatives ✔️
```diff
+aliases_implicit.py:54:36: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
-aliases_implicit.py:68:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
+aliases_implicit.py:68:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `(...) -> Unknown`
```
These require `ParamSpec`
```diff
+aliases_implicit.py:76:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_implicit.py:77:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_implicit.py:78:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_implicit.py:79:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_implicit.py:80:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
+aliases_implicit.py:81:25: error[invalid-type-arguments] Type `str` is not assignable to upper bound `int | float` of type variable `TFloat@GoodTypeAlias12`
+aliases_implicit.py:135:20: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
```
New true positives ✔️
```diff
+callables_annotation.py:172:19: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+callables_annotation.py:175:19: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+callables_annotation.py:188:25: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+callables_annotation.py:189:25: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
```
These require `ParamSpec` and `Concatenate`.
```diff
-generics_defaults_specialization.py:26:5: error[type-assertion-failure] Type `SomethingWithNoDefaults[int, str]` does not match asserted type `SomethingWithNoDefaults[int, typing.TypeVar]`
+generics_defaults_specialization.py:26:5: error[type-assertion-failure] Type `SomethingWithNoDefaults[int, str]` does not match asserted type `SomethingWithNoDefaults[int, DefaultStrT]`
```
Favorable diagnostic change ✔️
```diff
-generics_defaults_specialization.py:27:5: error[type-assertion-failure] Type `SomethingWithNoDefaults[int, bool]` does not match asserted type `@Todo(specialized generic alias in type expression)`
```
New true negative ✔️
```diff
-generics_defaults_specialization.py:30:1: error[non-subscriptable] Cannot subscript object of type `<class 'SomethingWithNoDefaults[int, typing.TypeVar]'>` with no `__class_getitem__` method
+generics_defaults_specialization.py:30:15: error[invalid-type-arguments] Too many type arguments: expected between 0 and 1, got 2
```
Correct new diagnostic ✔️
```diff
-generics_variance.py:175:25: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:175:35: error[non-subscriptable] Cannot subscript object of type `<class 'Co[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:179:29: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:179:39: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:183:21: error[non-subscriptable] Cannot subscript object of type `<class 'Co[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:183:27: error[non-subscriptable] Cannot subscript object of type `<class 'Co[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:187:25: error[non-subscriptable] Cannot subscript object of type `<class 'Co[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:187:31: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:191:33: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:191:43: error[non-subscriptable] Cannot subscript object of type `<class 'Co[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:191:49: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:196:5: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:196:15: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
-generics_variance.py:196:25: error[non-subscriptable] Cannot subscript object of type `<class 'Contra[typing.TypeVar]'>` with no `__class_getitem__` method
```
One of these should apparently be an error, but not of this kind, so
this is good ✔️
```diff
-specialtypes_type.py:152:16: error[invalid-type-form] `typing.TypeVar` is not a generic class
-specialtypes_type.py:156:16: error[invalid-type-form] `typing.TypeVar` is not a generic class
```
Good, those were false positives. ✔️
I skipped the analysis for everything involving `TypeVarTuple`.
## Ecosystem impact
**[Full report with detailed
diff](https://david-generic-implicit-alias.ecosystem-663.pages.dev/diff)**
Previous iterations of this PR showed all kinds of problems. In it's
current state, I do not see any large systematic problems, but it is
hard to tell with 5k diagnostic changes.
## Performance
* There is a huge 4x regression in `colour-science/colour`, related to
[this large
file](https://github.com/colour-science/colour/blob/develop/colour/io/luts/tests/test_lut.py)
with [many assignments of hard-coded arrays (lists of lists) to
`np.NDArray`
types](83e754c8b6/colour/io/luts/tests/test_lut.py (L701-L781))
that we now understand. We now take ~2 seconds to check this file, so
definitely not great, but maybe acceptable for now.
## Test Plan
Updated and new Markdown tests
This commit is contained in:
parent
594b7b04d3
commit
42f152108a
|
|
@ -120,7 +120,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
|
|||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY310,
|
||||
},
|
||||
600,
|
||||
1070,
|
||||
);
|
||||
|
||||
static FREQTRADE: Benchmark = Benchmark::new(
|
||||
|
|
|
|||
|
|
@ -61,8 +61,7 @@ async def main():
|
|||
|
||||
result = await task
|
||||
|
||||
# TODO: this should be `int`
|
||||
reveal_type(result) # revealed: Unknown
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
### `asyncio.gather`
|
||||
|
|
@ -79,9 +78,8 @@ async def main():
|
|||
task("B"),
|
||||
)
|
||||
|
||||
# TODO: these should be `int`
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int
|
||||
```
|
||||
|
||||
## Under the hood
|
||||
|
|
|
|||
|
|
@ -191,13 +191,13 @@ def _(
|
|||
reveal_type(int_or_callable) # revealed: int | ((str, /) -> bytes)
|
||||
reveal_type(callable_or_int) # revealed: ((str, /) -> bytes) | int
|
||||
# TODO should be Unknown | int
|
||||
reveal_type(type_var_or_int) # revealed: typing.TypeVar | int
|
||||
reveal_type(type_var_or_int) # revealed: T@TypeVarOrInt | int
|
||||
# TODO should be int | Unknown
|
||||
reveal_type(int_or_type_var) # revealed: int | typing.TypeVar
|
||||
reveal_type(int_or_type_var) # revealed: int | T@IntOrTypeVar
|
||||
# TODO should be Unknown | None
|
||||
reveal_type(type_var_or_none) # revealed: typing.TypeVar | None
|
||||
reveal_type(type_var_or_none) # revealed: T@TypeVarOrNone | None
|
||||
# TODO should be None | Unknown
|
||||
reveal_type(none_or_type_var) # revealed: None | typing.TypeVar
|
||||
reveal_type(none_or_type_var) # revealed: None | T@NoneOrTypeVar
|
||||
```
|
||||
|
||||
If a type is unioned with itself in a value expression, the result is just that type. No
|
||||
|
|
@ -366,7 +366,9 @@ def g(obj: Y):
|
|||
reveal_type(obj) # revealed: list[int | str]
|
||||
```
|
||||
|
||||
## Generic types
|
||||
## Generic implicit type aliases
|
||||
|
||||
### Functionality
|
||||
|
||||
Implicit type aliases can also be generic:
|
||||
|
||||
|
|
@ -388,73 +390,62 @@ ListOrTuple = list[T] | tuple[T, ...]
|
|||
ListOrTupleLegacy = Union[list[T], tuple[T, ...]]
|
||||
MyCallable = Callable[P, T]
|
||||
AnnotatedType = Annotated[T, "tag"]
|
||||
TransparentAlias = T
|
||||
MyOptional = T | None
|
||||
|
||||
# TODO: Consider displaying this as `<class 'list[T]'>`, … instead? (and similar for some others below)
|
||||
reveal_type(MyList) # revealed: <class 'list[typing.TypeVar]'>
|
||||
reveal_type(MyDict) # revealed: <class 'dict[typing.TypeVar, typing.TypeVar]'>
|
||||
reveal_type(MyList) # revealed: <class 'list[T@MyList]'>
|
||||
reveal_type(MyDict) # revealed: <class 'dict[T@MyDict, U@MyDict]'>
|
||||
reveal_type(MyType) # revealed: GenericAlias
|
||||
reveal_type(IntAndType) # revealed: <class 'tuple[int, typing.TypeVar]'>
|
||||
reveal_type(Pair) # revealed: <class 'tuple[typing.TypeVar, typing.TypeVar]'>
|
||||
reveal_type(Sum) # revealed: <class 'tuple[typing.TypeVar, typing.TypeVar]'>
|
||||
reveal_type(IntAndType) # revealed: <class 'tuple[int, T@IntAndType]'>
|
||||
reveal_type(Pair) # revealed: <class 'tuple[T@Pair, T@Pair]'>
|
||||
reveal_type(Sum) # revealed: <class 'tuple[T@Sum, U@Sum]'>
|
||||
reveal_type(ListOrTuple) # revealed: types.UnionType
|
||||
reveal_type(ListOrTupleLegacy) # revealed: types.UnionType
|
||||
reveal_type(MyCallable) # revealed: GenericAlias
|
||||
reveal_type(MyCallable) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
reveal_type(AnnotatedType) # revealed: <typing.Annotated special form>
|
||||
reveal_type(TransparentAlias) # revealed: typing.TypeVar
|
||||
reveal_type(MyOptional) # revealed: types.UnionType
|
||||
|
||||
def _(
|
||||
list_of_ints: MyList[int],
|
||||
dict_str_to_int: MyDict[str, int],
|
||||
# TODO: no error here
|
||||
# error: [invalid-type-form] "`typing.TypeVar` is not a generic class"
|
||||
subclass_of_int: MyType[int],
|
||||
int_and_str: IntAndType[str],
|
||||
pair_of_ints: Pair[int],
|
||||
int_and_bytes: Sum[int, bytes],
|
||||
list_or_tuple: ListOrTuple[int],
|
||||
list_or_tuple_legacy: ListOrTupleLegacy[int],
|
||||
# TODO: no error here
|
||||
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `tuple[str, bytes]`?"
|
||||
my_callable: MyCallable[[str, bytes], int],
|
||||
annotated_int: AnnotatedType[int],
|
||||
transparent_alias: TransparentAlias[int],
|
||||
optional_int: MyOptional[int],
|
||||
):
|
||||
# TODO: This should be `list[int]`
|
||||
reveal_type(list_of_ints) # revealed: @Todo(specialized generic alias in type expression)
|
||||
# TODO: This should be `dict[str, int]`
|
||||
reveal_type(dict_str_to_int) # revealed: @Todo(specialized generic alias in type expression)
|
||||
# TODO: This should be `type[int]`
|
||||
reveal_type(subclass_of_int) # revealed: Unknown
|
||||
# TODO: This should be `tuple[int, str]`
|
||||
reveal_type(int_and_str) # revealed: @Todo(specialized generic alias in type expression)
|
||||
# TODO: This should be `tuple[int, int]`
|
||||
reveal_type(pair_of_ints) # revealed: @Todo(specialized generic alias in type expression)
|
||||
# TODO: This should be `tuple[int, bytes]`
|
||||
reveal_type(int_and_bytes) # revealed: @Todo(specialized generic alias in type expression)
|
||||
# TODO: This should be `list[int] | tuple[int, ...]`
|
||||
reveal_type(list_or_tuple) # revealed: @Todo(Generic specialization of types.UnionType)
|
||||
# TODO: This should be `list[int] | tuple[int, ...]`
|
||||
reveal_type(list_or_tuple_legacy) # revealed: @Todo(Generic specialization of types.UnionType)
|
||||
reveal_type(list_of_ints) # revealed: list[int]
|
||||
reveal_type(dict_str_to_int) # revealed: dict[str, int]
|
||||
reveal_type(subclass_of_int) # revealed: type[int]
|
||||
reveal_type(int_and_str) # revealed: tuple[int, str]
|
||||
reveal_type(pair_of_ints) # revealed: tuple[int, int]
|
||||
reveal_type(int_and_bytes) # revealed: tuple[int, bytes]
|
||||
reveal_type(list_or_tuple) # revealed: list[int] | tuple[int, ...]
|
||||
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
|
||||
# TODO: This should be `(str, bytes) -> int`
|
||||
reveal_type(my_callable) # revealed: @Todo(Generic specialization of typing.Callable)
|
||||
# TODO: This should be `int`
|
||||
reveal_type(annotated_int) # revealed: @Todo(Generic specialization of typing.Annotated)
|
||||
reveal_type(my_callable) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
reveal_type(annotated_int) # revealed: int
|
||||
reveal_type(transparent_alias) # revealed: int
|
||||
reveal_type(optional_int) # revealed: int | None
|
||||
```
|
||||
|
||||
Generic implicit type aliases can be partially specialized:
|
||||
|
||||
```py
|
||||
U = TypeVar("U")
|
||||
|
||||
DictStrTo = MyDict[str, U]
|
||||
|
||||
reveal_type(DictStrTo) # revealed: GenericAlias
|
||||
reveal_type(DictStrTo) # revealed: <class 'dict[str, U@DictStrTo]'>
|
||||
|
||||
def _(
|
||||
# TODO: No error here
|
||||
# error: [invalid-type-form] "Invalid subscript of object of type `GenericAlias` in type expression"
|
||||
dict_str_to_int: DictStrTo[int],
|
||||
):
|
||||
# TODO: This should be `dict[str, int]`
|
||||
reveal_type(dict_str_to_int) # revealed: Unknown
|
||||
reveal_type(dict_str_to_int) # revealed: dict[str, int]
|
||||
```
|
||||
|
||||
Using specializations of generic implicit type aliases in other implicit type aliases works as
|
||||
|
|
@ -464,43 +455,118 @@ expected:
|
|||
IntsOrNone = MyList[int] | None
|
||||
IntsOrStrs = Pair[int] | Pair[str]
|
||||
ListOfPairs = MyList[Pair[str]]
|
||||
ListOrTupleOfInts = ListOrTuple[int]
|
||||
AnnotatedInt = AnnotatedType[int]
|
||||
SubclassOfInt = MyType[int]
|
||||
CallableIntToStr = MyCallable[[int], str]
|
||||
|
||||
reveal_type(IntsOrNone) # revealed: UnionType
|
||||
reveal_type(IntsOrStrs) # revealed: UnionType
|
||||
reveal_type(ListOfPairs) # revealed: GenericAlias
|
||||
reveal_type(IntsOrNone) # revealed: types.UnionType
|
||||
reveal_type(IntsOrStrs) # revealed: types.UnionType
|
||||
reveal_type(ListOfPairs) # revealed: <class 'list[tuple[str, str]]'>
|
||||
reveal_type(ListOrTupleOfInts) # revealed: types.UnionType
|
||||
reveal_type(AnnotatedInt) # revealed: <typing.Annotated special form>
|
||||
reveal_type(SubclassOfInt) # revealed: GenericAlias
|
||||
reveal_type(CallableIntToStr) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
|
||||
def _(
|
||||
# TODO: This should not be an error
|
||||
# error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression"
|
||||
ints_or_none: IntsOrNone,
|
||||
# TODO: This should not be an error
|
||||
# error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression"
|
||||
ints_or_strs: IntsOrStrs,
|
||||
list_of_pairs: ListOfPairs,
|
||||
list_or_tuple_of_ints: ListOrTupleOfInts,
|
||||
annotated_int: AnnotatedInt,
|
||||
subclass_of_int: SubclassOfInt,
|
||||
callable_int_to_str: CallableIntToStr,
|
||||
):
|
||||
# TODO: This should be `list[int] | None`
|
||||
reveal_type(ints_or_none) # revealed: Unknown
|
||||
# TODO: This should be `tuple[int, int] | tuple[str, str]`
|
||||
reveal_type(ints_or_strs) # revealed: Unknown
|
||||
# TODO: This should be `list[tuple[str, str]]`
|
||||
reveal_type(list_of_pairs) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
|
||||
reveal_type(ints_or_none) # revealed: list[int] | None
|
||||
reveal_type(ints_or_strs) # revealed: tuple[int, int] | tuple[str, str]
|
||||
reveal_type(list_of_pairs) # revealed: list[tuple[str, str]]
|
||||
reveal_type(list_or_tuple_of_ints) # revealed: list[int] | tuple[int, ...]
|
||||
reveal_type(annotated_int) # revealed: int
|
||||
reveal_type(subclass_of_int) # revealed: type[int]
|
||||
# TODO: This should be `(int, /) -> str`
|
||||
reveal_type(callable_int_to_str) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
```
|
||||
|
||||
If a generic implicit type alias is used unspecialized in a type expression, we treat it as an
|
||||
`Unknown` specialization:
|
||||
A generic implicit type alias can also be used in another generic implicit type alias:
|
||||
|
||||
```py
|
||||
from typing_extensions import Any
|
||||
|
||||
B = TypeVar("B", bound=int)
|
||||
|
||||
MyOtherList = MyList[T]
|
||||
MyOtherType = MyType[T]
|
||||
TypeOrList = MyType[B] | MyList[B]
|
||||
|
||||
reveal_type(MyOtherList) # revealed: <class 'list[T@MyOtherList]'>
|
||||
reveal_type(MyOtherType) # revealed: GenericAlias
|
||||
reveal_type(TypeOrList) # revealed: types.UnionType
|
||||
|
||||
def _(
|
||||
list_of_ints: MyOtherList[int],
|
||||
subclass_of_int: MyOtherType[int],
|
||||
type_or_list: TypeOrList[Any],
|
||||
):
|
||||
reveal_type(list_of_ints) # revealed: list[int]
|
||||
reveal_type(subclass_of_int) # revealed: type[int]
|
||||
reveal_type(type_or_list) # revealed: type[Any] | list[Any]
|
||||
```
|
||||
|
||||
If a generic implicit type alias is used unspecialized in a type expression, we use the default
|
||||
specialization. For type variables without defaults, this is `Unknown`:
|
||||
|
||||
```py
|
||||
def _(
|
||||
my_list: MyList,
|
||||
my_dict: MyDict,
|
||||
list_unknown: MyList,
|
||||
dict_unknown: MyDict,
|
||||
subclass_of_unknown: MyType,
|
||||
int_and_unknown: IntAndType,
|
||||
pair_of_unknown: Pair,
|
||||
unknown_and_unknown: Sum,
|
||||
list_or_tuple: ListOrTuple,
|
||||
list_or_tuple_legacy: ListOrTupleLegacy,
|
||||
my_callable: MyCallable,
|
||||
annotated_unknown: AnnotatedType,
|
||||
optional_unknown: MyOptional,
|
||||
):
|
||||
# TODO: Should be `list[Unknown]`
|
||||
reveal_type(my_list) # revealed: list[typing.TypeVar]
|
||||
# TODO: Should be `dict[Unknown, Unknown]`
|
||||
reveal_type(my_dict) # revealed: dict[typing.TypeVar, typing.TypeVar]
|
||||
# TODO: This should be `list[Unknown]`
|
||||
reveal_type(list_unknown) # revealed: list[T@MyList]
|
||||
# TODO: This should be `dict[Unknown, Unknown]`
|
||||
reveal_type(dict_unknown) # revealed: dict[T@MyDict, U@MyDict]
|
||||
# TODO: Should be `type[Unknown]`
|
||||
reveal_type(subclass_of_unknown) # revealed: type[T@MyType]
|
||||
# TODO: Should be `tuple[int, Unknown]`
|
||||
reveal_type(int_and_unknown) # revealed: tuple[int, T@IntAndType]
|
||||
# TODO: Should be `tuple[Unknown, Unknown]`
|
||||
reveal_type(pair_of_unknown) # revealed: tuple[T@Pair, T@Pair]
|
||||
# TODO: Should be `tuple[Unknown, Unknown]`
|
||||
reveal_type(unknown_and_unknown) # revealed: tuple[T@Sum, U@Sum]
|
||||
# TODO: Should be `list[Unknown] | tuple[Unknown, ...]`
|
||||
reveal_type(list_or_tuple) # revealed: list[T@ListOrTuple] | tuple[T@ListOrTuple, ...]
|
||||
# TODO: Should be `list[Unknown] | tuple[Unknown, ...]`
|
||||
reveal_type(list_or_tuple_legacy) # revealed: list[T@ListOrTupleLegacy] | tuple[T@ListOrTupleLegacy, ...]
|
||||
# TODO: Should be `(...) -> Unknown`
|
||||
reveal_type(my_callable) # revealed: (...) -> typing.TypeVar
|
||||
reveal_type(my_callable) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
# TODO: Should be `Unknown`
|
||||
reveal_type(annotated_unknown) # revealed: T@AnnotatedType
|
||||
# TODO: Should be `Unknown | None`
|
||||
reveal_type(optional_unknown) # revealed: T@MyOptional | None
|
||||
```
|
||||
|
||||
For a type variable with a default, we use the default type:
|
||||
|
||||
```py
|
||||
T_default = TypeVar("T_default", default=int)
|
||||
|
||||
MyListWithDefault = list[T_default]
|
||||
|
||||
def _(
|
||||
list_of_str: MyListWithDefault[str],
|
||||
list_of_int: MyListWithDefault,
|
||||
):
|
||||
reveal_type(list_of_str) # revealed: list[str]
|
||||
# TODO: this should be `list[int]`
|
||||
reveal_type(list_of_int) # revealed: list[T_default@MyListWithDefault]
|
||||
```
|
||||
|
||||
(Generic) implicit type aliases can be used as base classes:
|
||||
|
|
@ -522,37 +588,209 @@ reveal_mro(Derived1)
|
|||
|
||||
GenericBaseAlias = GenericBase[T]
|
||||
|
||||
# TODO: No error here
|
||||
# error: [non-subscriptable] "Cannot subscript object of type `<class 'GenericBase[typing.TypeVar]'>` with no `__class_getitem__` method"
|
||||
class Derived2(GenericBaseAlias[int]):
|
||||
pass
|
||||
```
|
||||
|
||||
### Imported aliases
|
||||
|
||||
Generic implicit type aliases can be imported from other modules and specialized:
|
||||
|
||||
`my_types.py`:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
MyList = list[T]
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from my_types import MyList
|
||||
import my_types as mt
|
||||
|
||||
def _(
|
||||
list_of_ints1: MyList[int],
|
||||
list_of_ints2: mt.MyList[int],
|
||||
):
|
||||
reveal_type(list_of_ints1) # revealed: list[int]
|
||||
reveal_type(list_of_ints2) # revealed: list[int]
|
||||
```
|
||||
|
||||
### In stringified annotations
|
||||
|
||||
Generic implicit type aliases can be specialized in stringified annotations:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
MyList = list[T]
|
||||
|
||||
def _(
|
||||
list_of_ints: "MyList[int]",
|
||||
):
|
||||
reveal_type(list_of_ints) # revealed: list[int]
|
||||
```
|
||||
|
||||
### Tuple unpacking
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
V = TypeVar("V")
|
||||
|
||||
X = tuple[T, *tuple[U, ...], V]
|
||||
Y = X[T, tuple[int, str, U], bytes]
|
||||
|
||||
def g(obj: Y[bool, range]):
|
||||
reveal_type(obj) # revealed: tuple[bool, *tuple[tuple[int, str, range], ...], bytes]
|
||||
```
|
||||
|
||||
### Error cases
|
||||
|
||||
A generic alias that is already fully specialized cannot be specialized again:
|
||||
|
||||
```py
|
||||
ListOfInts = list[int]
|
||||
|
||||
# TODO: this should be an error
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
def _(doubly_specialized: ListOfInts[int]):
|
||||
# TODO: this should be `Unknown`
|
||||
reveal_type(doubly_specialized) # revealed: @Todo(specialized generic alias in type expression)
|
||||
# TODO: This should ideally be `list[Unknown]` or `Unknown`
|
||||
reveal_type(doubly_specialized) # revealed: list[int]
|
||||
```
|
||||
|
||||
Specializing a generic implicit type alias with an incorrect number of type arguments also results
|
||||
in an error:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
MyList = list[T]
|
||||
MyDict = dict[T, U]
|
||||
|
||||
def _(
|
||||
# TODO: this should be an error
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 1, got 2"
|
||||
list_too_many_args: MyList[int, str],
|
||||
# TODO: this should be an error
|
||||
# error: [invalid-type-arguments] "No type argument provided for required type variable `U`"
|
||||
dict_too_few_args: MyDict[int],
|
||||
):
|
||||
# TODO: this should be `Unknown`
|
||||
reveal_type(list_too_many_args) # revealed: @Todo(specialized generic alias in type expression)
|
||||
# TODO: this should be `Unknown`
|
||||
reveal_type(dict_too_few_args) # revealed: @Todo(specialized generic alias in type expression)
|
||||
reveal_type(list_too_many_args) # revealed: list[Unknown]
|
||||
reveal_type(dict_too_few_args) # revealed: dict[Unknown, Unknown]
|
||||
```
|
||||
|
||||
Trying to specialize a non-name node results in an error:
|
||||
|
||||
```py
|
||||
from ty_extensions import TypeOf
|
||||
|
||||
IntOrStr = int | str
|
||||
|
||||
def this_does_not_work() -> TypeOf[IntOrStr]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def _(
|
||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
specialized: this_does_not_work()[int],
|
||||
):
|
||||
reveal_type(specialized) # revealed: int | str
|
||||
```
|
||||
|
||||
Similarly, if you try to specialize a union type without a binding context, we emit an error:
|
||||
|
||||
```py
|
||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
x: (list[T] | set[T])[int]
|
||||
|
||||
def _():
|
||||
# TODO: `list[Unknown] | set[Unknown]` might be better
|
||||
reveal_type(x) # revealed: list[typing.TypeVar] | set[typing.TypeVar]
|
||||
```
|
||||
|
||||
### Multiple definitions
|
||||
|
||||
#### Shadowed definitions
|
||||
|
||||
When a generic type alias shadows a definition from an outer scope, the inner definition is used:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
MyAlias = list[T]
|
||||
|
||||
def outer():
|
||||
MyAlias = set[T]
|
||||
|
||||
def _(x: MyAlias[int]):
|
||||
reveal_type(x) # revealed: set[int]
|
||||
```
|
||||
|
||||
#### Statically known conditions
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
if True:
|
||||
MyAlias1 = list[T]
|
||||
else:
|
||||
MyAlias1 = set[T]
|
||||
|
||||
if False:
|
||||
MyAlias2 = list[T]
|
||||
else:
|
||||
MyAlias2 = set[T]
|
||||
|
||||
def _(
|
||||
x1: MyAlias1[int],
|
||||
x2: MyAlias2[int],
|
||||
):
|
||||
reveal_type(x1) # revealed: list[int]
|
||||
reveal_type(x2) # revealed: set[int]
|
||||
```
|
||||
|
||||
#### Statically unknown conditions
|
||||
|
||||
If several definitions are visible, we emit an error:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
if flag():
|
||||
MyAlias = list[T]
|
||||
else:
|
||||
MyAlias = set[T]
|
||||
|
||||
# It is questionable whether this should be supported or not. It might also be reasonable to
|
||||
# emit an error here (e.g. "Invalid subscript of object of type `<class 'list[T@MyAlias]'> |
|
||||
# <class 'set[T@MyAlias]'>` in type expression"). If we ever choose to do so, the revealed
|
||||
# type should probably be `Unknown`.
|
||||
def _(x: MyAlias[int]):
|
||||
reveal_type(x) # revealed: list[int] | set[int]
|
||||
```
|
||||
|
||||
## `Literal`s
|
||||
|
|
@ -642,8 +880,7 @@ Deprecated = Annotated[T, "deprecated attribute"]
|
|||
class C:
|
||||
old: Deprecated[int]
|
||||
|
||||
# TODO: Should be `int`
|
||||
reveal_type(C().old) # revealed: @Todo(Generic specialization of typing.Annotated)
|
||||
reveal_type(C().old) # revealed: int
|
||||
```
|
||||
|
||||
If the metadata argument is missing, we emit an error (because this code fails at runtime), but
|
||||
|
|
@ -1298,3 +1535,21 @@ def _(
|
|||
reveal_type(recursive_dict3) # revealed: dict[Divergent, int]
|
||||
reveal_type(recursive_dict4) # revealed: dict[Divergent, int]
|
||||
```
|
||||
|
||||
### Self-referential generic implicit type aliases
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
NestedDict = dict[str, "NestedDict[T] | T"]
|
||||
NestedList = list["NestedList[T] | None"]
|
||||
|
||||
def _(
|
||||
nested_dict_int: NestedDict[int],
|
||||
nested_list_str: NestedList[str],
|
||||
):
|
||||
reveal_type(nested_dict_int) # revealed: dict[str, Divergent]
|
||||
reveal_type(nested_list_str) # revealed: list[Divergent]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -44,9 +44,6 @@ class _SupportsDType(Protocol[_DTypeT_co]):
|
|||
@property
|
||||
def dtype(self) -> _DTypeT_co: ...
|
||||
|
||||
# TODO: no errors here
|
||||
# error: [invalid-type-arguments] "Type `typing.TypeVar` is not assignable to upper bound `generic[Any]` of type variable `_ScalarT_co@dtype`"
|
||||
# error: [invalid-type-arguments] "Type `typing.TypeVar` is not assignable to upper bound `generic[Any]` of type variable `_ScalarT_co@dtype`"
|
||||
_DTypeLike: TypeAlias = type[_ScalarT] | dtype[_ScalarT] | _SupportsDType[dtype[_ScalarT]]
|
||||
|
||||
DTypeLike: TypeAlias = _DTypeLike[Any] | str | None
|
||||
|
|
|
|||
|
|
@ -96,6 +96,45 @@ def _(x: MyAlias):
|
|||
reveal_type(x) # revealed: int | ((str, /) -> int)
|
||||
```
|
||||
|
||||
## Generic aliases
|
||||
|
||||
A more comprehensive set of tests can be found in
|
||||
[`implicit_type_aliases.md`](./implicit_type_aliases.md). If the implementations ever diverge, we
|
||||
may need to duplicate more tests here.
|
||||
|
||||
### Basic
|
||||
|
||||
```py
|
||||
from typing import TypeAlias, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
MyList: TypeAlias = list[T]
|
||||
ListOrSet: TypeAlias = list[T] | set[T]
|
||||
|
||||
reveal_type(MyList) # revealed: <class 'list[T]'>
|
||||
reveal_type(ListOrSet) # revealed: types.UnionType
|
||||
|
||||
def _(list_of_int: MyList[int], list_or_set_of_str: ListOrSet[str]):
|
||||
reveal_type(list_of_int) # revealed: list[int]
|
||||
reveal_type(list_or_set_of_str) # revealed: list[str] | set[str]
|
||||
```
|
||||
|
||||
### Stringified generic alias
|
||||
|
||||
```py
|
||||
from typing import TypeAlias, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
TotallyStringifiedPEP613: TypeAlias = "dict[T, U]"
|
||||
TotallyStringifiedPartiallySpecialized: TypeAlias = "TotallyStringifiedPEP613[U, int]"
|
||||
|
||||
def f(x: "TotallyStringifiedPartiallySpecialized[str]"):
|
||||
reveal_type(x) # revealed: @Todo(Generic stringified PEP-613 type alias)
|
||||
```
|
||||
|
||||
## Subscripted generic alias in union
|
||||
|
||||
```py
|
||||
|
|
@ -107,8 +146,7 @@ Alias1: TypeAlias = list[T] | set[T]
|
|||
MyAlias: TypeAlias = int | Alias1[str]
|
||||
|
||||
def _(x: MyAlias):
|
||||
# TODO: int | list[str] | set[str]
|
||||
reveal_type(x) # revealed: int | @Todo(Specialization of union type alias)
|
||||
reveal_type(x) # revealed: int | list[str] | set[str]
|
||||
```
|
||||
|
||||
## Imported
|
||||
|
|
@ -173,7 +211,7 @@ NestedDict: TypeAlias = dict[K, Union[V, "NestedDict[K, V]"]]
|
|||
|
||||
def _(nested: NestedDict[str, int]):
|
||||
# TODO should be `dict[str, int | NestedDict[str, int]]`
|
||||
reveal_type(nested) # revealed: @Todo(specialized generic alias in type expression)
|
||||
reveal_type(nested) # revealed: dict[@Todo(specialized recursive generic type alias), Divergent]
|
||||
|
||||
my_isinstance(1, int)
|
||||
my_isinstance(1, int | str)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,12 @@ impl<'db> Definition<'db> {
|
|||
.to_string(),
|
||||
)
|
||||
}
|
||||
DefinitionKind::Assignment(assignment) => {
|
||||
let target_node = assignment.target.node(&module);
|
||||
target_node
|
||||
.as_name_expr()
|
||||
.map(|name_expr| name_expr.id.as_str().to_string())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3726,7 +3726,7 @@ impl<'db> Type<'db> {
|
|||
dunder_call
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
CallableType::unknown(db),
|
||||
Type::Callable(CallableType::unknown(db)),
|
||||
inferable,
|
||||
TypeRelation::Assignability,
|
||||
relation_visitor,
|
||||
|
|
@ -7219,7 +7219,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
KnownInstanceType::Literal(ty) => Ok(ty.inner(db)),
|
||||
KnownInstanceType::Annotated(ty) => Ok(ty.inner(db)),
|
||||
KnownInstanceType::TypeGenericAlias(ty) => {
|
||||
KnownInstanceType::TypeGenericAlias(instance) => {
|
||||
// When `type[…]` appears in a value position (e.g. in an implicit type alias),
|
||||
// we infer its argument as a type expression. This ensures that we can emit
|
||||
// diagnostics for invalid type expressions, and more importantly, that we can
|
||||
|
|
@ -7228,7 +7228,7 @@ impl<'db> Type<'db> {
|
|||
// (`int` -> instance of `int` -> subclass of `int`) can be lossy, but it is
|
||||
// okay for all valid arguments to `type[…]`.
|
||||
|
||||
Ok(ty.inner(db).to_meta_type(db))
|
||||
Ok(instance.inner(db).to_meta_type(db))
|
||||
}
|
||||
KnownInstanceType::Callable(callable) => Ok(Type::Callable(*callable)),
|
||||
KnownInstanceType::LiteralStringAlias(ty) => Ok(ty.inner(db)),
|
||||
|
|
@ -7258,7 +7258,7 @@ impl<'db> Type<'db> {
|
|||
SpecialFormType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)),
|
||||
|
||||
// TODO: Use an opt-in rule for a bare `Callable`
|
||||
SpecialFormType::Callable => Ok(CallableType::unknown(db)),
|
||||
SpecialFormType::Callable => Ok(Type::Callable(CallableType::unknown(db))),
|
||||
|
||||
// Special case: `NamedTuple` in a type expression is understood to describe the type
|
||||
// `tuple[object, ...] & <a protocol that any `NamedTuple` class would satisfy>`.
|
||||
|
|
@ -7578,18 +7578,71 @@ impl<'db> Type<'db> {
|
|||
match self {
|
||||
Type::TypeVar(bound_typevar) => bound_typevar.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
|
||||
TypeMapping::BindLegacyTypevars(binding_context) => {
|
||||
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::TypeVar(typevar) => {
|
||||
match type_mapping {
|
||||
TypeMapping::BindLegacyTypevars(binding_context) => {
|
||||
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
|
||||
}
|
||||
TypeMapping::Specialization(_) |
|
||||
TypeMapping::PartialSpecialization(_) |
|
||||
TypeMapping::PromoteLiterals(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::Materialize(_) |
|
||||
TypeMapping::ReplaceParameterDefaults |
|
||||
TypeMapping::EagerExpansion => self,
|
||||
}
|
||||
}
|
||||
TypeMapping::Specialization(_) |
|
||||
TypeMapping::PartialSpecialization(_) |
|
||||
TypeMapping::PromoteLiterals(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::Materialize(_) |
|
||||
TypeMapping::ReplaceParameterDefaults |
|
||||
TypeMapping::EagerExpansion => self,
|
||||
KnownInstanceType::UnionType(instance) => {
|
||||
if let Ok(union_type) = instance.union_type(db) {
|
||||
Type::KnownInstance(KnownInstanceType::UnionType(
|
||||
UnionTypeInstance::new(
|
||||
db,
|
||||
instance._value_expr_types(db),
|
||||
Ok(union_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
|
||||
)
|
||||
)))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
},
|
||||
KnownInstanceType::Annotated(ty) => {
|
||||
Type::KnownInstance(KnownInstanceType::Annotated(
|
||||
InternedType::new(
|
||||
db,
|
||||
ty.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
|
||||
)
|
||||
))
|
||||
},
|
||||
KnownInstanceType::Callable(callable_type) => {
|
||||
Type::KnownInstance(KnownInstanceType::Callable(
|
||||
callable_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor),
|
||||
))
|
||||
},
|
||||
KnownInstanceType::TypeGenericAlias(ty) => {
|
||||
Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
|
||||
InternedType::new(
|
||||
db,
|
||||
ty.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
|
||||
)
|
||||
))
|
||||
},
|
||||
|
||||
KnownInstanceType::SubscriptedProtocol(_) |
|
||||
KnownInstanceType::SubscriptedGeneric(_) |
|
||||
KnownInstanceType::TypeAliasType(_) |
|
||||
KnownInstanceType::Deprecated(_) |
|
||||
KnownInstanceType::Field(_) |
|
||||
KnownInstanceType::ConstraintSet(_) |
|
||||
KnownInstanceType::GenericContext(_) |
|
||||
KnownInstanceType::Specialization(_) |
|
||||
KnownInstanceType::Literal(_) |
|
||||
KnownInstanceType::LiteralStringAlias(_) |
|
||||
KnownInstanceType::NewType(_) => {
|
||||
// TODO: For some of these, we may need to apply the type mapping to inner types.
|
||||
self
|
||||
},
|
||||
}
|
||||
|
||||
Type::FunctionLiteral(function) => {
|
||||
|
|
@ -7755,8 +7808,7 @@ impl<'db> Type<'db> {
|
|||
// some other generic context's specialization is applied to it.
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_) => self,
|
||||
| Type::SpecialForm(_) => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7893,6 +7945,44 @@ impl<'db> Type<'db> {
|
|||
});
|
||||
}
|
||||
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::UnionType(instance) => {
|
||||
if let Ok(union_type) = instance.union_type(db) {
|
||||
union_type.find_legacy_typevars_impl(
|
||||
db,
|
||||
binding_context,
|
||||
typevars,
|
||||
visitor,
|
||||
);
|
||||
}
|
||||
}
|
||||
KnownInstanceType::Annotated(ty) => {
|
||||
ty.inner(db)
|
||||
.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
|
||||
}
|
||||
KnownInstanceType::Callable(callable_type) => {
|
||||
callable_type.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
|
||||
}
|
||||
KnownInstanceType::TypeGenericAlias(ty) => {
|
||||
ty.inner(db)
|
||||
.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
|
||||
}
|
||||
KnownInstanceType::SubscriptedProtocol(_)
|
||||
| KnownInstanceType::SubscriptedGeneric(_)
|
||||
| KnownInstanceType::TypeVar(_)
|
||||
| KnownInstanceType::TypeAliasType(_)
|
||||
| KnownInstanceType::Deprecated(_)
|
||||
| KnownInstanceType::Field(_)
|
||||
| KnownInstanceType::ConstraintSet(_)
|
||||
| KnownInstanceType::GenericContext(_)
|
||||
| KnownInstanceType::Specialization(_)
|
||||
| KnownInstanceType::Literal(_)
|
||||
| KnownInstanceType::LiteralStringAlias(_)
|
||||
| KnownInstanceType::NewType(_) => {
|
||||
// TODO: For some of these, we may need to try to find legacy typevars in inner types.
|
||||
}
|
||||
},
|
||||
|
||||
Type::Dynamic(_)
|
||||
| Type::Never
|
||||
| Type::AlwaysTruthy
|
||||
|
|
@ -7920,7 +8010,6 @@ impl<'db> Type<'db> {
|
|||
| Type::EnumLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::TypedDict(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -11595,8 +11684,8 @@ impl<'db> CallableType<'db> {
|
|||
}
|
||||
|
||||
/// Create a callable type which accepts any parameters and returns an `Unknown` type.
|
||||
pub(crate) fn unknown(db: &'db dyn Db) -> Type<'db> {
|
||||
Type::Callable(Self::single(db, Signature::unknown()))
|
||||
pub(crate) fn unknown(db: &'db dyn Db) -> CallableType<'db> {
|
||||
Self::single(db, Signature::unknown())
|
||||
}
|
||||
|
||||
pub(crate) fn bind_self(
|
||||
|
|
|
|||
|
|
@ -4796,6 +4796,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
unpacked.expression_type(target)
|
||||
}
|
||||
TargetKind::Single => {
|
||||
// This could be an implicit type alias (OptionalList = list[T] | None). Use the definition
|
||||
// of `OptionalList` as the binding context while inferring the RHS (`list[T] | None`), in
|
||||
// order to bind `T` to `OptionalList`.
|
||||
let previous_typevar_binding_context =
|
||||
self.typevar_binding_context.replace(definition);
|
||||
|
||||
let value_ty = if let Some(standalone_expression) = self.index.try_expression(value)
|
||||
{
|
||||
self.infer_standalone_expression_impl(value, standalone_expression, tcx)
|
||||
|
|
@ -4834,6 +4840,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.infer_expression(value, tcx)
|
||||
};
|
||||
|
||||
self.typevar_binding_context = previous_typevar_binding_context;
|
||||
|
||||
// `TYPE_CHECKING` is a special variable that should only be assigned `False`
|
||||
// at runtime, but is always considered `True` in type checking.
|
||||
// See mdtest/known_constants.md#user-defined-type_checking for details.
|
||||
|
|
@ -5580,11 +5588,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.deferred_state = DeferredExpressionState::Deferred;
|
||||
}
|
||||
|
||||
// This might be a PEP-613 type alias (`OptionalList: TypeAlias = list[T] | None`). Use
|
||||
// the definition of `OptionalList` as the binding context while inferring the
|
||||
// RHS (`list[T] | None`), in order to bind `T` to `OptionalList`.
|
||||
let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);
|
||||
|
||||
let inferred_ty = self.infer_maybe_standalone_expression(
|
||||
value,
|
||||
TypeContext::new(Some(declared.inner_type())),
|
||||
);
|
||||
|
||||
self.typevar_binding_context = previous_typevar_binding_context;
|
||||
|
||||
self.deferred_state = previous_deferred_state;
|
||||
|
||||
self.dataclass_field_specifiers.clear();
|
||||
|
|
@ -10860,6 +10875,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
fn infer_subscript_load(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
|
||||
let value_ty = self.infer_expression(&subscript.value, TypeContext::default());
|
||||
|
||||
// If we have an implicit type alias like `MyList = list[T]`, and if `MyList` is being
|
||||
// used in another implicit type alias like `Numbers = MyList[int]`, then we infer the
|
||||
// right hand side as a value expression, and need to handle the specialization here.
|
||||
if value_ty.is_generic_alias() {
|
||||
return self.infer_explicit_type_alias_specialization(subscript, value_ty, false);
|
||||
}
|
||||
|
||||
self.infer_subscript_load_impl(value_ty, subscript)
|
||||
}
|
||||
|
||||
|
|
@ -10929,7 +10952,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias)) => {
|
||||
if let Some(generic_context) = type_alias.generic_context(self.db()) {
|
||||
return self.infer_explicit_type_alias_specialization(
|
||||
return self.infer_explicit_type_alias_type_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
type_alias,
|
||||
|
|
@ -11065,6 +11088,35 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
));
|
||||
}
|
||||
Type::SpecialForm(SpecialFormType::Callable) => {
|
||||
// TODO: Remove this once we support ParamSpec properly. This is necessary to avoid
|
||||
// a lot of false positives downstream, because we can't represent the specialized
|
||||
// `Callable[P, _]` type yet.
|
||||
if let Some(first_arg) = subscript
|
||||
.slice
|
||||
.as_ref()
|
||||
.as_tuple_expr()
|
||||
.and_then(|args| args.elts.first())
|
||||
&& first_arg.is_name_expr()
|
||||
{
|
||||
let first_arg_ty = self.infer_expression(first_arg, TypeContext::default());
|
||||
|
||||
if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = first_arg_ty
|
||||
&& typevar.kind(self.db()).is_paramspec()
|
||||
{
|
||||
return todo_type!("Callable[..] specialized with ParamSpec");
|
||||
}
|
||||
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The first argument to `Callable` must be either a list of types, \
|
||||
ParamSpec, Concatenate, or `...`",
|
||||
));
|
||||
}
|
||||
return Type::KnownInstance(KnownInstanceType::Callable(
|
||||
CallableType::unknown(self.db()),
|
||||
));
|
||||
}
|
||||
|
||||
let callable = self
|
||||
.infer_callable_type(subscript)
|
||||
.as_callable()
|
||||
|
|
@ -11161,8 +11213,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.map(Type::from)
|
||||
.unwrap_or_else(Type::unknown);
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::UnionType(_)) => {
|
||||
return todo_type!("Specialization of union type alias");
|
||||
Type::KnownInstance(
|
||||
KnownInstanceType::UnionType(_)
|
||||
| KnownInstanceType::Annotated(_)
|
||||
| KnownInstanceType::Callable(_)
|
||||
| KnownInstanceType::TypeGenericAlias(_),
|
||||
) => {
|
||||
return self.infer_explicit_type_alias_specialization(subscript, value_ty, false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -11194,7 +11251,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
)
|
||||
}
|
||||
|
||||
fn infer_explicit_type_alias_specialization(
|
||||
fn infer_explicit_type_alias_type_specialization(
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
value_ty: Type<'db>,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use itertools::Either;
|
|||
use ruff_python_ast as ast;
|
||||
|
||||
use super::{DeferredExpressionState, TypeInferenceBuilder};
|
||||
use crate::FxOrderSet;
|
||||
use crate::types::diagnostic::{
|
||||
self, INVALID_TYPE_FORM, NON_SUBSCRIPTABLE, report_invalid_argument_number_to_special_form,
|
||||
report_invalid_arguments_to_callable,
|
||||
|
|
@ -12,9 +13,9 @@ use crate::types::string_annotation::parse_string_annotation;
|
|||
use crate::types::tuple::{TupleSpecBuilder, TupleType};
|
||||
use crate::types::visitor::any_over_type;
|
||||
use crate::types::{
|
||||
CallableType, DynamicType, IntersectionBuilder, KnownClass, KnownInstanceType,
|
||||
LintDiagnosticGuard, SpecialFormType, SubclassOfType, Type, TypeAliasType, TypeContext,
|
||||
TypeIsType, UnionBuilder, UnionType, todo_type,
|
||||
BindingContext, CallableType, DynamicType, GenericContext, IntersectionBuilder, KnownClass,
|
||||
KnownInstanceType, LintDiagnosticGuard, SpecialFormType, SubclassOfType, Type, TypeAliasType,
|
||||
TypeContext, TypeIsType, TypeMapping, UnionBuilder, UnionType, todo_type,
|
||||
};
|
||||
|
||||
/// Type expressions
|
||||
|
|
@ -734,6 +735,84 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Infer the type of an explicitly specialized generic type alias (implicit or PEP 613).
|
||||
pub(crate) fn infer_explicit_type_alias_specialization(
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
mut value_ty: Type<'db>,
|
||||
in_type_expression: bool,
|
||||
) -> Type<'db> {
|
||||
let db = self.db();
|
||||
|
||||
if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = value_ty
|
||||
&& let Some(definition) = typevar.definition(db)
|
||||
{
|
||||
value_ty = value_ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::BindLegacyTypevars(BindingContext::Definition(definition)),
|
||||
TypeContext::default(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut variables = FxOrderSet::default();
|
||||
value_ty.find_legacy_typevars(db, None, &mut variables);
|
||||
let generic_context = GenericContext::from_typevar_instances(db, variables);
|
||||
|
||||
let scope_id = self.scope();
|
||||
let current_typevar_binding_context = self.typevar_binding_context;
|
||||
|
||||
// TODO
|
||||
// If we explicitly specialize a recursive generic (PEP-613 or implicit) type alias,
|
||||
// we currently miscount the number of type variables. For example, for a nested
|
||||
// dictionary type alias `NestedDict = dict[K, "V | NestedDict[K, V]"]]`, we might
|
||||
// infer `<class 'dict[K, Divergent]'>`, and therefore count just one type variable
|
||||
// instead of two. So until we properly support these, specialize all remaining type
|
||||
// variables with a `@Todo` type (since we don't know which of the type arguments
|
||||
// belongs to the remaining type variables).
|
||||
if any_over_type(self.db(), value_ty, &|ty| ty.is_divergent(), true) {
|
||||
let value_ty = value_ty.apply_specialization(
|
||||
db,
|
||||
generic_context.specialize(
|
||||
db,
|
||||
std::iter::repeat_n(
|
||||
todo_type!("specialized recursive generic type alias"),
|
||||
generic_context.len(db),
|
||||
)
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
return if in_type_expression {
|
||||
value_ty
|
||||
.in_type_expression(db, scope_id, current_typevar_binding_context)
|
||||
.unwrap_or_else(|_| Type::unknown())
|
||||
} else {
|
||||
value_ty
|
||||
};
|
||||
}
|
||||
|
||||
let specialize = |types: &[Option<Type<'db>>]| {
|
||||
let specialized = value_ty.apply_specialization(
|
||||
db,
|
||||
generic_context.specialize_partial(db, types.iter().copied()),
|
||||
);
|
||||
|
||||
if in_type_expression {
|
||||
specialized
|
||||
.in_type_expression(db, scope_id, current_typevar_binding_context)
|
||||
.unwrap_or_else(|_| Type::unknown())
|
||||
} else {
|
||||
specialized
|
||||
}
|
||||
};
|
||||
|
||||
self.infer_explicit_callable_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
generic_context,
|
||||
specialize,
|
||||
)
|
||||
}
|
||||
|
||||
fn infer_subscript_type_expression(
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
|
|
@ -824,15 +903,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
Type::unknown()
|
||||
}
|
||||
KnownInstanceType::TypeVar(_) => {
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("TypeVar annotations")
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(type_alias @ TypeAliasType::PEP695(_)) => {
|
||||
match type_alias.generic_context(self.db()) {
|
||||
Some(generic_context) => {
|
||||
let specialized_type_alias = self
|
||||
.infer_explicit_type_alias_specialization(
|
||||
.infer_explicit_type_alias_type_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
type_alias,
|
||||
|
|
@ -870,11 +945,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
self.infer_type_expression(slice);
|
||||
todo_type!("Generic stringified PEP-613 type alias")
|
||||
}
|
||||
KnownInstanceType::UnionType(_) => {
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("Generic specialization of types.UnionType")
|
||||
}
|
||||
KnownInstanceType::Literal(ty) | KnownInstanceType::TypeGenericAlias(ty) => {
|
||||
KnownInstanceType::Literal(ty) => {
|
||||
self.infer_type_expression(slice);
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
|
|
@ -884,13 +955,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
Type::unknown()
|
||||
}
|
||||
KnownInstanceType::Callable(_) => {
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("Generic specialization of typing.Callable")
|
||||
KnownInstanceType::TypeVar(_) => {
|
||||
self.infer_explicit_type_alias_specialization(subscript, value_ty, false)
|
||||
}
|
||||
KnownInstanceType::Annotated(_) => {
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("Generic specialization of typing.Annotated")
|
||||
|
||||
KnownInstanceType::UnionType(_)
|
||||
| KnownInstanceType::Callable(_)
|
||||
| KnownInstanceType::Annotated(_)
|
||||
| KnownInstanceType::TypeGenericAlias(_) => {
|
||||
self.infer_explicit_type_alias_specialization(subscript, value_ty, true)
|
||||
}
|
||||
KnownInstanceType::NewType(newtype) => {
|
||||
self.infer_type_expression(&subscript.slice);
|
||||
|
|
@ -904,7 +977,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
},
|
||||
Type::Dynamic(_) => {
|
||||
self.infer_type_expression(slice);
|
||||
// Infer slice as a value expression to avoid false-positive
|
||||
// `invalid-type-form` diagnostics, when we have e.g.
|
||||
// `MyCallable[[int, str], None]` but `MyCallable` is dynamic.
|
||||
self.infer_expression(slice, TypeContext::default());
|
||||
value_ty
|
||||
}
|
||||
Type::ClassLiteral(class) => {
|
||||
|
|
@ -933,11 +1009,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
}
|
||||
Type::GenericAlias(_) => {
|
||||
self.infer_type_expression(slice);
|
||||
// If the generic alias is already fully specialized, this is an error. But it
|
||||
// could have been specialized with another typevar (e.g. a type alias like `MyList
|
||||
// = list[T]`), in which case it's later valid to do `MyList[int]`.
|
||||
todo_type!("specialized generic alias in type expression")
|
||||
self.infer_explicit_type_alias_specialization(subscript, value_ty, true)
|
||||
}
|
||||
Type::StringLiteral(_) => {
|
||||
self.infer_type_expression(slice);
|
||||
|
|
@ -1049,7 +1121,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
{
|
||||
Type::single_callable(db, Signature::new(parameters, Some(return_type)))
|
||||
} else {
|
||||
CallableType::unknown(db)
|
||||
Type::Callable(CallableType::unknown(db))
|
||||
};
|
||||
|
||||
// `Signature` / `Parameters` are not a `Type` variant, so we're storing
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ impl ClassInfoConstraintFunction {
|
|||
Type::SpecialForm(SpecialFormType::Callable)
|
||||
if self == ClassInfoConstraintFunction::IsInstance =>
|
||||
{
|
||||
Some(CallableType::unknown(db).top_materialization(db))
|
||||
Some(Type::Callable(CallableType::unknown(db)).top_materialization(db))
|
||||
}
|
||||
|
||||
Type::SpecialForm(special_form) => special_form
|
||||
|
|
|
|||
Loading…
Reference in New Issue