mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-01 04:18:05 +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
|
|
@ -308,26 +308,8 @@ mod tests {
|
|||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing_extensions import TypeAliasType
|
||||
3 |
|
||||
4 | Alias = TypeAliasType("Alias", tuple[int, int])
|
||||
| ^^^^^
|
||||
5 |
|
||||
6 | Alias
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:1
|
||||
|
|
||||
4 | Alias = TypeAliasType("Alias", tuple[int, int])
|
||||
5 |
|
||||
6 | Alias
|
||||
| ^^^^^
|
||||
|
|
||||
"#);
|
||||
// TODO: This should jump to the definition of `Alias` above.
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
@ -1165,10 +1165,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
target: &'ast ast::Expr,
|
||||
value: Expression<'db>,
|
||||
) {
|
||||
// We only handle assignments to names and unpackings here, other targets like
|
||||
// attribute and subscript are handled separately as they don't create a new
|
||||
// definition.
|
||||
|
||||
let current_assignment = match target {
|
||||
ast::Expr::List(_) | ast::Expr::Tuple(_) => {
|
||||
if matches!(unpackable, Unpackable::Comprehension { .. }) {
|
||||
|
|
@ -1628,10 +1624,22 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
debug_assert_eq!(&self.current_assignments, &[]);
|
||||
|
||||
self.visit_expr(&node.value);
|
||||
let value = self.add_standalone_assigned_expression(&node.value, node);
|
||||
|
||||
for target in &node.targets {
|
||||
self.add_unpackable_assignment(&Unpackable::Assign(node), target, value);
|
||||
// Optimization for the common case: if there's just one target, and it's not an
|
||||
// unpacking, and the target is a simple name, we don't need the RHS to be a
|
||||
// standalone expression at all.
|
||||
if let [target] = &node.targets[..]
|
||||
&& target.is_name_expr()
|
||||
{
|
||||
self.push_assignment(CurrentAssignment::Assign { node, unpack: None });
|
||||
self.visit_expr(target);
|
||||
self.pop_assignment();
|
||||
} else {
|
||||
let value = self.add_standalone_assigned_expression(&node.value, node);
|
||||
|
||||
for target in &node.targets {
|
||||
self.add_unpackable_assignment(&Unpackable::Assign(node), target, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Stmt::AnnAssign(node) => {
|
||||
|
|
|
|||
|
|
@ -706,13 +706,6 @@ impl DefinitionKind<'_> {
|
|||
matches!(self, DefinitionKind::Assignment(_))
|
||||
}
|
||||
|
||||
pub(crate) fn as_typevar(&self) -> Option<&AstNodeRef<ast::TypeParamTypeVar>> {
|
||||
match self {
|
||||
DefinitionKind::TypeVar(type_var) => Some(type_var),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`TextRange`] of the definition target.
|
||||
///
|
||||
/// A definition target would mainly be the node representing the place being defined i.e.,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
|||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{KnownModule, resolve_module};
|
||||
use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::place::ScopedPlaceId;
|
||||
use crate::semantic_index::scope::ScopeId;
|
||||
use crate::semantic_index::{imported_modules, place_table, semantic_index};
|
||||
|
|
@ -1642,7 +1642,9 @@ impl<'db> Type<'db> {
|
|||
(
|
||||
Type::NonInferableTypeVar(lhs_bound_typevar),
|
||||
Type::NonInferableTypeVar(rhs_bound_typevar),
|
||||
) if lhs_bound_typevar == rhs_bound_typevar => ConstraintSet::from(true),
|
||||
) if lhs_bound_typevar.is_identical_to(db, rhs_bound_typevar) => {
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// A fully static typevar is a subtype of its upper bound, and to something similar to
|
||||
// the union of its constraints. An unbound, unconstrained, fully static typevar has an
|
||||
|
|
@ -4841,56 +4843,6 @@ impl<'db> Type<'db> {
|
|||
.into()
|
||||
}
|
||||
|
||||
Some(KnownClass::TypeVar) => {
|
||||
// ```py
|
||||
// class TypeVar:
|
||||
// def __new__(
|
||||
// cls,
|
||||
// name: str,
|
||||
// *constraints: Any,
|
||||
// bound: Any | None = None,
|
||||
// contravariant: bool = False,
|
||||
// covariant: bool = False,
|
||||
// infer_variance: bool = False,
|
||||
// default: Any = ...,
|
||||
// ) -> Self: ...
|
||||
// ```
|
||||
Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_or_keyword(Name::new_static("name"))
|
||||
.with_annotated_type(Type::LiteralString),
|
||||
Parameter::variadic(Name::new_static("constraints"))
|
||||
.type_form()
|
||||
.with_annotated_type(Type::any()),
|
||||
Parameter::keyword_only(Name::new_static("bound"))
|
||||
.type_form()
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[Type::any(), Type::none(db)],
|
||||
))
|
||||
.with_default_type(Type::none(db)),
|
||||
Parameter::keyword_only(Name::new_static("default"))
|
||||
.type_form()
|
||||
.with_annotated_type(Type::any())
|
||||
.with_default_type(KnownClass::NoneType.to_instance(db)),
|
||||
Parameter::keyword_only(Name::new_static("contravariant"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("covariant"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
Parameter::keyword_only(Name::new_static("infer_variance"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(false)),
|
||||
]),
|
||||
Some(KnownClass::TypeVar.to_instance(db)),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
Some(KnownClass::Deprecated) => {
|
||||
// ```py
|
||||
// class deprecated:
|
||||
|
|
@ -7832,6 +7784,12 @@ pub struct TypeVarInstance<'db> {
|
|||
_default: Option<TypeVarDefaultEvaluation<'db>>,
|
||||
|
||||
pub kind: TypeVarKind,
|
||||
|
||||
/// If this typevar was transformed from another typevar via `mark_typevars_inferable`, this
|
||||
/// records the identity of the "original" typevar, so we can recognize them as the same
|
||||
/// typevar in `bind_typevar`. TODO: this (and the `is_identical_to` methods) should be
|
||||
/// removable once we remove `mark_typevars_inferable`.
|
||||
pub(crate) original: Option<TypeVarInstance<'db>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
|
|
@ -7942,6 +7900,7 @@ impl<'db> TypeVarInstance<'db> {
|
|||
.map(|ty| ty.normalized_impl(db, visitor).into()),
|
||||
}),
|
||||
self.kind(db),
|
||||
self.original(db),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -7987,6 +7946,7 @@ impl<'db> TypeVarInstance<'db> {
|
|||
.map(|ty| ty.materialize(db, materialization_kind, visitor).into()),
|
||||
}),
|
||||
self.kind(db),
|
||||
self.original(db),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -8000,10 +7960,7 @@ impl<'db> TypeVarInstance<'db> {
|
|||
// inferable, so we set the parameter to `None` here.
|
||||
let type_mapping = &TypeMapping::MarkTypeVarsInferable(None);
|
||||
|
||||
Self::new(
|
||||
db,
|
||||
self.name(db),
|
||||
self.definition(db),
|
||||
let new_bound_or_constraints =
|
||||
self._bound_or_constraints(db)
|
||||
.map(|bound_or_constraints| match bound_or_constraints {
|
||||
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
|
||||
|
|
@ -8013,22 +7970,46 @@ impl<'db> TypeVarInstance<'db> {
|
|||
}
|
||||
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound
|
||||
| TypeVarBoundOrConstraintsEvaluation::LazyConstraints => bound_or_constraints,
|
||||
}),
|
||||
self.explicit_variance(db),
|
||||
self._default(db).and_then(|default| match default {
|
||||
TypeVarDefaultEvaluation::Eager(ty) => Some(
|
||||
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
|
||||
.into(),
|
||||
),
|
||||
TypeVarDefaultEvaluation::Lazy => self.lazy_default(db).map(|ty| {
|
||||
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
|
||||
.into()
|
||||
}),
|
||||
});
|
||||
|
||||
let new_default = self._default(db).and_then(|default| match default {
|
||||
TypeVarDefaultEvaluation::Eager(ty) => Some(
|
||||
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
|
||||
.into(),
|
||||
),
|
||||
TypeVarDefaultEvaluation::Lazy => self.lazy_default(db).map(|ty| {
|
||||
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
|
||||
.into()
|
||||
}),
|
||||
});
|
||||
|
||||
// Ensure that we only modify the `original` field if we are going to modify one or both of
|
||||
// `_bound_or_constraints` and `_default`; don't trigger creation of a new
|
||||
// `TypeVarInstance` unnecessarily.
|
||||
let new_original = if new_bound_or_constraints == self._bound_or_constraints(db)
|
||||
&& new_default == self._default(db)
|
||||
{
|
||||
self.original(db)
|
||||
} else {
|
||||
Some(self)
|
||||
};
|
||||
|
||||
Self::new(
|
||||
db,
|
||||
self.name(db),
|
||||
self.definition(db),
|
||||
new_bound_or_constraints,
|
||||
self.explicit_variance(db),
|
||||
new_default,
|
||||
self.kind(db),
|
||||
new_original,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_identical_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self == other || (self.original(db) == Some(other) || other.original(db) == Some(self))
|
||||
}
|
||||
|
||||
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
|
||||
let bound_or_constraints = match self.bound_or_constraints(db)? {
|
||||
TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
|
||||
|
|
@ -8046,38 +8027,88 @@ impl<'db> TypeVarInstance<'db> {
|
|||
self.explicit_variance(db),
|
||||
None,
|
||||
self.kind(db),
|
||||
self.original(db),
|
||||
))
|
||||
}
|
||||
|
||||
#[salsa::tracked(cycle_fn=lazy_bound_cycle_recover, cycle_initial=lazy_bound_cycle_initial)]
|
||||
#[salsa::tracked(cycle_fn=lazy_bound_cycle_recover, cycle_initial=lazy_bound_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn lazy_bound(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
|
||||
let definition = self.definition(db)?;
|
||||
let module = parsed_module(db, definition.file(db)).load(db);
|
||||
let typevar_node = definition.kind(db).as_typevar()?.node(&module);
|
||||
let ty = definition_expression_type(db, definition, typevar_node.bound.as_ref()?);
|
||||
let ty = match definition.kind(db) {
|
||||
// PEP 695 typevar
|
||||
DefinitionKind::TypeVar(typevar) => {
|
||||
let typevar_node = typevar.node(&module);
|
||||
definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
|
||||
}
|
||||
// legacy typevar
|
||||
DefinitionKind::Assignment(assignment) => {
|
||||
let call_expr = assignment.value(&module).as_call_expr()?;
|
||||
let expr = &call_expr.arguments.find_keyword("bound")?.value;
|
||||
definition_expression_type(db, definition, expr)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(ty))
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
fn lazy_constraints(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
|
||||
let definition = self.definition(db)?;
|
||||
let module = parsed_module(db, definition.file(db)).load(db);
|
||||
let typevar_node = definition.kind(db).as_typevar()?.node(&module);
|
||||
let ty = definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
|
||||
.into_union()?;
|
||||
let ty = match definition.kind(db) {
|
||||
// PEP 695 typevar
|
||||
DefinitionKind::TypeVar(typevar) => {
|
||||
let typevar_node = typevar.node(&module);
|
||||
definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
|
||||
.into_union()?
|
||||
}
|
||||
// legacy typevar
|
||||
DefinitionKind::Assignment(assignment) => {
|
||||
let call_expr = assignment.value(&module).as_call_expr()?;
|
||||
// We don't use `UnionType::from_elements` or `UnionBuilder` here,
|
||||
// because we don't want to simplify the list of constraints as we would with
|
||||
// an actual union type.
|
||||
// TODO: We probably shouldn't use `UnionType` to store these at all? TypeVar
|
||||
// constraints are not a union.
|
||||
UnionType::new(
|
||||
db,
|
||||
call_expr
|
||||
.arguments
|
||||
.args
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|arg| definition_expression_type(db, definition, arg))
|
||||
.collect::<Box<_>>(),
|
||||
)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(TypeVarBoundOrConstraints::Constraints(ty))
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
fn lazy_default(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
let definition = self.definition(db)?;
|
||||
let module = parsed_module(db, definition.file(db)).load(db);
|
||||
let typevar_node = definition.kind(db).as_typevar()?.node(&module);
|
||||
Some(definition_expression_type(
|
||||
db,
|
||||
definition,
|
||||
typevar_node.default.as_ref()?,
|
||||
))
|
||||
match definition.kind(db) {
|
||||
// PEP 695 typevar
|
||||
DefinitionKind::TypeVar(typevar) => {
|
||||
let typevar_node = typevar.node(&module);
|
||||
Some(definition_expression_type(
|
||||
db,
|
||||
definition,
|
||||
typevar_node.default.as_ref()?,
|
||||
))
|
||||
}
|
||||
// legacy typevar
|
||||
DefinitionKind::Assignment(assignment) => {
|
||||
let call_expr = assignment.value(&module).as_call_expr()?;
|
||||
let expr = &call_expr.arguments.find_keyword("default")?.value;
|
||||
Some(definition_expression_type(db, definition, expr))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8153,6 +8184,7 @@ impl<'db> BoundTypeVarInstance<'db> {
|
|||
Some(variance),
|
||||
None, // _default
|
||||
TypeVarKind::Pep695,
|
||||
None,
|
||||
),
|
||||
BindingContext::Synthetic,
|
||||
)
|
||||
|
|
@ -8174,11 +8206,24 @@ impl<'db> BoundTypeVarInstance<'db> {
|
|||
Some(TypeVarVariance::Invariant),
|
||||
None,
|
||||
TypeVarKind::TypingSelf,
|
||||
None,
|
||||
),
|
||||
binding_context,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_identical_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.binding_context(db) != other.binding_context(db) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.typevar(db).is_identical_to(db, other.typevar(db))
|
||||
}
|
||||
|
||||
pub(crate) fn variance_with_polarity(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use crate::semantic_index::{
|
|||
};
|
||||
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||
use crate::types::context::InferContext;
|
||||
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
||||
use crate::types::diagnostic::INVALID_TYPE_ALIAS_TYPE;
|
||||
use crate::types::enums::enum_metadata;
|
||||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||
use crate::types::generics::{GenericContext, Specialization, walk_specialization};
|
||||
|
|
@ -29,9 +29,8 @@ use crate::types::{
|
|||
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||
IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
|
||||
MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
|
||||
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
TypeVarKind, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type,
|
||||
determine_upper_bound, infer_definition_types,
|
||||
TypeContext, TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable,
|
||||
declaration_type, determine_upper_bound, infer_definition_types,
|
||||
};
|
||||
use crate::{
|
||||
Db, FxIndexMap, FxOrderSet, Program,
|
||||
|
|
@ -3761,6 +3760,8 @@ pub enum KnownClass {
|
|||
SupportsIndex,
|
||||
Iterable,
|
||||
Iterator,
|
||||
// typing_extensions
|
||||
ExtensionsTypeVar, // must be distinct from typing.TypeVar, backports new features
|
||||
// Collections
|
||||
ChainMap,
|
||||
Counter,
|
||||
|
|
@ -3815,6 +3816,7 @@ impl KnownClass {
|
|||
| Self::VersionInfo
|
||||
| Self::TypeAliasType
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
|
|
@ -3943,6 +3945,7 @@ impl KnownClass {
|
|||
| KnownClass::StdlibAlias
|
||||
| KnownClass::SpecialForm
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
|
|
@ -4025,6 +4028,7 @@ impl KnownClass {
|
|||
| KnownClass::StdlibAlias
|
||||
| KnownClass::SpecialForm
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
|
|
@ -4107,6 +4111,7 @@ impl KnownClass {
|
|||
| KnownClass::StdlibAlias
|
||||
| KnownClass::SpecialForm
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
|
|
@ -4194,6 +4199,7 @@ impl KnownClass {
|
|||
| Self::NoneType
|
||||
| Self::SpecialForm
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
|
|
@ -4289,6 +4295,7 @@ impl KnownClass {
|
|||
| KnownClass::StdlibAlias
|
||||
| KnownClass::SpecialForm
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
|
|
@ -4358,6 +4365,7 @@ impl KnownClass {
|
|||
Self::NoneType => "NoneType",
|
||||
Self::SpecialForm => "_SpecialForm",
|
||||
Self::TypeVar => "TypeVar",
|
||||
Self::ExtensionsTypeVar => "TypeVar",
|
||||
Self::ParamSpec => "ParamSpec",
|
||||
Self::ParamSpecArgs => "ParamSpecArgs",
|
||||
Self::ParamSpecKwargs => "ParamSpecKwargs",
|
||||
|
|
@ -4656,6 +4664,7 @@ impl KnownClass {
|
|||
| Self::ProtocolMeta
|
||||
| Self::SupportsIndex => KnownModule::Typing,
|
||||
Self::TypeAliasType
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::TypeVarTuple
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
|
|
@ -4752,6 +4761,7 @@ impl KnownClass {
|
|||
| Self::SupportsIndex
|
||||
| Self::StdlibAlias
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
|
|
@ -4838,6 +4848,7 @@ impl KnownClass {
|
|||
| Self::Generator
|
||||
| Self::Deprecated
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
|
|
@ -4875,99 +4886,102 @@ impl KnownClass {
|
|||
) -> Option<Self> {
|
||||
// We assert that this match is exhaustive over the right-hand side in the unit test
|
||||
// `known_class_roundtrip_from_str()`
|
||||
let candidate = match class_name {
|
||||
"bool" => Self::Bool,
|
||||
"object" => Self::Object,
|
||||
"bytes" => Self::Bytes,
|
||||
"bytearray" => Self::Bytearray,
|
||||
"tuple" => Self::Tuple,
|
||||
"type" => Self::Type,
|
||||
"int" => Self::Int,
|
||||
"float" => Self::Float,
|
||||
"complex" => Self::Complex,
|
||||
"str" => Self::Str,
|
||||
"set" => Self::Set,
|
||||
"frozenset" => Self::FrozenSet,
|
||||
"dict" => Self::Dict,
|
||||
"list" => Self::List,
|
||||
"slice" => Self::Slice,
|
||||
"property" => Self::Property,
|
||||
"BaseException" => Self::BaseException,
|
||||
"BaseExceptionGroup" => Self::BaseExceptionGroup,
|
||||
"Exception" => Self::Exception,
|
||||
"ExceptionGroup" => Self::ExceptionGroup,
|
||||
"staticmethod" => Self::Staticmethod,
|
||||
"classmethod" => Self::Classmethod,
|
||||
"Awaitable" => Self::Awaitable,
|
||||
"Generator" => Self::Generator,
|
||||
"deprecated" => Self::Deprecated,
|
||||
"GenericAlias" => Self::GenericAlias,
|
||||
"NoneType" => Self::NoneType,
|
||||
"ModuleType" => Self::ModuleType,
|
||||
"GeneratorType" => Self::GeneratorType,
|
||||
"AsyncGeneratorType" => Self::AsyncGeneratorType,
|
||||
"CoroutineType" => Self::CoroutineType,
|
||||
"FunctionType" => Self::FunctionType,
|
||||
"MethodType" => Self::MethodType,
|
||||
"UnionType" => Self::UnionType,
|
||||
"MethodWrapperType" => Self::MethodWrapperType,
|
||||
"WrapperDescriptorType" => Self::WrapperDescriptorType,
|
||||
"BuiltinFunctionType" => Self::BuiltinFunctionType,
|
||||
"NewType" => Self::NewType,
|
||||
"TypeAliasType" => Self::TypeAliasType,
|
||||
"TypeVar" => Self::TypeVar,
|
||||
"Iterable" => Self::Iterable,
|
||||
"Iterator" => Self::Iterator,
|
||||
"ParamSpec" => Self::ParamSpec,
|
||||
"ParamSpecArgs" => Self::ParamSpecArgs,
|
||||
"ParamSpecKwargs" => Self::ParamSpecKwargs,
|
||||
"TypeVarTuple" => Self::TypeVarTuple,
|
||||
"ChainMap" => Self::ChainMap,
|
||||
"Counter" => Self::Counter,
|
||||
"defaultdict" => Self::DefaultDict,
|
||||
"deque" => Self::Deque,
|
||||
"OrderedDict" => Self::OrderedDict,
|
||||
"_Alias" => Self::StdlibAlias,
|
||||
"_SpecialForm" => Self::SpecialForm,
|
||||
"_NoDefaultType" => Self::NoDefaultType,
|
||||
"SupportsIndex" => Self::SupportsIndex,
|
||||
"Enum" => Self::Enum,
|
||||
"EnumMeta" => Self::EnumType,
|
||||
let candidates: &[Self] = match class_name {
|
||||
"bool" => &[Self::Bool],
|
||||
"object" => &[Self::Object],
|
||||
"bytes" => &[Self::Bytes],
|
||||
"bytearray" => &[Self::Bytearray],
|
||||
"tuple" => &[Self::Tuple],
|
||||
"type" => &[Self::Type],
|
||||
"int" => &[Self::Int],
|
||||
"float" => &[Self::Float],
|
||||
"complex" => &[Self::Complex],
|
||||
"str" => &[Self::Str],
|
||||
"set" => &[Self::Set],
|
||||
"frozenset" => &[Self::FrozenSet],
|
||||
"dict" => &[Self::Dict],
|
||||
"list" => &[Self::List],
|
||||
"slice" => &[Self::Slice],
|
||||
"property" => &[Self::Property],
|
||||
"BaseException" => &[Self::BaseException],
|
||||
"BaseExceptionGroup" => &[Self::BaseExceptionGroup],
|
||||
"Exception" => &[Self::Exception],
|
||||
"ExceptionGroup" => &[Self::ExceptionGroup],
|
||||
"staticmethod" => &[Self::Staticmethod],
|
||||
"classmethod" => &[Self::Classmethod],
|
||||
"Awaitable" => &[Self::Awaitable],
|
||||
"Generator" => &[Self::Generator],
|
||||
"deprecated" => &[Self::Deprecated],
|
||||
"GenericAlias" => &[Self::GenericAlias],
|
||||
"NoneType" => &[Self::NoneType],
|
||||
"ModuleType" => &[Self::ModuleType],
|
||||
"GeneratorType" => &[Self::GeneratorType],
|
||||
"AsyncGeneratorType" => &[Self::AsyncGeneratorType],
|
||||
"CoroutineType" => &[Self::CoroutineType],
|
||||
"FunctionType" => &[Self::FunctionType],
|
||||
"MethodType" => &[Self::MethodType],
|
||||
"UnionType" => &[Self::UnionType],
|
||||
"MethodWrapperType" => &[Self::MethodWrapperType],
|
||||
"WrapperDescriptorType" => &[Self::WrapperDescriptorType],
|
||||
"BuiltinFunctionType" => &[Self::BuiltinFunctionType],
|
||||
"NewType" => &[Self::NewType],
|
||||
"TypeAliasType" => &[Self::TypeAliasType],
|
||||
"TypeVar" => &[Self::TypeVar, Self::ExtensionsTypeVar],
|
||||
"Iterable" => &[Self::Iterable],
|
||||
"Iterator" => &[Self::Iterator],
|
||||
"ParamSpec" => &[Self::ParamSpec],
|
||||
"ParamSpecArgs" => &[Self::ParamSpecArgs],
|
||||
"ParamSpecKwargs" => &[Self::ParamSpecKwargs],
|
||||
"TypeVarTuple" => &[Self::TypeVarTuple],
|
||||
"ChainMap" => &[Self::ChainMap],
|
||||
"Counter" => &[Self::Counter],
|
||||
"defaultdict" => &[Self::DefaultDict],
|
||||
"deque" => &[Self::Deque],
|
||||
"OrderedDict" => &[Self::OrderedDict],
|
||||
"_Alias" => &[Self::StdlibAlias],
|
||||
"_SpecialForm" => &[Self::SpecialForm],
|
||||
"_NoDefaultType" => &[Self::NoDefaultType],
|
||||
"SupportsIndex" => &[Self::SupportsIndex],
|
||||
"Enum" => &[Self::Enum],
|
||||
"EnumMeta" => &[Self::EnumType],
|
||||
"EnumType" if Program::get(db).python_version(db) >= PythonVersion::PY311 => {
|
||||
Self::EnumType
|
||||
&[Self::EnumType]
|
||||
}
|
||||
"StrEnum" if Program::get(db).python_version(db) >= PythonVersion::PY311 => {
|
||||
Self::StrEnum
|
||||
&[Self::StrEnum]
|
||||
}
|
||||
"auto" => Self::Auto,
|
||||
"member" => Self::Member,
|
||||
"nonmember" => Self::Nonmember,
|
||||
"ABCMeta" => Self::ABCMeta,
|
||||
"super" => Self::Super,
|
||||
"_version_info" => Self::VersionInfo,
|
||||
"auto" => &[Self::Auto],
|
||||
"member" => &[Self::Member],
|
||||
"nonmember" => &[Self::Nonmember],
|
||||
"ABCMeta" => &[Self::ABCMeta],
|
||||
"super" => &[Self::Super],
|
||||
"_version_info" => &[Self::VersionInfo],
|
||||
"ellipsis" if Program::get(db).python_version(db) <= PythonVersion::PY39 => {
|
||||
Self::EllipsisType
|
||||
&[Self::EllipsisType]
|
||||
}
|
||||
"EllipsisType" if Program::get(db).python_version(db) >= PythonVersion::PY310 => {
|
||||
Self::EllipsisType
|
||||
&[Self::EllipsisType]
|
||||
}
|
||||
"_NotImplementedType" => Self::NotImplementedType,
|
||||
"Field" => Self::Field,
|
||||
"KW_ONLY" => Self::KwOnly,
|
||||
"InitVar" => Self::InitVar,
|
||||
"NamedTupleFallback" => Self::NamedTupleFallback,
|
||||
"NamedTupleLike" => Self::NamedTupleLike,
|
||||
"ConstraintSet" => Self::ConstraintSet,
|
||||
"TypedDictFallback" => Self::TypedDictFallback,
|
||||
"Template" => Self::Template,
|
||||
"Path" => Self::Path,
|
||||
"_ProtocolMeta" => Self::ProtocolMeta,
|
||||
"_NotImplementedType" => &[Self::NotImplementedType],
|
||||
"Field" => &[Self::Field],
|
||||
"KW_ONLY" => &[Self::KwOnly],
|
||||
"InitVar" => &[Self::InitVar],
|
||||
"NamedTupleFallback" => &[Self::NamedTupleFallback],
|
||||
"NamedTupleLike" => &[Self::NamedTupleLike],
|
||||
"ConstraintSet" => &[Self::ConstraintSet],
|
||||
"TypedDictFallback" => &[Self::TypedDictFallback],
|
||||
"Template" => &[Self::Template],
|
||||
"Path" => &[Self::Path],
|
||||
"_ProtocolMeta" => &[Self::ProtocolMeta],
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
candidate
|
||||
.check_module(db, file_to_module(db, file)?.known(db)?)
|
||||
.then_some(candidate)
|
||||
let module = file_to_module(db, file)?.known(db)?;
|
||||
|
||||
candidates
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|&candidate| candidate.check_module(db, module))
|
||||
}
|
||||
|
||||
/// Return `true` if the module of `self` matches `module`
|
||||
|
|
@ -5028,6 +5042,8 @@ impl KnownClass {
|
|||
| Self::InitVar
|
||||
| Self::NamedTupleFallback
|
||||
| Self::TypedDictFallback
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::NamedTupleLike
|
||||
| Self::ConstraintSet
|
||||
| Self::Awaitable
|
||||
|
|
@ -5036,7 +5052,6 @@ impl KnownClass {
|
|||
| Self::Path => module == self.canonical_module(db),
|
||||
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
||||
Self::SpecialForm
|
||||
| Self::TypeVar
|
||||
| Self::TypeAliasType
|
||||
| Self::NoDefaultType
|
||||
| Self::SupportsIndex
|
||||
|
|
@ -5059,7 +5074,6 @@ impl KnownClass {
|
|||
context: &InferContext<'db, '_>,
|
||||
index: &SemanticIndex<'db>,
|
||||
overload: &mut Binding<'db>,
|
||||
call_arguments: &CallArguments<'_, 'db>,
|
||||
call_expression: &ast::ExprCall,
|
||||
) {
|
||||
let db = context.db();
|
||||
|
|
@ -5132,6 +5146,7 @@ impl KnownClass {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
KnownClass::Deprecated => {
|
||||
// Parsing something of the form:
|
||||
//
|
||||
|
|
@ -5158,153 +5173,6 @@ impl KnownClass {
|
|||
DeprecatedInstance::new(db, message.into_string_literal()),
|
||||
)));
|
||||
}
|
||||
KnownClass::TypeVar => {
|
||||
let assigned_to = index
|
||||
.try_expression(ast::ExprRef::from(call_expression))
|
||||
.and_then(|expr| expr.assigned_to(db));
|
||||
|
||||
let Some(target) = assigned_to.as_ref().and_then(|assigned_to| {
|
||||
match assigned_to.node(module).targets.as_slice() {
|
||||
[ast::Expr::Name(target)] => Some(target),
|
||||
_ => None,
|
||||
}
|
||||
}) else {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A legacy `typing.TypeVar` must be immediately assigned to a variable",
|
||||
);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
let [
|
||||
Some(name_param),
|
||||
constraints,
|
||||
bound,
|
||||
default,
|
||||
contravariant,
|
||||
covariant,
|
||||
_infer_variance,
|
||||
] = overload.parameter_types()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let covariant = covariant
|
||||
.map(|ty| ty.bool(db))
|
||||
.unwrap_or(Truthiness::AlwaysFalse);
|
||||
|
||||
let contravariant = contravariant
|
||||
.map(|ty| ty.bool(db))
|
||||
.unwrap_or(Truthiness::AlwaysFalse);
|
||||
|
||||
let variance = match (contravariant, covariant) {
|
||||
(Truthiness::Ambiguous, _) => {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"The `contravariant` parameter of a legacy `typing.TypeVar` \
|
||||
cannot have an ambiguous value",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
(_, Truthiness::Ambiguous) => {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"The `covariant` parameter of a legacy `typing.TypeVar` \
|
||||
cannot have an ambiguous value",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
(Truthiness::AlwaysTrue, Truthiness::AlwaysTrue) => {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A legacy `typing.TypeVar` cannot be both \
|
||||
covariant and contravariant",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
(Truthiness::AlwaysTrue, Truthiness::AlwaysFalse) => {
|
||||
TypeVarVariance::Contravariant
|
||||
}
|
||||
(Truthiness::AlwaysFalse, Truthiness::AlwaysTrue) => TypeVarVariance::Covariant,
|
||||
(Truthiness::AlwaysFalse, Truthiness::AlwaysFalse) => {
|
||||
TypeVarVariance::Invariant
|
||||
}
|
||||
};
|
||||
|
||||
let name_param = name_param.into_string_literal().map(|name| name.value(db));
|
||||
|
||||
if name_param.is_none_or(|name_param| name_param != target.id) {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The name of a legacy `typing.TypeVar`{} must match \
|
||||
the name of the variable it is assigned to (`{}`)",
|
||||
if let Some(name_param) = name_param {
|
||||
format!(" (`{name_param}`)")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
target.id,
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let bound_or_constraint = match (bound, constraints) {
|
||||
(Some(bound), None) => {
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(*bound).into())
|
||||
}
|
||||
|
||||
(None, Some(_constraints)) => {
|
||||
// We don't use UnionType::from_elements or UnionBuilder here,
|
||||
// because we don't want to simplify the list of constraints like
|
||||
// we do with the elements of an actual union type.
|
||||
// TODO: Consider using a new `OneOfType` connective here instead,
|
||||
// since that more accurately represents the actual semantics of
|
||||
// typevar constraints.
|
||||
let elements = UnionType::new(
|
||||
db,
|
||||
overload
|
||||
.arguments_for_parameter(call_arguments, 1)
|
||||
.map(|(_, ty)| ty)
|
||||
.collect::<Box<_>>(),
|
||||
);
|
||||
Some(TypeVarBoundOrConstraints::Constraints(elements).into())
|
||||
}
|
||||
|
||||
// TODO: Emit a diagnostic that TypeVar cannot be both bounded and
|
||||
// constrained
|
||||
(Some(_), Some(_)) => return,
|
||||
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
let containing_assignment = index.expect_single_definition(target);
|
||||
overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar(
|
||||
TypeVarInstance::new(
|
||||
db,
|
||||
&target.id,
|
||||
Some(containing_assignment),
|
||||
bound_or_constraint,
|
||||
Some(variance),
|
||||
default.map(Into::into),
|
||||
TypeVarKind::Legacy,
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
KnownClass::TypeAliasType => {
|
||||
let assigned_to = index
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ pub(crate) fn typing_self<'db>(
|
|||
Some(TypeVarVariance::Invariant),
|
||||
None,
|
||||
TypeVarKind::TypingSelf,
|
||||
None,
|
||||
);
|
||||
|
||||
bind_typevar(
|
||||
|
|
@ -396,7 +397,7 @@ impl<'db> GenericContext<'db> {
|
|||
typevar: TypeVarInstance<'db>,
|
||||
) -> Option<BoundTypeVarInstance<'db>> {
|
||||
self.variables(db)
|
||||
.find(|self_bound_typevar| self_bound_typevar.typevar(db) == typevar)
|
||||
.find(|self_bound_typevar| self_bound_typevar.typevar(db).is_identical_to(db, typevar))
|
||||
}
|
||||
|
||||
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
||||
|
|
|
|||
|
|
@ -631,7 +631,7 @@ struct DefinitionInferenceExtra<'db> {
|
|||
/// Is this a cycle-recovery inference result, and if so, what kind?
|
||||
cycle_recovery: Option<CycleRecovery<'db>>,
|
||||
|
||||
/// The definitions that are deferred.
|
||||
/// The definitions that have some deferred parts.
|
||||
deferred: Box<[Definition<'db>]>,
|
||||
|
||||
/// The diagnostics for this region.
|
||||
|
|
|
|||
|
|
@ -53,11 +53,12 @@ use crate::types::diagnostic::{
|
|||
CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION,
|
||||
DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE,
|
||||
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
|
||||
INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE, INVALID_PARAMETER_DEFAULT,
|
||||
INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS,
|
||||
IncompatibleBases, NON_SUBSCRIPTABLE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT,
|
||||
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
|
||||
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, report_bad_dunder_set_call,
|
||||
INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_NAMED_TUPLE,
|
||||
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NON_SUBSCRIPTABLE,
|
||||
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, UNDEFINED_REVEAL,
|
||||
UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE,
|
||||
UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, report_bad_dunder_set_call,
|
||||
report_cannot_pop_required_field_on_typed_dict, report_implicit_return_type,
|
||||
report_instance_layout_conflict, report_invalid_assignment,
|
||||
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
||||
|
|
@ -95,7 +96,7 @@ use crate::types::{
|
|||
Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
|
||||
TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
|
||||
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind,
|
||||
TypedDictType, UnionBuilder, UnionType, binding_type, todo_type,
|
||||
TypeVarVariance, TypedDictType, UnionBuilder, UnionType, binding_type, todo_type,
|
||||
};
|
||||
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
|
||||
use crate::unpack::{EvaluationMode, UnpackPosition};
|
||||
|
|
@ -212,9 +213,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
|
|||
/// The list should only contain one entry per declaration at most.
|
||||
declarations: VecMap<Definition<'db>, TypeAndQualifiers<'db>>,
|
||||
|
||||
/// The definitions that are deferred.
|
||||
/// The definitions with deferred sub-parts.
|
||||
///
|
||||
/// The list should only contain one entry per deferred.
|
||||
/// The list should only contain one entry per definition.
|
||||
deferred: VecSet<Definition<'db>>,
|
||||
|
||||
/// The returned types and their corresponding ranges of the region, if it is a function body.
|
||||
|
|
@ -497,8 +498,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
|
||||
// Infer the deferred types for the definitions here to consider the end-of-scope
|
||||
// semantics.
|
||||
// Infer deferred types for all definitions.
|
||||
for definition in std::mem::take(&mut self.deferred) {
|
||||
self.extend_definition(infer_deferred_types(self.db(), definition));
|
||||
}
|
||||
|
|
@ -1245,6 +1245,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
DefinitionKind::TypeVar(typevar) => {
|
||||
self.infer_typevar_deferred(typevar.node(self.module()));
|
||||
}
|
||||
DefinitionKind::Assignment(assignment) => {
|
||||
self.infer_assignment_deferred(assignment.value(self.module()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -2961,6 +2964,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
None,
|
||||
default.as_deref().map(|_| TypeVarDefaultEvaluation::Lazy),
|
||||
TypeVarKind::Pep695,
|
||||
None,
|
||||
)));
|
||||
self.add_declaration_with_binding(
|
||||
node.into(),
|
||||
|
|
@ -3993,7 +3997,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
unpacked.expression_type(target)
|
||||
}
|
||||
TargetKind::Single => {
|
||||
let value_ty = self.infer_standalone_expression(value, TypeContext::default());
|
||||
let tcx = TypeContext::default();
|
||||
let value_ty = if let Some(standalone_expression) = self.index.try_expression(value)
|
||||
{
|
||||
self.infer_standalone_expression_impl(value, standalone_expression, tcx)
|
||||
} else if let ast::Expr::Call(call_expr) = value {
|
||||
// If the RHS is not a standalone expression, this is a simple assignment
|
||||
// (single target, no unpackings). That means it's a valid syntactic form
|
||||
// for a legacy TypeVar creation; check for that.
|
||||
let callable_type = self.infer_maybe_standalone_expression(
|
||||
call_expr.func.as_ref(),
|
||||
TypeContext::default(),
|
||||
);
|
||||
|
||||
let typevar_class = callable_type
|
||||
.into_class_literal()
|
||||
.and_then(|cls| cls.known(self.db()))
|
||||
.filter(|cls| {
|
||||
matches!(cls, KnownClass::TypeVar | KnownClass::ExtensionsTypeVar)
|
||||
});
|
||||
|
||||
let ty = if let Some(typevar_class) = typevar_class {
|
||||
self.infer_legacy_typevar(target, call_expr, definition, typevar_class)
|
||||
} else {
|
||||
self.infer_call_expression_impl(call_expr, callable_type, tcx)
|
||||
};
|
||||
self.store_expression_type(value, ty);
|
||||
ty
|
||||
} else {
|
||||
self.infer_expression(value, tcx)
|
||||
};
|
||||
|
||||
// `TYPE_CHECKING` is a special variable that should only be assigned `False`
|
||||
// at runtime, but is always considered `True` in type checking.
|
||||
|
|
@ -4024,6 +4057,272 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.add_binding(target.into(), definition, target_ty);
|
||||
}
|
||||
|
||||
fn infer_legacy_typevar(
|
||||
&mut self,
|
||||
target: &ast::Expr,
|
||||
call_expr: &ast::ExprCall,
|
||||
definition: Definition<'db>,
|
||||
known_class: KnownClass,
|
||||
) -> Type<'db> {
|
||||
fn error<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
message: impl std::fmt::Display,
|
||||
node: impl Ranged,
|
||||
) -> Type<'db> {
|
||||
if let Some(builder) = context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, node) {
|
||||
builder.into_diagnostic(message);
|
||||
}
|
||||
// If the call doesn't create a valid typevar, we'll emit diagnostics and fall back to
|
||||
// just creating a regular instance of `typing.TypeVar`.
|
||||
KnownClass::TypeVar.to_instance(context.db())
|
||||
}
|
||||
|
||||
let db = self.db();
|
||||
let arguments = &call_expr.arguments;
|
||||
let is_typing_extensions = known_class == KnownClass::ExtensionsTypeVar;
|
||||
let assume_all_features = self.in_stub() || is_typing_extensions;
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let have_features_from =
|
||||
|version: PythonVersion| assume_all_features || python_version >= version;
|
||||
|
||||
let mut has_bound = false;
|
||||
let mut default = None;
|
||||
let mut covariant = false;
|
||||
let mut contravariant = false;
|
||||
let mut name_param_ty = None;
|
||||
|
||||
if let Some(starred) = arguments.args.iter().find(|arg| arg.is_starred_expr()) {
|
||||
return error(
|
||||
&self.context,
|
||||
"Starred arguments are not supported in `TypeVar` creation",
|
||||
starred,
|
||||
);
|
||||
}
|
||||
|
||||
for kwarg in &arguments.keywords {
|
||||
let Some(identifier) = kwarg.arg.as_ref() else {
|
||||
return error(
|
||||
&self.context,
|
||||
"Starred arguments are not supported in `TypeVar` creation",
|
||||
kwarg,
|
||||
);
|
||||
};
|
||||
match identifier.id().as_str() {
|
||||
"name" => {
|
||||
// Duplicate keyword argument is a syntax error, so we don't have to check if
|
||||
// `name_param_ty.is_some()` here.
|
||||
if !arguments.args.is_empty() {
|
||||
return error(
|
||||
&self.context,
|
||||
"The `name` parameter of `TypeVar` can only be provided once.",
|
||||
kwarg,
|
||||
);
|
||||
}
|
||||
name_param_ty =
|
||||
Some(self.infer_expression(&kwarg.value, TypeContext::default()));
|
||||
}
|
||||
"bound" => has_bound = true,
|
||||
"covariant" => {
|
||||
match self
|
||||
.infer_expression(&kwarg.value, TypeContext::default())
|
||||
.bool(db)
|
||||
{
|
||||
Truthiness::AlwaysTrue => covariant = true,
|
||||
Truthiness::AlwaysFalse => {}
|
||||
Truthiness::Ambiguous => {
|
||||
return error(
|
||||
&self.context,
|
||||
"The `covariant` parameter of `TypeVar` \
|
||||
cannot have an ambiguous truthiness",
|
||||
&kwarg.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"contravariant" => {
|
||||
match self
|
||||
.infer_expression(&kwarg.value, TypeContext::default())
|
||||
.bool(db)
|
||||
{
|
||||
Truthiness::AlwaysTrue => contravariant = true,
|
||||
Truthiness::AlwaysFalse => {}
|
||||
Truthiness::Ambiguous => {
|
||||
return error(
|
||||
&self.context,
|
||||
"The `contravariant` parameter of `TypeVar` \
|
||||
cannot have an ambiguous truthiness",
|
||||
&kwarg.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"default" => {
|
||||
if !have_features_from(PythonVersion::PY313) {
|
||||
// We don't return here; this error is informational since this will error
|
||||
// at runtime, but the user's intent is plain, we may as well respect it.
|
||||
error(
|
||||
&self.context,
|
||||
"The `default` parameter of `typing.TypeVar` was added in Python 3.13",
|
||||
kwarg,
|
||||
);
|
||||
}
|
||||
|
||||
default = Some(TypeVarDefaultEvaluation::Lazy);
|
||||
}
|
||||
"infer_variance" => {
|
||||
if !have_features_from(PythonVersion::PY312) {
|
||||
// We don't return here; this error is informational since this will error
|
||||
// at runtime, but the user's intent is plain, we may as well respect it.
|
||||
error(
|
||||
&self.context,
|
||||
"The `infer_variance` parameter of `typing.TypeVar` was added in Python 3.12",
|
||||
kwarg,
|
||||
);
|
||||
}
|
||||
// TODO support `infer_variance` in legacy TypeVars
|
||||
if self
|
||||
.infer_expression(&kwarg.value, TypeContext::default())
|
||||
.bool(db)
|
||||
.is_ambiguous()
|
||||
{
|
||||
return error(
|
||||
&self.context,
|
||||
"The `infer_variance` parameter of `TypeVar` \
|
||||
cannot have an ambiguous truthiness",
|
||||
&kwarg.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
name => {
|
||||
// We don't return here; this error is informational since this will error
|
||||
// at runtime, but it will likely cause fewer cascading errors if we just
|
||||
// ignore the unknown keyword and still understand as much of the typevar as we
|
||||
// can.
|
||||
error(
|
||||
&self.context,
|
||||
format_args!("Unknown keyword argument `{name}` in `TypeVar` creation",),
|
||||
kwarg,
|
||||
);
|
||||
self.infer_expression(&kwarg.value, TypeContext::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let variance = match (covariant, contravariant) {
|
||||
(true, true) => {
|
||||
return error(
|
||||
&self.context,
|
||||
"A `TypeVar` cannot be both covariant and contravariant",
|
||||
call_expr,
|
||||
);
|
||||
}
|
||||
(true, false) => TypeVarVariance::Covariant,
|
||||
(false, true) => TypeVarVariance::Contravariant,
|
||||
(false, false) => TypeVarVariance::Invariant,
|
||||
};
|
||||
|
||||
let Some(name_param_ty) = name_param_ty.or_else(|| {
|
||||
arguments
|
||||
.find_positional(0)
|
||||
.map(|arg| self.infer_expression(arg, TypeContext::default()))
|
||||
}) else {
|
||||
return error(
|
||||
&self.context,
|
||||
"The `name` parameter of `TypeVar` is required.",
|
||||
call_expr,
|
||||
);
|
||||
};
|
||||
|
||||
let Some(name_param) = name_param_ty
|
||||
.into_string_literal()
|
||||
.map(|name| name.value(db))
|
||||
else {
|
||||
return error(
|
||||
&self.context,
|
||||
"The first argument to `TypeVar` must be a string literal.",
|
||||
call_expr,
|
||||
);
|
||||
};
|
||||
|
||||
let ast::Expr::Name(ast::ExprName {
|
||||
id: target_name, ..
|
||||
}) = target
|
||||
else {
|
||||
return error(
|
||||
&self.context,
|
||||
"A `TypeVar` definition must be a simple variable assignment",
|
||||
target,
|
||||
);
|
||||
};
|
||||
|
||||
if name_param != target_name {
|
||||
return error(
|
||||
&self.context,
|
||||
format_args!(
|
||||
"The name of a `TypeVar` (`{name_param}`) must match \
|
||||
the name of the variable it is assigned to (`{target_name}`)"
|
||||
),
|
||||
target,
|
||||
);
|
||||
}
|
||||
|
||||
// Inference of bounds, constraints, and defaults must be deferred, to avoid cycles. So we
|
||||
// only check presence/absence/number here.
|
||||
|
||||
let num_constraints = arguments.args.len().saturating_sub(1);
|
||||
|
||||
let bound_or_constraints = match (has_bound, num_constraints) {
|
||||
(false, 0) => None,
|
||||
(true, 0) => Some(TypeVarBoundOrConstraintsEvaluation::LazyUpperBound),
|
||||
(true, _) => {
|
||||
return error(
|
||||
&self.context,
|
||||
"A `TypeVar` cannot have both a bound and constraints",
|
||||
call_expr,
|
||||
);
|
||||
}
|
||||
(_, 1) => {
|
||||
return error(
|
||||
&self.context,
|
||||
"A `TypeVar` cannot have exactly one constraint",
|
||||
&arguments.args[1],
|
||||
);
|
||||
}
|
||||
(false, _) => Some(TypeVarBoundOrConstraintsEvaluation::LazyConstraints),
|
||||
};
|
||||
|
||||
if bound_or_constraints.is_some() || default.is_some() {
|
||||
self.deferred.insert(definition);
|
||||
}
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new(
|
||||
db,
|
||||
target_name,
|
||||
Some(definition),
|
||||
bound_or_constraints,
|
||||
Some(variance),
|
||||
default,
|
||||
TypeVarKind::Legacy,
|
||||
None,
|
||||
)))
|
||||
}
|
||||
|
||||
fn infer_assignment_deferred(&mut self, value: &ast::Expr) {
|
||||
// Infer deferred bounds/constraints/defaults of a legacy TypeVar.
|
||||
let ast::Expr::Call(ast::ExprCall { arguments, .. }) = value else {
|
||||
return;
|
||||
};
|
||||
for arg in arguments.args.iter().skip(1) {
|
||||
self.infer_type_expression(arg);
|
||||
}
|
||||
if let Some(bound) = arguments.find_keyword("bound") {
|
||||
self.infer_type_expression(&bound.value);
|
||||
}
|
||||
if let Some(default) = arguments.find_keyword("default") {
|
||||
self.infer_type_expression(&default.value);
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_annotated_assignment_statement(&mut self, assignment: &ast::StmtAnnAssign) {
|
||||
if assignment.target.is_name_expr() {
|
||||
self.infer_definition(assignment);
|
||||
|
|
@ -6045,6 +6344,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
&mut self,
|
||||
call_expression: &ast::ExprCall,
|
||||
tcx: TypeContext<'db>,
|
||||
) -> Type<'db> {
|
||||
// TODO: Use the type context for more precise inference.
|
||||
let callable_type =
|
||||
self.infer_maybe_standalone_expression(&call_expression.func, TypeContext::default());
|
||||
|
||||
self.infer_call_expression_impl(call_expression, callable_type, tcx)
|
||||
}
|
||||
|
||||
fn infer_call_expression_impl(
|
||||
&mut self,
|
||||
call_expression: &ast::ExprCall,
|
||||
callable_type: Type<'db>,
|
||||
tcx: TypeContext<'db>,
|
||||
) -> Type<'db> {
|
||||
let ast::ExprCall {
|
||||
range: _,
|
||||
|
|
@ -6065,9 +6377,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
ty
|
||||
});
|
||||
|
||||
// TODO: Use the type context for more precise inference.
|
||||
let callable_type = self.infer_maybe_standalone_expression(func, TypeContext::default());
|
||||
|
||||
// Special handling for `TypedDict` method calls
|
||||
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
|
||||
let value_type = self.expression_type(value);
|
||||
|
|
@ -6171,7 +6480,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| KnownClass::Object
|
||||
| KnownClass::Property
|
||||
| KnownClass::Super
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::TypeAliasType
|
||||
| KnownClass::Deprecated
|
||||
)
|
||||
|
|
@ -6194,6 +6502,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
|
||||
self.infer_argument_types(arguments, &mut call_arguments, &argument_forms);
|
||||
|
||||
if matches!(
|
||||
class.known(self.db()),
|
||||
Some(KnownClass::TypeVar | KnownClass::ExtensionsTypeVar)
|
||||
) {
|
||||
// Inference of correctly-placed `TypeVar` definitions is done in
|
||||
// `TypeInferenceBuilder::infer_legacy_typevar`, and doesn't use the full
|
||||
// call-binding machinery. If we reach here, it means that someone is trying to
|
||||
// instantiate a `typing.TypeVar` in an invalid context.
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
|
||||
{
|
||||
builder.into_diagnostic(
|
||||
"A `TypeVar` definition must be a simple variable assignment",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return callable_type
|
||||
.try_call_constructor(self.db(), call_arguments, tcx)
|
||||
.unwrap_or_else(|err| {
|
||||
|
|
@ -6253,7 +6579,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
&self.context,
|
||||
self.index,
|
||||
overload,
|
||||
&call_arguments,
|
||||
call_expression,
|
||||
);
|
||||
}
|
||||
|
|
@ -9353,7 +9678,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
);
|
||||
assert!(
|
||||
deferred.is_empty(),
|
||||
"Expression region can't have deferred types"
|
||||
"Expression region can't have deferred definitions"
|
||||
);
|
||||
|
||||
let extra =
|
||||
|
|
|
|||
|
|
@ -418,7 +418,8 @@ fn dependency_implicit_instance_attribute() -> anyhow::Result<()> {
|
|||
"/src/main.py",
|
||||
r#"
|
||||
from mod import C
|
||||
x = C().attr
|
||||
# multiple targets ensures RHS is a standalone expression, relied on by this test
|
||||
x = y = C().attr
|
||||
"#,
|
||||
)?;
|
||||
|
||||
|
|
@ -508,7 +509,8 @@ fn dependency_own_instance_member() -> anyhow::Result<()> {
|
|||
"/src/main.py",
|
||||
r#"
|
||||
from mod import C
|
||||
x = C().attr
|
||||
# multiple targets ensures RHS is a standalone expression, relied on by this test
|
||||
x = y = C().attr
|
||||
"#,
|
||||
)?;
|
||||
|
||||
|
|
@ -603,7 +605,8 @@ fn dependency_implicit_class_member() -> anyhow::Result<()> {
|
|||
r#"
|
||||
from mod import C
|
||||
C.method()
|
||||
x = C().class_attr
|
||||
# multiple targets ensures RHS is a standalone expression, relied on by this test
|
||||
x = y = C().class_attr
|
||||
"#,
|
||||
)?;
|
||||
|
||||
|
|
@ -688,7 +691,8 @@ fn call_type_doesnt_rerun_when_only_callee_changed() -> anyhow::Result<()> {
|
|||
r#"
|
||||
from foo import foo
|
||||
|
||||
a = foo()
|
||||
# multiple targets ensures RHS is a standalone expression, relied on by this test
|
||||
a = b = foo()
|
||||
"#,
|
||||
)?;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue