mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-01 12:25:45 +00:00
[ty] defer inference of legacy TypeVar bound/constraints/defaults (#20598)
## Summary This allows us to handle self-referential bounds/constraints/defaults without panicking. Handles more cases from https://github.com/astral-sh/ty/issues/256 This also changes the way we infer the types of legacy TypeVars. Rather than understanding a constructor call to `typing[_extension].TypeVar` inside of any (arbitrarily nested) expression, and having to use a special `assigned_to` field of the semantic index to try to best-effort figure out what name the typevar was assigned to, we instead understand the creation of a legacy `TypeVar` only in the supported syntactic position (RHS of a simple un-annotated assignment with one target). In any other position, we just infer it as creating an opaque instance of `typing.TypeVar`. (This behavior matches all other type checkers.) So we now special-case TypeVar creation in `TypeInferenceBuilder`, as a special case of an assignment definition, rather than deeper inside call binding. This does mean we re-implement slightly more of argument-parsing, but in practice this is minimal and easy to handle correctly. This is easier to implement if we also make the RHS of a simple (no unpacking) one-target assignment statement no longer a standalone expression. Which is fine to do, because simple one-target assignments don't need to infer the RHS more than once. This is a bonus performance (0-3% across various projects) and significant memory-usage win, since most assignment statements are simple one-target assignment statements, meaning we now create many fewer standalone-expression salsa ingredients. This change does mean that inference of manually-constructed `TypeAliasType` instances can no longer find its Definition in `assigned_to`, which regresses go-to-definition for these aliases. In a future PR, `TypeAliasType` will receive the same treatment that `TypeVar` did in this PR (moving its special-case inference into `TypeInferenceBuilder` and supporting it only in the correct syntactic position, and lazily inferring its value type to support recursion), which will also fix the go-to-definition regression. (I decided a temporary edge-case regression is better in this case than doubling the size of this PR.) This PR also tightens up and fixes various aspects of the validation of `TypeVar` creation, as seen in the tests. We still (for now) treat all typevars as instances of `typing.TypeVar`, even if they were created using `typing_extensions.TypeVar`. This means we'll wrongly error on e.g. `T.__default__` on Python 3.11, even if `T` is a `typing_extensions.TypeVar` instance at runtime. We share this wrong behavior with both mypy and pyrefly. It will be easier to fix after we pull in https://github.com/python/typeshed/pull/14840. There are some issues that showed up here with typevar identity and `MarkTypeVarsInferable`; the fix here (using the new `original` field and `is_identical_to` methods on `BoundTypeVarInstance` and `TypeVarInstance`) is a bit kludgy, but it can go away when we eliminate `MarkTypeVarsInferable`. ## Test Plan Added and updated mdtests. ### Conformance suite impact The impact here is all positive: * We now correctly error on a legacy TypeVar with exactly one constraint type given. * We now correctly error on a legacy TypeVar with both an upper bound and constraints specified. ### Ecosystem impact Basically none; in the setuptools case we just issue slightly different errors on an invalid TypeVar definition, due to the modified validation code. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
b086ffe921
commit
8248193ed9
24 changed files with 1441 additions and 408 deletions
|
|
@ -0,0 +1,139 @@
|
|||
# Legacy typevar creation diagnostics
|
||||
|
||||
The full tests for these features are in `generics/legacy/variables.md`.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## Must have a name
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar()
|
||||
```
|
||||
|
||||
## Name can't be given more than once
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", name="T")
|
||||
```
|
||||
|
||||
## Must be directly assigned to a variable
|
||||
|
||||
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
|
||||
> part of a larger expression).
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U: TypeVar = TypeVar("U")
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
```
|
||||
|
||||
## `TypeVar` parameter must match variable name
|
||||
|
||||
> The argument to `TypeVar()` must be a string equal to the variable name to which it is assigned.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("Q")
|
||||
```
|
||||
|
||||
## No variadic arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
types = (int, str)
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", *types)
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
S = TypeVar("S", **{"bound": int})
|
||||
```
|
||||
|
||||
## Cannot have only one constraint
|
||||
|
||||
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
|
||||
> be at least two constraints, if any; specifying a single constraint is disallowed.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", int)
|
||||
```
|
||||
|
||||
## Cannot have both bound and constraint
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", int, str, bound=bytes)
|
||||
```
|
||||
|
||||
## Cannot be both covariant and contravariant
|
||||
|
||||
> To facilitate the declaration of container types where covariant or contravariant type checking is
|
||||
> acceptable, type variables accept keyword arguments `covariant=True` or `contravariant=True`. At
|
||||
> most one of these may be passed.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", covariant=True, contravariant=True)
|
||||
```
|
||||
|
||||
## Boolean parameters must be unambiguous
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", covariant=cond())
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U = TypeVar("U", contravariant=cond())
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
V = TypeVar("V", infer_variance=cond())
|
||||
```
|
||||
|
||||
## Invalid keyword arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", invalid_keyword=True)
|
||||
```
|
||||
|
||||
## Invalid feature for this Python version
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", default=int)
|
||||
```
|
||||
|
|
@ -108,7 +108,7 @@ reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTyp
|
|||
The type parameter can be specified explicitly:
|
||||
|
||||
```py
|
||||
from typing import Generic, Literal, TypeVar
|
||||
from typing_extensions import Generic, Literal, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
|
|||
We can infer the type parameter from a type context:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ consistent with each other.
|
|||
### `__new__` only
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -257,7 +257,7 @@ wrong_innards: C[int] = C("five")
|
|||
### `__init__` only
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ wrong_innards: C[int] = C("five")
|
|||
### Identical `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -292,7 +292,7 @@ wrong_innards: C[int] = C("five")
|
|||
### Compatible `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -325,7 +325,7 @@ If either method comes from a generic base class, we don't currently use its inf
|
|||
to specialize the class.
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -344,7 +344,7 @@ reveal_type(D(1)) # revealed: D[int]
|
|||
### Generic class inherits `__init__` from generic base class
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -364,7 +364,7 @@ reveal_type(D(1, "str")) # revealed: D[int, str]
|
|||
This is a specific example of the above, since it was reported specifically by a user.
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -382,7 +382,7 @@ for `tuple`, so we use a different mechanism to make sure it has the right inher
|
|||
context. But from the user's point of view, this is another example of the above.)
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -403,7 +403,7 @@ python-version = "3.11"
|
|||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Sequence, Never
|
||||
from typing_extensions import TypeVar, Sequence, Never
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -421,7 +421,7 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t
|
|||
### `__init__` is itself generic
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
T = TypeVar("T")
|
||||
|
|
@ -440,7 +440,7 @@ wrong_innards: C[int] = C("five", 1)
|
|||
### Some `__init__` overloads only apply to certain specializations
|
||||
|
||||
```py
|
||||
from typing import overload, Generic, TypeVar
|
||||
from typing_extensions import overload, Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -480,7 +480,7 @@ C[None](12)
|
|||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -494,7 +494,7 @@ reveal_type(A(x=1)) # revealed: A[int]
|
|||
### Class typevar has another typevar as a default
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U", default=T)
|
||||
|
|
@ -515,7 +515,7 @@ When a generic subclass fills its superclass's type parameter with one of its ow
|
|||
propagate through:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -549,7 +549,7 @@ scope for the method.
|
|||
|
||||
```py
|
||||
from ty_extensions import generic_context
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -581,7 +581,7 @@ In a specialized generic alias, the specialization is applied to the attributes
|
|||
class.
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar, Protocol
|
||||
from typing_extensions import Generic, TypeVar, Protocol
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
|
@ -639,7 +639,7 @@ reveal_type(d.method3().x) # revealed: int
|
|||
When a method is overloaded, the specialization is applied to all overloads.
|
||||
|
||||
```py
|
||||
from typing import overload, Generic, TypeVar
|
||||
from typing_extensions import overload, Generic, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
|
|
@ -667,7 +667,7 @@ A class can use itself as the type parameter of one of its superclasses. (This i
|
|||
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
|
||||
|
||||
```pyi
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -682,7 +682,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
|
|||
A similar case can work in a non-stub file, if forward references are stringified:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -697,7 +697,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
|
|||
In a non-stub file, without stringified forward references, this raises a `NameError`:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -710,7 +710,7 @@ class Sub(Base[Sub]): ...
|
|||
### Cyclic inheritance as a generic parameter
|
||||
|
||||
```pyi
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -722,7 +722,7 @@ class Derived(list[Derived[T]], Generic[T]): ...
|
|||
Inheritance that would result in a cyclic MRO is detected as an error.
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ for both type variable syntaxes.
|
|||
|
||||
Unless otherwise specified, all quotations come from the [Generics] section of the typing spec.
|
||||
|
||||
Diagnostics for invalid type variables are snapshotted in `diagnostics/legacy_typevars.md`.
|
||||
|
||||
## Type variables
|
||||
|
||||
### Defining legacy type variables
|
||||
|
|
@ -24,7 +26,16 @@ reveal_type(T) # revealed: typing.TypeVar
|
|||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
### Directly assigned to a variable
|
||||
The typevar name can also be provided as a keyword argument:
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar(name="T")
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
### Must be directly assigned to a variable
|
||||
|
||||
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
|
||||
> part of a larger expression).
|
||||
|
|
@ -33,13 +44,24 @@ reveal_type(T.__name__) # revealed: Literal["T"]
|
|||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
# TODO: no error
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U: TypeVar = TypeVar("U")
|
||||
|
||||
# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable"
|
||||
# error: [invalid-type-form] "Function calls are not allowed in type expressions"
|
||||
TestList = list[TypeVar("W")]
|
||||
# error: [invalid-legacy-type-variable]
|
||||
tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U: TypeVar = TypeVar("U")
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
|
||||
```
|
||||
|
||||
### `TypeVar` parameter must match variable name
|
||||
|
|
@ -49,7 +71,7 @@ TestList = list[TypeVar("W")]
|
|||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)"
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("Q")
|
||||
```
|
||||
|
||||
|
|
@ -66,6 +88,22 @@ T = TypeVar("T")
|
|||
T = TypeVar("T")
|
||||
```
|
||||
|
||||
### No variadic arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
types = (int, str)
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", *types)
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
S = TypeVar("S", **{"bound": int})
|
||||
reveal_type(S) # revealed: TypeVar
|
||||
```
|
||||
|
||||
### Type variables with a default
|
||||
|
||||
Note that the `__default__` property is only available in Python ≥3.13.
|
||||
|
|
@ -91,6 +129,11 @@ reveal_type(S.__default__) # revealed: NoDefault
|
|||
|
||||
### Using other typevars as a default
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar, Union
|
||||
|
||||
|
|
@ -124,6 +167,15 @@ S = TypeVar("S")
|
|||
reveal_type(S.__bound__) # revealed: None
|
||||
```
|
||||
|
||||
The upper bound must be a valid type expression:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
# error: [invalid-type-form]
|
||||
T = TypeVar("T", bound=TypedDict)
|
||||
```
|
||||
|
||||
### Type variables with constraints
|
||||
|
||||
```py
|
||||
|
|
@ -138,6 +190,16 @@ S = TypeVar("S")
|
|||
reveal_type(S.__constraints__) # revealed: tuple[()]
|
||||
```
|
||||
|
||||
Constraints are not simplified relative to each other, even if one is a subtype of the other:
|
||||
|
||||
```py
|
||||
T = TypeVar("T", int, bool)
|
||||
reveal_type(T.__constraints__) # revealed: tuple[int, bool]
|
||||
|
||||
S = TypeVar("S", float, str)
|
||||
reveal_type(S.__constraints__) # revealed: tuple[int | float, str]
|
||||
```
|
||||
|
||||
### Cannot have only one constraint
|
||||
|
||||
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
|
||||
|
|
@ -146,10 +208,19 @@ reveal_type(S.__constraints__) # revealed: tuple[()]
|
|||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# TODO: error: [invalid-type-variable-constraints]
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", int)
|
||||
```
|
||||
|
||||
### Cannot have both bound and constraint
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", int, str, bound=bytes)
|
||||
```
|
||||
|
||||
### Cannot be both covariant and contravariant
|
||||
|
||||
> To facilitate the declaration of container types where covariant or contravariant type checking is
|
||||
|
|
@ -163,10 +234,10 @@ from typing import TypeVar
|
|||
T = TypeVar("T", covariant=True, contravariant=True)
|
||||
```
|
||||
|
||||
### Variance parameters must be unambiguous
|
||||
### Boolean parameters must be unambiguous
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
|
|
@ -176,6 +247,73 @@ T = TypeVar("T", covariant=cond())
|
|||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U = TypeVar("U", contravariant=cond())
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
V = TypeVar("V", infer_variance=cond())
|
||||
```
|
||||
|
||||
### Invalid keyword arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", invalid_keyword=True)
|
||||
```
|
||||
|
||||
```pyi
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", invalid_keyword=True)
|
||||
|
||||
```
|
||||
|
||||
### Constructor signature versioning
|
||||
|
||||
#### For `typing.TypeVar`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
In a stub file, features from the latest supported Python version can be used on any version.
|
||||
There's no need to require use of `typing_extensions.TypeVar` in a stub file, when the type checker
|
||||
can understand the typevar definition perfectly well either way, and there can be no runtime error.
|
||||
(Perhaps it's arguable whether this special case is worth it, but other type checkers do it, so we
|
||||
maintain compatibility.)
|
||||
|
||||
```pyi
|
||||
from typing import TypeVar
|
||||
T = TypeVar("T", default=int)
|
||||
```
|
||||
|
||||
But this raises an error in a non-stub file:
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", default=int)
|
||||
```
|
||||
|
||||
#### For `typing_extensions.TypeVar`
|
||||
|
||||
`typing_extensions.TypeVar` always supports the latest features, on any Python version.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T", default=int)
|
||||
# TODO: should not error, should reveal `int`
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(T.__default__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Callability
|
||||
|
|
@ -231,4 +369,96 @@ def constrained(x: T_constrained):
|
|||
reveal_type(type(x)) # revealed: type[int] | type[str]
|
||||
```
|
||||
|
||||
## Cycles
|
||||
|
||||
### Bounds and constraints
|
||||
|
||||
A typevar's bounds and constraints cannot be generic, cyclic or otherwise:
|
||||
|
||||
```py
|
||||
from typing import Any, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
# TODO: error
|
||||
T = TypeVar("T", bound=list[S])
|
||||
|
||||
# TODO: error
|
||||
U = TypeVar("U", list["T"], str)
|
||||
|
||||
# TODO: error
|
||||
V = TypeVar("V", list["V"], str)
|
||||
```
|
||||
|
||||
However, they are lazily evaluated and can cyclically refer to their own type:
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
T = TypeVar("T", bound=list["G"])
|
||||
|
||||
class G(Generic[T]):
|
||||
x: T
|
||||
|
||||
reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
|
||||
```
|
||||
|
||||
### Defaults
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
Defaults can be generic, but can only refer to typevars from the same scope if they were defined
|
||||
earlier in that scope:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U", default=T)
|
||||
|
||||
class C(Generic[T, U]):
|
||||
x: T
|
||||
y: U
|
||||
|
||||
reveal_type(C[int, str]().x) # revealed: int
|
||||
reveal_type(C[int, str]().y) # revealed: str
|
||||
reveal_type(C[int]().x) # revealed: int
|
||||
reveal_type(C[int]().y) # revealed: int
|
||||
|
||||
# TODO: error
|
||||
V = TypeVar("V", default="V")
|
||||
|
||||
class D(Generic[V]):
|
||||
x: V
|
||||
|
||||
# TODO: we shouldn't leak a typevar like this in type inference
|
||||
reveal_type(D().x) # revealed: V@D
|
||||
```
|
||||
|
||||
## Regression
|
||||
|
||||
### Use of typevar with default inside a function body that binds it
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
_DataT = TypeVar("_DataT", bound=int, default=int)
|
||||
|
||||
class Event(Generic[_DataT]):
|
||||
def __init__(self, data: _DataT) -> None:
|
||||
self.data = data
|
||||
|
||||
def async_fire_internal(event_data: _DataT):
|
||||
event: Event[_DataT] | None = None
|
||||
event = Event(event_data)
|
||||
```
|
||||
|
||||
[generics]: https://typing.python.org/en/latest/spec/generics.html
|
||||
|
|
|
|||
|
|
@ -197,9 +197,9 @@ from typing_extensions import TypeAliasType, TypeVar
|
|||
|
||||
T = TypeVar("T")
|
||||
|
||||
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
|
||||
IntAndT = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
|
||||
|
||||
def f(x: IntAnd[str]) -> None:
|
||||
def f(x: IntAndT[str]) -> None:
|
||||
reveal_type(x) # revealed: @Todo(Generic manual PEP-695 type alias)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Boolean parameters must be unambiguous
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import TypeVar
|
||||
2 |
|
||||
3 | def cond() -> bool:
|
||||
4 | return True
|
||||
5 |
|
||||
6 | # error: [invalid-legacy-type-variable]
|
||||
7 | T = TypeVar("T", covariant=cond())
|
||||
8 |
|
||||
9 | # error: [invalid-legacy-type-variable]
|
||||
10 | U = TypeVar("U", contravariant=cond())
|
||||
11 |
|
||||
12 | # error: [invalid-legacy-type-variable]
|
||||
13 | V = TypeVar("V", infer_variance=cond())
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `covariant` parameter of `TypeVar` cannot have an ambiguous truthiness
|
||||
--> src/mdtest_snippet.py:7:28
|
||||
|
|
||||
6 | # error: [invalid-legacy-type-variable]
|
||||
7 | T = TypeVar("T", covariant=cond())
|
||||
| ^^^^^^
|
||||
8 |
|
||||
9 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `contravariant` parameter of `TypeVar` cannot have an ambiguous truthiness
|
||||
--> src/mdtest_snippet.py:10:32
|
||||
|
|
||||
9 | # error: [invalid-legacy-type-variable]
|
||||
10 | U = TypeVar("U", contravariant=cond())
|
||||
| ^^^^^^
|
||||
11 |
|
||||
12 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `infer_variance` parameter of `TypeVar` cannot have an ambiguous truthiness
|
||||
--> src/mdtest_snippet.py:13:33
|
||||
|
|
||||
12 | # error: [invalid-legacy-type-variable]
|
||||
13 | V = TypeVar("V", infer_variance=cond())
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot be both covariant and contravariant
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", covariant=True, contravariant=True)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` cannot be both covariant and contravariant
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", covariant=True, contravariant=True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have both bound and constraint
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int, str, bound=bytes)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` cannot have both a bound and constraints
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int, str, bound=bytes)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have only one constraint
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` cannot have exactly one constraint
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int)
|
||||
| ^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid feature for this Python version
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", default=int)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `default` parameter of `typing.TypeVar` was added in Python 3.13
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", default=int)
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid keyword arguments
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", invalid_keyword=True)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: Unknown keyword argument `invalid_keyword` in `TypeVar` creation
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", invalid_keyword=True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must be directly assigned to a variable
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | T = TypeVar("T")
|
||||
4 | # error: [invalid-legacy-type-variable]
|
||||
5 | U: TypeVar = TypeVar("U")
|
||||
6 |
|
||||
7 | # error: [invalid-legacy-type-variable]
|
||||
8 | tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
|
||||
--> src/mdtest_snippet.py:5:14
|
||||
|
|
||||
3 | T = TypeVar("T")
|
||||
4 | # error: [invalid-legacy-type-variable]
|
||||
5 | U: TypeVar = TypeVar("U")
|
||||
| ^^^^^^^^^^^^
|
||||
6 |
|
||||
7 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
|
||||
--> src/mdtest_snippet.py:8:30
|
||||
|
|
||||
7 | # error: [invalid-legacy-type-variable]
|
||||
8 | tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must have a name
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar()
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` is required.
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar()
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Name can't be given more than once
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", name="T")
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` can only be provided once.
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", name="T")
|
||||
| ^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - No variadic arguments
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | types = (int, str)
|
||||
4 |
|
||||
5 | # error: [invalid-legacy-type-variable]
|
||||
6 | T = TypeVar("T", *types)
|
||||
7 |
|
||||
8 | # error: [invalid-legacy-type-variable]
|
||||
9 | S = TypeVar("S", **{"bound": int})
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
|
||||
--> src/mdtest_snippet.py:6:18
|
||||
|
|
||||
5 | # error: [invalid-legacy-type-variable]
|
||||
6 | T = TypeVar("T", *types)
|
||||
| ^^^^^^
|
||||
7 |
|
||||
8 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
|
||||
--> src/mdtest_snippet.py:9:18
|
||||
|
|
||||
8 | # error: [invalid-legacy-type-variable]
|
||||
9 | S = TypeVar("S", **{"bound": int})
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - `TypeVar` parameter must match variable name
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("Q")
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The name of a `TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)
|
||||
--> src/mdtest_snippet.py:4:1
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("Q")
|
||||
| ^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue