mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
Rename Red Knot (#17820)
This commit is contained in:
parent
e6a798b962
commit
b51c4f82ea
1564 changed files with 1598 additions and 1578 deletions
|
@ -0,0 +1,443 @@
|
|||
# Generic classes: Legacy syntax
|
||||
|
||||
## Defining a generic class
|
||||
|
||||
At its simplest, to define a generic class using the legacy syntax, you inherit from the
|
||||
`typing.Generic` special form, which is "specialized" with the generic class's type variables.
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
S = TypeVar("S")
|
||||
|
||||
class SingleTypevar(Generic[T]): ...
|
||||
class MultipleTypevars(Generic[T, S]): ...
|
||||
|
||||
reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T]
|
||||
reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S]
|
||||
```
|
||||
|
||||
You cannot use the same typevar more than once.
|
||||
|
||||
```py
|
||||
# TODO: error
|
||||
class RepeatedTypevar(Generic[T, T]): ...
|
||||
```
|
||||
|
||||
You can only specialize `typing.Generic` with typevars (TODO: or param specs or typevar tuples).
|
||||
|
||||
```py
|
||||
# error: [invalid-argument-type] "`Literal[int]` is not a valid argument to `typing.Generic`"
|
||||
class GenericOfType(Generic[int]): ...
|
||||
```
|
||||
|
||||
You can also define a generic class by inheriting from some _other_ generic class, and specializing
|
||||
it with typevars.
|
||||
|
||||
```py
|
||||
class InheritedGeneric(MultipleTypevars[T, S]): ...
|
||||
class InheritedGenericPartiallySpecialized(MultipleTypevars[T, int]): ...
|
||||
class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ...
|
||||
|
||||
reveal_type(generic_context(InheritedGeneric)) # revealed: tuple[T, S]
|
||||
reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: tuple[T]
|
||||
reveal_type(generic_context(InheritedGenericFullySpecialized)) # revealed: None
|
||||
```
|
||||
|
||||
If you don't specialize a generic base class, we use the default specialization, which maps each
|
||||
typevar to its default value or `Any`. Since that base class is fully specialized, it does not make
|
||||
the inheriting class generic.
|
||||
|
||||
```py
|
||||
class InheritedGenericDefaultSpecialization(MultipleTypevars): ...
|
||||
|
||||
reveal_type(generic_context(InheritedGenericDefaultSpecialization)) # revealed: None
|
||||
```
|
||||
|
||||
When inheriting from a generic class, you can optionally inherit from `typing.Generic` as well. But
|
||||
if you do, you have to mention all of the typevars that you use in your other base classes.
|
||||
|
||||
```py
|
||||
class ExplicitInheritedGeneric(MultipleTypevars[T, S], Generic[T, S]): ...
|
||||
|
||||
# error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes"
|
||||
class ExplicitInheritedGenericMissingTypevar(MultipleTypevars[T, S], Generic[T]): ...
|
||||
class ExplicitInheritedGenericPartiallySpecialized(MultipleTypevars[T, int], Generic[T]): ...
|
||||
class ExplicitInheritedGenericPartiallySpecializedExtraTypevar(MultipleTypevars[T, int], Generic[T, S]): ...
|
||||
|
||||
# error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes"
|
||||
class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ...
|
||||
|
||||
reveal_type(generic_context(ExplicitInheritedGeneric)) # revealed: tuple[T, S]
|
||||
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized)) # revealed: tuple[T]
|
||||
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar)) # revealed: tuple[T, S]
|
||||
```
|
||||
|
||||
## Specializing generic classes explicitly
|
||||
|
||||
The type parameter can be specified explicitly:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
x: T
|
||||
|
||||
reveal_type(C[int]()) # revealed: C[int]
|
||||
```
|
||||
|
||||
The specialization must match the generic types:
|
||||
|
||||
```py
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to class `C`: expected 1, got 2"
|
||||
reveal_type(C[int, int]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
BoundedT = TypeVar("BoundedT", bound=int)
|
||||
BoundedByUnionT = TypeVar("BoundedByUnionT", bound=Union[int, str])
|
||||
|
||||
class Bounded(Generic[BoundedT]): ...
|
||||
class BoundedByUnion(Generic[BoundedByUnionT]): ...
|
||||
class IntSubclass(int): ...
|
||||
|
||||
reveal_type(Bounded[int]()) # revealed: Bounded[int]
|
||||
reveal_type(Bounded[IntSubclass]()) # revealed: Bounded[IntSubclass]
|
||||
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `str`"
|
||||
reveal_type(Bounded[str]()) # revealed: Unknown
|
||||
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int`, found `int | str`"
|
||||
reveal_type(Bounded[int | str]()) # revealed: Unknown
|
||||
|
||||
reveal_type(BoundedByUnion[int]()) # revealed: BoundedByUnion[int]
|
||||
reveal_type(BoundedByUnion[IntSubclass]()) # revealed: BoundedByUnion[IntSubclass]
|
||||
reveal_type(BoundedByUnion[str]()) # revealed: BoundedByUnion[str]
|
||||
reveal_type(BoundedByUnion[int | str]()) # revealed: BoundedByUnion[int | str]
|
||||
```
|
||||
|
||||
If the type variable is constrained, the specialized type must satisfy those constraints:
|
||||
|
||||
```py
|
||||
ConstrainedT = TypeVar("ConstrainedT", int, str)
|
||||
|
||||
class Constrained(Generic[ConstrainedT]): ...
|
||||
|
||||
reveal_type(Constrained[int]()) # revealed: Constrained[int]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# TODO: revealed: Constrained[Unknown]
|
||||
reveal_type(Constrained[IntSubclass]()) # revealed: Constrained[IntSubclass]
|
||||
|
||||
reveal_type(Constrained[str]()) # revealed: Constrained[str]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# TODO: revealed: Unknown
|
||||
reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str]
|
||||
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `int | str`, found `object`"
|
||||
reveal_type(Constrained[object]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring generic class parameters
|
||||
|
||||
We can infer the type parameter from a type context:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
x: T
|
||||
|
||||
c: C[int] = C()
|
||||
# TODO: revealed: C[int]
|
||||
reveal_type(c) # revealed: C[Unknown]
|
||||
```
|
||||
|
||||
The typevars of a fully specialized generic class should no longer be visible:
|
||||
|
||||
```py
|
||||
# TODO: revealed: int
|
||||
reveal_type(c.x) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type parameter is not specified explicitly, and there are no constraints that let us infer a
|
||||
specific type, we infer the typevar's default type:
|
||||
|
||||
```py
|
||||
DefaultT = TypeVar("DefaultT", default=int)
|
||||
|
||||
class D(Generic[DefaultT]): ...
|
||||
|
||||
reveal_type(D()) # revealed: D[int]
|
||||
```
|
||||
|
||||
If a typevar does not provide a default, we use `Unknown`:
|
||||
|
||||
```py
|
||||
reveal_type(C()) # revealed: C[Unknown]
|
||||
```
|
||||
|
||||
## Inferring generic class parameters from constructors
|
||||
|
||||
If the type of a constructor parameter is a class typevar, we can use that to infer the type
|
||||
parameter. The types inferred from a type context and from a constructor parameter must be
|
||||
consistent with each other.
|
||||
|
||||
### `__new__` only
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
### `__init__` only
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
### Identical `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
### Compatible `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
def __new__(cls, *args, **kwargs) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
|
||||
class D(Generic[T]):
|
||||
def __new__(cls, x: T) -> "D[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None: ...
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `D[Literal["five"]]` is not assignable to `D[int]`"
|
||||
wrong_innards: D[int] = D("five")
|
||||
```
|
||||
|
||||
### Both present, `__new__` inherited from a generic base class
|
||||
|
||||
If either method comes from a generic base class, we don't currently use its inferred specialization
|
||||
to specialize the class.
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
V = TypeVar("V")
|
||||
|
||||
class C(Generic[T, U]):
|
||||
def __new__(cls, *args, **kwargs) -> "C[T, U]":
|
||||
return object.__new__(cls)
|
||||
|
||||
class D(C[V, int]):
|
||||
def __init__(self, x: V) -> None: ...
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
```
|
||||
|
||||
### `__init__` is itself generic
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
T = TypeVar("T")
|
||||
|
||||
class C(Generic[T]):
|
||||
def __init__(self, x: T, y: S) -> None: ...
|
||||
|
||||
reveal_type(C(1, 1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, "string")) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, True)) # revealed: C[Literal[1]]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five", 1)
|
||||
```
|
||||
|
||||
## Generic subclass
|
||||
|
||||
When a generic subclass fills its superclass's type parameter with one of its own, the actual types
|
||||
propagate through:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Base(Generic[T]):
|
||||
x: T | None = None
|
||||
|
||||
class ExplicitlyGenericSub(Base[T], Generic[T]): ...
|
||||
class ImplicitlyGenericSub(Base[T]): ...
|
||||
|
||||
reveal_type(Base[int].x) # revealed: int | None
|
||||
reveal_type(ExplicitlyGenericSub[int].x) # revealed: int | None
|
||||
reveal_type(ImplicitlyGenericSub[int].x) # revealed: int | None
|
||||
```
|
||||
|
||||
## Generic methods
|
||||
|
||||
Generic classes can contain methods that are themselves generic. The generic methods can refer to
|
||||
the typevars of the enclosing generic class, and introduce new (distinct) typevars that are only in
|
||||
scope for the method.
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
class C(Generic[T]):
|
||||
def method(self, u: U) -> U:
|
||||
return u
|
||||
|
||||
c: C[int] = C[int]()
|
||||
reveal_type(c.method("string")) # revealed: Literal["string"]
|
||||
```
|
||||
|
||||
## Cyclic class definitions
|
||||
|
||||
### F-bounded quantification
|
||||
|
||||
A class can use itself as the type parameter of one of its superclasses. (This is also known as the
|
||||
[curiously recurring template pattern][crtp] or [F-bounded quantification][f-bound].)
|
||||
|
||||
#### In a stub file
|
||||
|
||||
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
|
||||
|
||||
```pyi
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Base(Generic[T]): ...
|
||||
class Sub(Base[Sub]): ...
|
||||
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
```
|
||||
|
||||
#### With string forward references
|
||||
|
||||
A similar case can work in a non-stub file, if forward references are stringified:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Base(Generic[T]): ...
|
||||
class Sub(Base["Sub"]): ...
|
||||
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
```
|
||||
|
||||
#### Without string forward references
|
||||
|
||||
In a non-stub file, without stringified forward references, this raises a `NameError`:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Base(Generic[T]): ...
|
||||
|
||||
# error: [unresolved-reference]
|
||||
class Sub(Base[Sub]): ...
|
||||
```
|
||||
|
||||
### Cyclic inheritance as a generic parameter
|
||||
|
||||
```pyi
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Derived(list[Derived[T]], Generic[T]): ...
|
||||
```
|
||||
|
||||
### Direct cyclic inheritance
|
||||
|
||||
Inheritance that would result in a cyclic MRO is detected as an error.
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: [unresolved-reference]
|
||||
class C(C, Generic[T]): ...
|
||||
|
||||
# error: [unresolved-reference]
|
||||
class D(D[int], Generic[T]): ...
|
||||
```
|
||||
|
||||
[crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
|
||||
[f-bound]: https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification
|
|
@ -0,0 +1,272 @@
|
|||
# Generic functions: Legacy syntax
|
||||
|
||||
## Typevar must be used at least twice
|
||||
|
||||
If you're only using a typevar for a single parameter, you don't need the typevar — just use
|
||||
`object` (or the typevar's upper bound):
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# TODO: error, should be (x: object)
|
||||
def typevar_not_needed(x: T) -> None:
|
||||
pass
|
||||
|
||||
BoundedT = TypeVar("BoundedT", bound=int)
|
||||
|
||||
# TODO: error, should be (x: int)
|
||||
def bounded_typevar_not_needed(x: BoundedT) -> None:
|
||||
pass
|
||||
```
|
||||
|
||||
Typevars are only needed if you use them more than once. For instance, to specify that two
|
||||
parameters must both have the same type:
|
||||
|
||||
```py
|
||||
def two_params(x: T, y: T) -> T:
|
||||
return x
|
||||
```
|
||||
|
||||
or to specify that a return value is the same as a parameter:
|
||||
|
||||
```py
|
||||
def return_value(x: T) -> T:
|
||||
return x
|
||||
```
|
||||
|
||||
Each typevar must also appear _somewhere_ in the parameter list:
|
||||
|
||||
```py
|
||||
def absurd() -> T:
|
||||
# There's no way to construct a T!
|
||||
raise ValueError("absurd")
|
||||
```
|
||||
|
||||
## Inferring generic function parameter types
|
||||
|
||||
If the type of a generic function parameter is a typevar, then we can infer what type that typevar
|
||||
is bound to at each call site.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def f(x: T) -> T:
|
||||
return x
|
||||
|
||||
reveal_type(f(1)) # revealed: Literal[1]
|
||||
reveal_type(f(1.0)) # revealed: float
|
||||
reveal_type(f(True)) # revealed: Literal[True]
|
||||
reveal_type(f("string")) # revealed: Literal["string"]
|
||||
```
|
||||
|
||||
## Inferring “deep” generic parameter types
|
||||
|
||||
The matching up of call arguments and discovery of constraints on typevars can be a recursive
|
||||
process for arbitrarily-nested generic types in parameters.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def f(x: list[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: float
|
||||
reveal_type(f([1.0, 2.0])) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
T = TypeVar("T", bound=int)
|
||||
|
||||
def f(x: T) -> T:
|
||||
return x
|
||||
|
||||
reveal_type(f(1)) # revealed: Literal[1]
|
||||
reveal_type(f(True)) # revealed: Literal[True]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(f("string")) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a constrained typevar
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
T = TypeVar("T", int, None)
|
||||
|
||||
def f(x: T) -> T:
|
||||
return x
|
||||
|
||||
reveal_type(f(1)) # revealed: int
|
||||
reveal_type(f(True)) # revealed: int
|
||||
reveal_type(f(None)) # revealed: None
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(f("string")) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Typevar constraints
|
||||
|
||||
If a type parameter has an upper bound, that upper bound constrains which types can be used for that
|
||||
typevar. This effectively adds the upper bound as an intersection to every appearance of the typevar
|
||||
in the function.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", bound=int)
|
||||
|
||||
def good_param(x: T) -> None:
|
||||
reveal_type(x) # revealed: T
|
||||
```
|
||||
|
||||
If the function is annotated as returning the typevar, this means that the upper bound is _not_
|
||||
assignable to that typevar, since return types are contravariant. In `bad`, we can infer that
|
||||
`x + 1` has type `int`. But `T` might be instantiated with a narrower type than `int`, and so the
|
||||
return value is not guaranteed to be compatible for all `T: int`.
|
||||
|
||||
```py
|
||||
def good_return(x: T) -> T:
|
||||
return x
|
||||
|
||||
def bad_return(x: T) -> T:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `int`"
|
||||
return x + 1
|
||||
```
|
||||
|
||||
## All occurrences of the same typevar have the same type
|
||||
|
||||
If a typevar appears multiple times in a function signature, all occurrences have the same type.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
S = TypeVar("S")
|
||||
|
||||
def different_types(cond: bool, t: T, s: S) -> T:
|
||||
if cond:
|
||||
return t
|
||||
else:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `S`"
|
||||
return s
|
||||
|
||||
def same_types(cond: bool, t1: T, t2: T) -> T:
|
||||
if cond:
|
||||
return t1
|
||||
else:
|
||||
return t2
|
||||
```
|
||||
|
||||
## All occurrences of the same constrained typevar have the same type
|
||||
|
||||
The above is true even when the typevars are constrained. Here, both `int` and `str` have `__add__`
|
||||
methods that are compatible with the return type, so the `return` expression is always well-typed:
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", int, str)
|
||||
|
||||
def same_constrained_types(t1: T, t2: T) -> T:
|
||||
# TODO: no error
|
||||
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T` and `T`"
|
||||
return t1 + t2
|
||||
```
|
||||
|
||||
This is _not_ the same as a union type, because of this additional constraint that the two
|
||||
occurrences have the same type. In `unions_are_different`, `t1` and `t2` might have different types,
|
||||
and an `int` and a `str` cannot be added together:
|
||||
|
||||
```py
|
||||
def unions_are_different(t1: int | str, t2: int | str) -> int | str:
|
||||
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `int | str` and `int | str`"
|
||||
return t1 + t2
|
||||
```
|
||||
|
||||
## Typevar inference is a unification problem
|
||||
|
||||
When inferring typevar assignments in a generic function call, we cannot simply solve constraints
|
||||
eagerly for each parameter in turn. We must solve a unification problem involving all of the
|
||||
parameters simultaneously.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def two_params(x: T, y: T) -> T:
|
||||
return x
|
||||
|
||||
reveal_type(two_params("a", "b")) # revealed: Literal["a", "b"]
|
||||
reveal_type(two_params("a", 1)) # revealed: Literal["a", 1]
|
||||
```
|
||||
|
||||
When one of the parameters is a union, we attempt to find the smallest specialization that satisfies
|
||||
all of the constraints.
|
||||
|
||||
```py
|
||||
def union_param(x: T | None) -> T:
|
||||
if x is None:
|
||||
raise ValueError
|
||||
return x
|
||||
|
||||
reveal_type(union_param("a")) # revealed: Literal["a"]
|
||||
reveal_type(union_param(1)) # revealed: Literal[1]
|
||||
reveal_type(union_param(None)) # revealed: Unknown
|
||||
```
|
||||
|
||||
```py
|
||||
def union_and_nonunion_params(x: T | int, y: T) -> T:
|
||||
return y
|
||||
|
||||
reveal_type(union_and_nonunion_params(1, "a")) # revealed: Literal["a"]
|
||||
reveal_type(union_and_nonunion_params("a", "a")) # revealed: Literal["a"]
|
||||
reveal_type(union_and_nonunion_params(1, 1)) # revealed: Literal[1]
|
||||
reveal_type(union_and_nonunion_params(3, 1)) # revealed: Literal[1]
|
||||
reveal_type(union_and_nonunion_params("a", 1)) # revealed: Literal["a", 1]
|
||||
```
|
||||
|
||||
```py
|
||||
S = TypeVar("S")
|
||||
|
||||
def tuple_param(x: T | S, y: tuple[T, S]) -> tuple[T, S]:
|
||||
return y
|
||||
|
||||
reveal_type(tuple_param("a", ("a", 1))) # revealed: tuple[Literal["a"], Literal[1]]
|
||||
reveal_type(tuple_param(1, ("a", 1))) # revealed: tuple[Literal["a"], Literal[1]]
|
||||
```
|
||||
|
||||
## Inferring nested generic function calls
|
||||
|
||||
We can infer type assignments in nested calls to multiple generic functions. If they use the same
|
||||
type variable, we do not confuse the two; `T@f` and `T@g` have separate types in each example below.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def f(x: T) -> tuple[T, int]:
|
||||
return (x, 1)
|
||||
|
||||
def g(x: T) -> T | None:
|
||||
return x
|
||||
|
||||
reveal_type(f(g("a"))) # revealed: tuple[Literal["a"] | None, int]
|
||||
reveal_type(g(f("a"))) # revealed: tuple[Literal["a"], int] | None
|
||||
```
|
|
@ -0,0 +1,126 @@
|
|||
# Legacy type variables
|
||||
|
||||
The tests in this file focus on how type variables are defined using the legacy notation. Most
|
||||
_uses_ of type variables are tested in other files in this directory; we do not duplicate every test
|
||||
for both type variable syntaxes.
|
||||
|
||||
Unless otherwise specified, all quotations come from the [Generics] section of the typing spec.
|
||||
|
||||
## Type variables
|
||||
|
||||
### Defining legacy type variables
|
||||
|
||||
> Generics can be parameterized by using a factory available in `typing` called `TypeVar`.
|
||||
|
||||
This was the only way to create type variables prior to PEP 695/Python 3.12. It is still available
|
||||
in newer Python releases.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
reveal_type(type(T)) # revealed: Literal[TypeVar]
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
### 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")
|
||||
# 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"
|
||||
TestList = list[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] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)"
|
||||
T = TypeVar("Q")
|
||||
```
|
||||
|
||||
### No redefinition
|
||||
|
||||
> Type variables must not be redefined.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# TODO: error
|
||||
T = TypeVar("T")
|
||||
```
|
||||
|
||||
### Type variables with a default
|
||||
|
||||
Note that the `__default__` property is only available in Python ≥3.13.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", default=int)
|
||||
reveal_type(T.__default__) # revealed: int
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
|
||||
S = TypeVar("S")
|
||||
reveal_type(S.__default__) # revealed: NoDefault
|
||||
```
|
||||
|
||||
### Type variables with an upper bound
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", bound=int)
|
||||
reveal_type(T.__bound__) # revealed: int
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
|
||||
S = TypeVar("S")
|
||||
reveal_type(S.__bound__) # revealed: None
|
||||
```
|
||||
|
||||
### Type variables with constraints
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", int, str)
|
||||
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
||||
|
||||
S = TypeVar("S")
|
||||
reveal_type(S.__constraints__) # revealed: tuple[()]
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
# TODO: error: [invalid-type-variable-constraints]
|
||||
T = TypeVar("T", int)
|
||||
```
|
||||
|
||||
[generics]: https://typing.python.org/en/latest/spec/generics.html
|
Loading…
Add table
Add a link
Reference in a new issue