mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 15:14:42 +00:00
[ty] fix deferred name loading in PEP695 generic classes/functions (#19888)
## Summary For PEP 695 generic functions and classes, there is an extra "type params scope" (a child of the outer scope, and wrapping the body scope) in which the type parameters are defined; class bases and function parameter/return annotations are resolved in that type-params scope. This PR fixes some longstanding bugs in how we resolve name loads from inside these PEP 695 type parameter scopes, and also defers type inference of PEP 695 typevar bounds/constraints/default, so we can handle cycles without panicking. We were previously treating these type-param scopes as lazy nested scopes, which is wrong. In fact they are eager nested scopes; the class `C` here inherits `int`, not `str`, and previously we got that wrong: ```py Base = int class C[T](Base): ... Base = str ``` But certain syntactic positions within type param scopes (typevar bounds/constraints/defaults) are lazy at runtime, and we should use deferred name resolution for them. This also means they can have cycles; in order to handle that without panicking in type inference, we need to actually defer their type inference until after we have constructed the `TypeVarInstance`. PEP 695 does specify that typevar bounds and constraints cannot be generic, and that typevar defaults can only reference prior typevars, not later ones. This reduces the scope of (valid from the type-system perspective) cycles somewhat, although cycles are still possible (e.g. `class C[T: list[C]]`). And this is a type-system-only restriction; from the runtime perspective an "invalid" case like `class C[T: T]` actually works fine. I debated whether to implement the PEP 695 restrictions as a way to avoid some cycles up-front, but I ended up deciding against that; I'd rather model the runtime name-resolution semantics accurately, and implement the PEP 695 restrictions as a separate diagnostic on top. (This PR doesn't yet implement those diagnostics, thus some `# TODO: error` in the added tests.) Introducing the possibility of cyclic typevars made typevar display potentially stack overflow. For now I've handled this by simply removing typevar details (bounds/constraints/default) from typevar display. This impacts display of two kinds of types. If you `reveal_type(T)` on an unbound `T` you now get just `typing.TypeVar` instead of `typing.TypeVar("T", ...)` where `...` is the bound/constraints/default. This matches pyright and mypy; pyrefly uses `type[TypeVar[T]]` which seems a bit confusing, but does include the name. (We could easily include the name without cycle issues, if there's a syntax we like for that.) It also means that displaying a generic function type like `def f[T: int](x: T) -> T: ...` now displays as `f[T](x: T) -> T` instead of `f[T: int](x: T) -> T`. This matches pyright and pyrefly; mypy does include bound/constraints/defaults of typevars in function/callable type display. If we wanted to add this, we would either need to thread a visitor through all the type display code, or add a `decycle` type transformation that replaced recursive reoccurrence of a type with a marker. ## Test Plan Added mdtests and modified existing tests to improve their correctness. After this PR, there's only a single remaining py-fuzzer seed in the 0-500 range that panics! (Before this PR, there were 10; the fuzzer likes to generate cyclic PEP 695 syntax.) ## Ecosystem report It's all just the changes to `TypeVar` display.
This commit is contained in:
parent
baadb5a78d
commit
5a570c8e6d
22 changed files with 429 additions and 239 deletions
|
@ -728,11 +728,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: This should render T@Alias once we create GenericContexts for type alias scopes.
|
// TODO: This should render T@Alias once we create GenericContexts for type alias scopes.
|
||||||
assert_snapshot!(test.hover(), @r#"
|
assert_snapshot!(test.hover(), @r###"
|
||||||
typing.TypeVar("T", bound=int, default=bool)
|
typing.TypeVar
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
```python
|
```python
|
||||||
typing.TypeVar("T", bound=int, default=bool)
|
typing.TypeVar
|
||||||
```
|
```
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[hover]: Hovered content is
|
info[hover]: Hovered content is
|
||||||
|
@ -743,7 +743,7 @@ mod tests {
|
||||||
| |
|
| |
|
||||||
| source
|
| source
|
||||||
|
|
|
|
||||||
"#);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -793,17 +793,17 @@ mod tests {
|
||||||
identity('hello')",
|
identity('hello')",
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.inlay_hints(), @r#"
|
assert_snapshot!(test.inlay_hints(), @r###"
|
||||||
from typing import TypeVar, Generic
|
from typing import TypeVar, Generic
|
||||||
|
|
||||||
T[: typing.TypeVar("T")] = TypeVar([name=]'T')
|
T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||||
|
|
||||||
def identity(x: T) -> T:
|
def identity(x: T) -> T:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
identity([x=]42)
|
identity([x=]42)
|
||||||
identity([x=]'hello')
|
identity([x=]'hello')
|
||||||
"#);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
def name_1[name_0: name_0](name_2: name_0):
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except name_2:
|
||||||
|
pass
|
|
@ -1,9 +1,3 @@
|
||||||
def name_1[name_0: name_0](name_2: name_0):
|
|
||||||
try:
|
|
||||||
pass
|
|
||||||
except name_2:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
def name_2[T: Any](x: T):
|
def name_2[T: Any](x: T):
|
||||||
|
|
|
@ -20,7 +20,7 @@ from typing import TypeVar
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T")
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ from typing import TypeVar
|
||||||
|
|
||||||
T = TypeVar("T", default=int)
|
T = TypeVar("T", default=int)
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T", default=int)
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__default__) # revealed: int
|
reveal_type(T.__default__) # revealed: int
|
||||||
reveal_type(T.__bound__) # revealed: None
|
reveal_type(T.__bound__) # revealed: None
|
||||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||||
|
@ -116,7 +116,7 @@ from typing import TypeVar
|
||||||
|
|
||||||
T = TypeVar("T", bound=int)
|
T = TypeVar("T", bound=int)
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T", bound=int)
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__bound__) # revealed: int
|
reveal_type(T.__bound__) # revealed: int
|
||||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ from typing import TypeVar
|
||||||
|
|
||||||
T = TypeVar("T", int, str)
|
T = TypeVar("T", int, str)
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T", int, str)
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
||||||
|
|
||||||
S = TypeVar("S")
|
S = TypeVar("S")
|
||||||
|
|
|
@ -391,6 +391,7 @@ wrong_innards: C[int] = C("five", 1)
|
||||||
### Some `__init__` overloads only apply to certain specializations
|
### Some `__init__` overloads only apply to certain specializations
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
from __future__ import annotations
|
||||||
from typing import overload
|
from typing import overload
|
||||||
|
|
||||||
class C[T]:
|
class C[T]:
|
||||||
|
@ -541,6 +542,23 @@ class WithOverloadedMethod[T]:
|
||||||
reveal_type(WithOverloadedMethod[int].method)
|
reveal_type(WithOverloadedMethod[int].method)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Scoping of typevars
|
||||||
|
|
||||||
|
### No back-references
|
||||||
|
|
||||||
|
Typevar bounds/constraints/defaults are lazy, but cannot refer to later typevars:
|
||||||
|
|
||||||
|
```py
|
||||||
|
# TODO error
|
||||||
|
class C[S: T, T]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class D[S: X]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
X = int
|
||||||
|
```
|
||||||
|
|
||||||
## Cyclic class definitions
|
## Cyclic class definitions
|
||||||
|
|
||||||
### F-bounded quantification
|
### F-bounded quantification
|
||||||
|
@ -591,7 +609,7 @@ class Derived[T](list[Derived[T]]): ...
|
||||||
|
|
||||||
Inheritance that would result in a cyclic MRO is detected as an error.
|
Inheritance that would result in a cyclic MRO is detected as an error.
|
||||||
|
|
||||||
```py
|
```pyi
|
||||||
# error: [cyclic-class-definition]
|
# error: [cyclic-class-definition]
|
||||||
class C[T](C): ...
|
class C[T](C): ...
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[environment]
|
[environment]
|
||||||
python-version = "3.12"
|
python-version = "3.13"
|
||||||
```
|
```
|
||||||
|
|
||||||
[PEP 695] and Python 3.12 introduced new, more ergonomic syntax for type variables.
|
[PEP 695] and Python 3.12 introduced new, more ergonomic syntax for type variables.
|
||||||
|
@ -17,7 +17,7 @@ instances of `typing.TypeVar`, just like legacy type variables.
|
||||||
```py
|
```py
|
||||||
def f[T]():
|
def f[T]():
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T")
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ python-version = "3.13"
|
||||||
```py
|
```py
|
||||||
def f[T = int]():
|
def f[T = int]():
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T", default=int)
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__default__) # revealed: int
|
reveal_type(T.__default__) # revealed: int
|
||||||
reveal_type(T.__bound__) # revealed: None
|
reveal_type(T.__bound__) # revealed: None
|
||||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||||
|
@ -66,7 +66,7 @@ class Invalid[S = T]: ...
|
||||||
```py
|
```py
|
||||||
def f[T: int]():
|
def f[T: int]():
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T", bound=int)
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__bound__) # revealed: int
|
reveal_type(T.__bound__) # revealed: int
|
||||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ def g[S]():
|
||||||
```py
|
```py
|
||||||
def f[T: (int, str)]():
|
def f[T: (int, str)]():
|
||||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||||
reveal_type(T) # revealed: typing.TypeVar("T", int, str)
|
reveal_type(T) # revealed: typing.TypeVar
|
||||||
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
||||||
reveal_type(T.__bound__) # revealed: None
|
reveal_type(T.__bound__) # revealed: None
|
||||||
|
|
||||||
|
@ -745,4 +745,88 @@ def constrained[T: (int, str)](x: T):
|
||||||
reveal_type(type(x)) # revealed: type[int] | type[str]
|
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
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
def f[S, T: list[S]](x: S, y: T) -> S | T:
|
||||||
|
return x or y
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
class C[S, T: list[S]]:
|
||||||
|
x: S
|
||||||
|
y: T
|
||||||
|
|
||||||
|
reveal_type(C[int, list[Any]]().x) # revealed: int
|
||||||
|
reveal_type(C[int, list[Any]]().y) # revealed: list[Any]
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
def g[T: list[T]](x: T) -> T:
|
||||||
|
return x
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
class D[T: list[T]]:
|
||||||
|
x: T
|
||||||
|
|
||||||
|
reveal_type(D[list[Any]]().x) # revealed: list[Any]
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
def h[S, T: (list[S], str)](x: S, y: T) -> S | T:
|
||||||
|
return x or y
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
class E[S, T: (list[S], str)]:
|
||||||
|
x: S
|
||||||
|
y: T
|
||||||
|
|
||||||
|
reveal_type(E[int, str]().x) # revealed: int
|
||||||
|
reveal_type(E[int, str]().y) # revealed: str
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
def i[T: (list[T], str)](x: T) -> T:
|
||||||
|
return x
|
||||||
|
|
||||||
|
# TODO: error
|
||||||
|
class F[T: (list[T], str)]:
|
||||||
|
x: T
|
||||||
|
|
||||||
|
reveal_type(F[list[Any]]().x) # revealed: list[Any]
|
||||||
|
```
|
||||||
|
|
||||||
|
However, they are lazily evaluated and can cyclically refer to their own type:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class G[T: list[G]]:
|
||||||
|
x: T
|
||||||
|
|
||||||
|
reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Defaults
|
||||||
|
|
||||||
|
Defaults can be generic, but can only refer to earlier typevars:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class C[T, U = T]:
|
||||||
|
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
|
||||||
|
class D[T = T]:
|
||||||
|
x: T
|
||||||
|
|
||||||
|
reveal_type(D().x) # revealed: T@D
|
||||||
|
```
|
||||||
|
|
||||||
[pep 695]: https://peps.python.org/pep-0695/
|
[pep 695]: https://peps.python.org/pep-0695/
|
||||||
|
|
|
@ -2051,6 +2051,7 @@ python-version = "3.12"
|
||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
from __future__ import annotations
|
||||||
from typing import cast, Protocol
|
from typing import cast, Protocol
|
||||||
|
|
||||||
class Iterator[T](Protocol):
|
class Iterator[T](Protocol):
|
||||||
|
|
|
@ -410,4 +410,47 @@ reveal_type(C.var) # revealed: int | str
|
||||||
x = str
|
x = str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Annotation scopes
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Type alias annotation scopes are lazy
|
||||||
|
|
||||||
|
```py
|
||||||
|
type Foo = Bar
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _(x: Foo):
|
||||||
|
if isinstance(x, Bar):
|
||||||
|
reveal_type(x) # revealed: Bar
|
||||||
|
else:
|
||||||
|
reveal_type(x) # revealed: Never
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Type-param scopes are eager, but bounds/constraints are deferred
|
||||||
|
|
||||||
|
```py
|
||||||
|
# error: [unresolved-reference]
|
||||||
|
class D[T](Bar):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class E[T: Bar]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# error: [unresolved-reference]
|
||||||
|
def g[T](x: Bar):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def h[T: Bar](x: T):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
[generators]: https://docs.python.org/3/reference/expressions.html#generator-expressions
|
[generators]: https://docs.python.org/3/reference/expressions.html#generator-expressions
|
||||||
|
|
|
@ -86,7 +86,7 @@ error[call-non-callable]: Object of type `Literal[5]` is not callable
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
|
|
||||||
info: Union variant `Literal[5]` is incompatible with this call site
|
info: Union variant `Literal[5]` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `call-non-callable` is enabled by default
|
info: rule `call-non-callable` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -101,7 +101,7 @@ error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
|
|
||||||
info: Union variant `PossiblyNotCallable` is incompatible with this call site
|
info: Union variant `PossiblyNotCallable` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `call-non-callable` is enabled by default
|
info: rule `call-non-callable` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -116,7 +116,7 @@ error[missing-argument]: No argument provided for required parameter `b` of func
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
|
|
||||||
info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site
|
info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `missing-argument` is enabled by default
|
info: rule `missing-argument` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -152,7 +152,7 @@ info: Overload implementation defined here
|
||||||
28 | return x + y if x and y else None
|
28 | return x + y if x and y else None
|
||||||
|
|
|
|
||||||
info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site
|
info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `no-matching-overload` is enabled by default
|
info: rule `no-matching-overload` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -176,7 +176,7 @@ info: Function defined here
|
||||||
8 | return 0
|
8 | return 0
|
||||||
|
|
|
|
||||||
info: Union variant `def f2(name: str) -> int` is incompatible with this call site
|
info: Union variant `def f2(name: str) -> int` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `invalid-argument-type` is enabled by default
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -199,8 +199,8 @@ info: Type variable defined here
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
14 | return 0
|
14 | return 0
|
||||||
|
|
|
|
||||||
info: Union variant `def f4[T: str](x: T@f4) -> int` is incompatible with this call site
|
info: Union variant `def f4[T](x: T@f4) -> int` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `invalid-argument-type` is enabled by default
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -227,7 +227,7 @@ info: Matching overload defined here
|
||||||
info: Non-matching overloads for function `f5`:
|
info: Non-matching overloads for function `f5`:
|
||||||
info: () -> None
|
info: () -> None
|
||||||
info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site
|
info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `invalid-argument-type` is enabled by default
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -242,7 +242,7 @@ error[too-many-positional-arguments]: Too many positional arguments to function
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
info: Union variant `def f1() -> int` is incompatible with this call site
|
info: Union variant `def f1() -> int` is incompatible with this call site
|
||||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T: str](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||||
info: rule `too-many-positional-arguments` is enabled by default
|
info: rule `too-many-positional-arguments` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -167,7 +167,7 @@ pub(crate) fn attribute_scopes<'db, 's>(
|
||||||
|
|
||||||
ChildrenIter::new(index, class_scope_id).filter_map(move |(child_scope_id, scope)| {
|
ChildrenIter::new(index, class_scope_id).filter_map(move |(child_scope_id, scope)| {
|
||||||
let (function_scope_id, function_scope) =
|
let (function_scope_id, function_scope) =
|
||||||
if scope.node().scope_kind() == ScopeKind::Annotation {
|
if scope.node().scope_kind() == ScopeKind::TypeParams {
|
||||||
// This could be a generic method with a type-params scope.
|
// This could be a generic method with a type-params scope.
|
||||||
// Go one level deeper to find the function scope. The first
|
// Go one level deeper to find the function scope. The first
|
||||||
// descendant is the (potential) function scope.
|
// descendant is the (potential) function scope.
|
||||||
|
@ -597,8 +597,8 @@ impl<'a> Iterator for VisibleAncestorsIter<'a> {
|
||||||
// Skip class scopes for subsequent scopes (following Python's lexical scoping rules)
|
// Skip class scopes for subsequent scopes (following Python's lexical scoping rules)
|
||||||
// Exception: type parameter scopes can see names defined in an immediately-enclosing class scope
|
// Exception: type parameter scopes can see names defined in an immediately-enclosing class scope
|
||||||
if scope.kind() == ScopeKind::Class {
|
if scope.kind() == ScopeKind::Class {
|
||||||
// Allow type parameter scopes to see their immediately-enclosing class scope exactly once
|
// Allow annotation scopes to see their immediately-enclosing class scope exactly once
|
||||||
if self.starting_scope_kind.is_type_parameter() && self.yielded_count == 2 {
|
if self.starting_scope_kind.is_annotation() && self.yielded_count == 2 {
|
||||||
return Some((scope_id, scope));
|
return Some((scope_id, scope));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -1317,7 +1317,7 @@ def func[T]():
|
||||||
panic!("expected one child scope");
|
panic!("expected one child scope");
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(ann_scope.kind(), ScopeKind::Annotation);
|
assert_eq!(ann_scope.kind(), ScopeKind::TypeParams);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ann_scope_id.to_scope_id(&db, file).name(&db, &module),
|
ann_scope_id.to_scope_id(&db, file).name(&db, &module),
|
||||||
"func"
|
"func"
|
||||||
|
@ -1361,7 +1361,7 @@ class C[T]:
|
||||||
panic!("expected one child scope");
|
panic!("expected one child scope");
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(ann_scope.kind(), ScopeKind::Annotation);
|
assert_eq!(ann_scope.kind(), ScopeKind::TypeParams);
|
||||||
assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db, &module), "C");
|
assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db, &module), "C");
|
||||||
let ann_table = index.place_table(ann_scope_id);
|
let ann_table = index.place_table(ann_scope_id);
|
||||||
assert_eq!(names(&ann_table), vec!["T"]);
|
assert_eq!(names(&ann_table), vec!["T"]);
|
||||||
|
|
|
@ -199,7 +199,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||||
|
|
||||||
match self.scopes[parent.file_scope_id].kind() {
|
match self.scopes[parent.file_scope_id].kind() {
|
||||||
ScopeKind::Class => Some(parent.file_scope_id),
|
ScopeKind::Class => Some(parent.file_scope_id),
|
||||||
ScopeKind::Annotation => {
|
ScopeKind::TypeParams => {
|
||||||
// If the function is generic, the parent scope is an annotation scope.
|
// If the function is generic, the parent scope is an annotation scope.
|
||||||
// In this case, we need to go up one level higher to find the class scope.
|
// In this case, we need to go up one level higher to find the class scope.
|
||||||
let grandparent = scopes_rev.next()?;
|
let grandparent = scopes_rev.next()?;
|
||||||
|
@ -2637,7 +2637,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||||
ScopeKind::Comprehension
|
ScopeKind::Comprehension
|
||||||
| ScopeKind::Module
|
| ScopeKind::Module
|
||||||
| ScopeKind::TypeAlias
|
| ScopeKind::TypeAlias
|
||||||
| ScopeKind::Annotation => {}
|
| ScopeKind::TypeParams => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
@ -2652,7 +2652,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||||
ScopeKind::Comprehension
|
ScopeKind::Comprehension
|
||||||
| ScopeKind::Module
|
| ScopeKind::Module
|
||||||
| ScopeKind::TypeAlias
|
| ScopeKind::TypeAlias
|
||||||
| ScopeKind::Annotation => {}
|
| ScopeKind::TypeParams => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
@ -2664,7 +2664,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||||
match scope.kind() {
|
match scope.kind() {
|
||||||
ScopeKind::Class | ScopeKind::Comprehension => return false,
|
ScopeKind::Class | ScopeKind::Comprehension => return false,
|
||||||
ScopeKind::Function | ScopeKind::Lambda => return true,
|
ScopeKind::Function | ScopeKind::Lambda => return true,
|
||||||
ScopeKind::Module | ScopeKind::TypeAlias | ScopeKind::Annotation => {}
|
ScopeKind::Module | ScopeKind::TypeAlias | ScopeKind::TypeParams => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
|
|
@ -702,6 +702,13 @@ impl DefinitionKind<'_> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// Returns the [`TextRange`] of the definition target.
|
||||||
///
|
///
|
||||||
/// A definition target would mainly be the node representing the place being defined i.e.,
|
/// A definition target would mainly be the node representing the place being defined i.e.,
|
||||||
|
|
|
@ -29,8 +29,8 @@ impl<'db> ScopeId<'db> {
|
||||||
self.node(db).scope_kind().is_function_like()
|
self.node(db).scope_kind().is_function_like()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_annotation(self, db: &'db dyn Db) -> bool {
|
||||||
self.node(db).scope_kind().is_type_parameter()
|
self.node(db).scope_kind().is_annotation()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind {
|
pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind {
|
||||||
|
@ -200,7 +200,7 @@ impl ScopeLaziness {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub(crate) enum ScopeKind {
|
pub(crate) enum ScopeKind {
|
||||||
Module,
|
Module,
|
||||||
Annotation,
|
TypeParams,
|
||||||
Class,
|
Class,
|
||||||
Function,
|
Function,
|
||||||
Lambda,
|
Lambda,
|
||||||
|
@ -215,18 +215,18 @@ impl ScopeKind {
|
||||||
|
|
||||||
pub(crate) const fn laziness(self) -> ScopeLaziness {
|
pub(crate) const fn laziness(self) -> ScopeLaziness {
|
||||||
match self {
|
match self {
|
||||||
ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => ScopeLaziness::Eager,
|
ScopeKind::Module
|
||||||
ScopeKind::Annotation
|
| ScopeKind::Class
|
||||||
| ScopeKind::Function
|
| ScopeKind::Comprehension
|
||||||
| ScopeKind::Lambda
|
| ScopeKind::TypeParams => ScopeLaziness::Eager,
|
||||||
| ScopeKind::TypeAlias => ScopeLaziness::Lazy,
|
ScopeKind::Function | ScopeKind::Lambda | ScopeKind::TypeAlias => ScopeLaziness::Lazy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn visibility(self) -> ScopeVisibility {
|
pub(crate) const fn visibility(self) -> ScopeVisibility {
|
||||||
match self {
|
match self {
|
||||||
ScopeKind::Module | ScopeKind::Class => ScopeVisibility::Public,
|
ScopeKind::Module | ScopeKind::Class => ScopeVisibility::Public,
|
||||||
ScopeKind::Annotation
|
ScopeKind::TypeParams
|
||||||
| ScopeKind::TypeAlias
|
| ScopeKind::TypeAlias
|
||||||
| ScopeKind::Function
|
| ScopeKind::Function
|
||||||
| ScopeKind::Lambda
|
| ScopeKind::Lambda
|
||||||
|
@ -239,7 +239,7 @@ impl ScopeKind {
|
||||||
// symbol table also uses the term "function-like" for these scopes.
|
// symbol table also uses the term "function-like" for these scopes.
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
ScopeKind::Annotation
|
ScopeKind::TypeParams
|
||||||
| ScopeKind::Function
|
| ScopeKind::Function
|
||||||
| ScopeKind::Lambda
|
| ScopeKind::Lambda
|
||||||
| ScopeKind::TypeAlias
|
| ScopeKind::TypeAlias
|
||||||
|
@ -255,8 +255,8 @@ impl ScopeKind {
|
||||||
matches!(self, ScopeKind::Module)
|
matches!(self, ScopeKind::Module)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn is_type_parameter(self) -> bool {
|
pub(crate) const fn is_annotation(self) -> bool {
|
||||||
matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias)
|
matches!(self, ScopeKind::TypeParams | ScopeKind::TypeAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn is_non_lambda_function(self) -> bool {
|
pub(crate) const fn is_non_lambda_function(self) -> bool {
|
||||||
|
@ -388,7 +388,7 @@ impl NodeWithScopeKind {
|
||||||
Self::Lambda(_) => ScopeKind::Lambda,
|
Self::Lambda(_) => ScopeKind::Lambda,
|
||||||
Self::FunctionTypeParameters(_)
|
Self::FunctionTypeParameters(_)
|
||||||
| Self::ClassTypeParameters(_)
|
| Self::ClassTypeParameters(_)
|
||||||
| Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation,
|
| Self::TypeAliasTypeParameters(_) => ScopeKind::TypeParams,
|
||||||
Self::TypeAlias(_) => ScopeKind::TypeAlias,
|
Self::TypeAlias(_) => ScopeKind::TypeAlias,
|
||||||
Self::ListComprehension(_)
|
Self::ListComprehension(_)
|
||||||
| Self::SetComprehension(_)
|
| Self::SetComprehension(_)
|
||||||
|
|
|
@ -5291,7 +5291,7 @@ impl<'db> Type<'db> {
|
||||||
db,
|
db,
|
||||||
Name::new(format!("{}'instance", typevar.name(db))),
|
Name::new(format!("{}'instance", typevar.name(db))),
|
||||||
None,
|
None,
|
||||||
Some(bound_or_constraints),
|
Some(bound_or_constraints.into()),
|
||||||
typevar.variance(db),
|
typevar.variance(db),
|
||||||
None,
|
None,
|
||||||
typevar.kind(db),
|
typevar.kind(db),
|
||||||
|
@ -5482,7 +5482,7 @@ impl<'db> Type<'db> {
|
||||||
db,
|
db,
|
||||||
ast::name::Name::new_static("Self"),
|
ast::name::Name::new_static("Self"),
|
||||||
Some(class_definition),
|
Some(class_definition),
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(instance)),
|
Some(TypeVarBoundOrConstraints::UpperBound(instance).into()),
|
||||||
TypeVarVariance::Invariant,
|
TypeVarVariance::Invariant,
|
||||||
None,
|
None,
|
||||||
TypeVarKind::Implicit,
|
TypeVarKind::Implicit,
|
||||||
|
@ -6439,9 +6439,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
|
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
|
||||||
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
|
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
|
||||||
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
|
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
|
||||||
KnownInstanceType::TypeVar(typevar) => {
|
KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"),
|
||||||
write!(f, "typing.TypeVar({})", typevar.display(self.db))
|
|
||||||
}
|
|
||||||
KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"),
|
KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"),
|
||||||
KnownInstanceType::Field(field) => {
|
KnownInstanceType::Field(field) => {
|
||||||
f.write_str("dataclasses.Field[")?;
|
f.write_str("dataclasses.Field[")?;
|
||||||
|
@ -6862,14 +6860,17 @@ pub struct TypeVarInstance<'db> {
|
||||||
/// The type var's definition (None if synthesized)
|
/// The type var's definition (None if synthesized)
|
||||||
pub definition: Option<Definition<'db>>,
|
pub definition: Option<Definition<'db>>,
|
||||||
|
|
||||||
/// The upper bound or constraint on the type of this TypeVar
|
/// The upper bound or constraint on the type of this TypeVar, if any. Don't use this field
|
||||||
bound_or_constraints: Option<TypeVarBoundOrConstraints<'db>>,
|
/// directly; use the `bound_or_constraints` (or `upper_bound` and `constraints`) methods
|
||||||
|
/// instead (to evaluate any lazy bound or constraints).
|
||||||
|
_bound_or_constraints: Option<TypeVarBoundOrConstraintsEvaluation<'db>>,
|
||||||
|
|
||||||
/// The variance of the TypeVar
|
/// The variance of the TypeVar
|
||||||
variance: TypeVarVariance,
|
variance: TypeVarVariance,
|
||||||
|
|
||||||
/// The default type for this TypeVar
|
/// The default type for this TypeVar, if any. Don't use this field directly, use the
|
||||||
default_ty: Option<Type<'db>>,
|
/// `default_type` method instead (to evaluate any lazy default).
|
||||||
|
_default: Option<TypeVarDefaultEvaluation<'db>>,
|
||||||
|
|
||||||
pub kind: TypeVarKind,
|
pub kind: TypeVarKind,
|
||||||
}
|
}
|
||||||
|
@ -6885,11 +6886,12 @@ fn walk_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||||
if let Some(bounds) = typevar.bound_or_constraints(db) {
|
if let Some(bounds) = typevar.bound_or_constraints(db) {
|
||||||
walk_type_var_bounds(db, bounds, visitor);
|
walk_type_var_bounds(db, bounds, visitor);
|
||||||
}
|
}
|
||||||
if let Some(default_type) = typevar.default_ty(db) {
|
if let Some(default_type) = typevar.default_type(db) {
|
||||||
visitor.visit_type(db, default_type);
|
visitor.visit_type(db, default_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked]
|
||||||
impl<'db> TypeVarInstance<'db> {
|
impl<'db> TypeVarInstance<'db> {
|
||||||
pub(crate) fn with_binding_context(
|
pub(crate) fn with_binding_context(
|
||||||
self,
|
self,
|
||||||
|
@ -6919,15 +6921,50 @@ impl<'db> TypeVarInstance<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bound_or_constraints(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
) -> Option<TypeVarBoundOrConstraints<'db>> {
|
||||||
|
self._bound_or_constraints(db).and_then(|w| match w {
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
|
||||||
|
Some(bound_or_constraints)
|
||||||
|
}
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => self.lazy_bound(db),
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::LazyConstraints => self.lazy_constraints(db),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_type(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||||
|
self._default(db).and_then(|d| match d {
|
||||||
|
TypeVarDefaultEvaluation::Eager(ty) => Some(ty),
|
||||||
|
TypeVarDefaultEvaluation::Lazy => self.lazy_default(db),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
db,
|
db,
|
||||||
self.name(db),
|
self.name(db),
|
||||||
self.definition(db),
|
self.definition(db),
|
||||||
self.bound_or_constraints(db)
|
self._bound_or_constraints(db)
|
||||||
.map(|b| b.normalized_impl(db, visitor)),
|
.and_then(|bound_or_constraints| match bound_or_constraints {
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
|
||||||
|
Some(bound_or_constraints.normalized_impl(db, visitor).into())
|
||||||
|
}
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => self
|
||||||
|
.lazy_bound(db)
|
||||||
|
.map(|bound| bound.normalized_impl(db, visitor).into()),
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::LazyConstraints => self
|
||||||
|
.lazy_constraints(db)
|
||||||
|
.map(|constraints| constraints.normalized_impl(db, visitor).into()),
|
||||||
|
}),
|
||||||
self.variance(db),
|
self.variance(db),
|
||||||
self.default_ty(db).map(|d| d.normalized_impl(db, visitor)),
|
self._default(db).and_then(|default| match default {
|
||||||
|
TypeVarDefaultEvaluation::Eager(ty) => Some(ty.normalized_impl(db, visitor).into()),
|
||||||
|
TypeVarDefaultEvaluation::Lazy => self
|
||||||
|
.lazy_default(db)
|
||||||
|
.map(|ty| ty.normalized_impl(db, visitor).into()),
|
||||||
|
}),
|
||||||
self.kind(db),
|
self.kind(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6937,13 +6974,59 @@ impl<'db> TypeVarInstance<'db> {
|
||||||
db,
|
db,
|
||||||
self.name(db),
|
self.name(db),
|
||||||
self.definition(db),
|
self.definition(db),
|
||||||
self.bound_or_constraints(db)
|
self._bound_or_constraints(db)
|
||||||
.map(|b| b.materialize(db, variance)),
|
.and_then(|bound_or_constraints| match bound_or_constraints {
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
|
||||||
|
Some(bound_or_constraints.materialize(db, variance).into())
|
||||||
|
}
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => self
|
||||||
|
.lazy_bound(db)
|
||||||
|
.map(|bound| bound.materialize(db, variance).into()),
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::LazyConstraints => self
|
||||||
|
.lazy_constraints(db)
|
||||||
|
.map(|constraints| constraints.materialize(db, variance).into()),
|
||||||
|
}),
|
||||||
self.variance(db),
|
self.variance(db),
|
||||||
self.default_ty(db),
|
self._default(db).and_then(|default| match default {
|
||||||
|
TypeVarDefaultEvaluation::Eager(ty) => Some(ty.materialize(db, variance).into()),
|
||||||
|
TypeVarDefaultEvaluation::Lazy => self
|
||||||
|
.lazy_default(db)
|
||||||
|
.map(|ty| ty.materialize(db, variance).into()),
|
||||||
|
}),
|
||||||
self.kind(db),
|
self.kind(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked]
|
||||||
|
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()?);
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked]
|
||||||
|
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()?;
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked]
|
||||||
|
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()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where a type variable is bound and usable.
|
/// Where a type variable is bound and usable.
|
||||||
|
@ -7008,10 +7091,10 @@ impl<'db> BoundTypeVarInstance<'db> {
|
||||||
/// By using `U` in the generic class, it becomes bound, and so we have a
|
/// By using `U` in the generic class, it becomes bound, and so we have a
|
||||||
/// `BoundTypeVarInstance`. As part of binding `U` we must also bind its default value
|
/// `BoundTypeVarInstance`. As part of binding `U` we must also bind its default value
|
||||||
/// (resulting in `T@C`).
|
/// (resulting in `T@C`).
|
||||||
pub(crate) fn default_ty(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
pub(crate) fn default_type(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||||
let binding_context = self.binding_context(db);
|
let binding_context = self.binding_context(db);
|
||||||
self.typevar(db)
|
self.typevar(db)
|
||||||
.default_ty(db)
|
.default_type(db)
|
||||||
.map(|ty| ty.apply_type_mapping(db, &TypeMapping::BindLegacyTypevars(binding_context)))
|
.map(|ty| ty.apply_type_mapping(db, &TypeMapping::BindLegacyTypevars(binding_context)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7054,6 +7137,38 @@ impl TypeVarVariance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a typevar default is eagerly specified or lazily evaluated.
|
||||||
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||||
|
pub enum TypeVarDefaultEvaluation<'db> {
|
||||||
|
/// The default type is lazily evaluated.
|
||||||
|
Lazy,
|
||||||
|
/// The default type is eagerly specified.
|
||||||
|
Eager(Type<'db>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> From<Type<'db>> for TypeVarDefaultEvaluation<'db> {
|
||||||
|
fn from(value: Type<'db>) -> Self {
|
||||||
|
TypeVarDefaultEvaluation::Eager(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a typevar bound/constraints is eagerly specified or lazily evaluated.
|
||||||
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||||
|
pub enum TypeVarBoundOrConstraintsEvaluation<'db> {
|
||||||
|
/// There is a lazily-evaluated upper bound.
|
||||||
|
LazyUpperBound,
|
||||||
|
/// There is a lazily-evaluated set of constraints.
|
||||||
|
LazyConstraints,
|
||||||
|
/// The upper bound/constraints are eagerly specified.
|
||||||
|
Eager(TypeVarBoundOrConstraints<'db>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> From<TypeVarBoundOrConstraints<'db>> for TypeVarBoundOrConstraintsEvaluation<'db> {
|
||||||
|
fn from(value: TypeVarBoundOrConstraints<'db>) -> Self {
|
||||||
|
TypeVarBoundOrConstraintsEvaluation::Eager(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||||
pub enum TypeVarBoundOrConstraints<'db> {
|
pub enum TypeVarBoundOrConstraints<'db> {
|
||||||
UpperBound(Type<'db>),
|
UpperBound(Type<'db>),
|
||||||
|
|
|
@ -397,7 +397,7 @@ impl<'db> Bindings<'db> {
|
||||||
}
|
}
|
||||||
Some("__default__") => {
|
Some("__default__") => {
|
||||||
overload.set_return_type(
|
overload.set_return_type(
|
||||||
typevar.default_ty(db).unwrap_or_else(|| {
|
typevar.default_type(db).unwrap_or_else(|| {
|
||||||
KnownClass::NoDefaultType.to_instance(db)
|
KnownClass::NoDefaultType.to_instance(db)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4520,7 +4520,9 @@ impl KnownClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
let bound_or_constraint = match (bound, constraints) {
|
let bound_or_constraint = match (bound, constraints) {
|
||||||
(Some(bound), None) => Some(TypeVarBoundOrConstraints::UpperBound(*bound)),
|
(Some(bound), None) => {
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(*bound).into())
|
||||||
|
}
|
||||||
|
|
||||||
(None, Some(_constraints)) => {
|
(None, Some(_constraints)) => {
|
||||||
// We don't use UnionType::from_elements or UnionBuilder here,
|
// We don't use UnionType::from_elements or UnionBuilder here,
|
||||||
|
@ -4536,7 +4538,7 @@ impl KnownClass {
|
||||||
.map(|(_, ty)| ty)
|
.map(|(_, ty)| ty)
|
||||||
.collect::<Box<_>>(),
|
.collect::<Box<_>>(),
|
||||||
);
|
);
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(elements))
|
Some(TypeVarBoundOrConstraints::Constraints(elements).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Emit a diagnostic that TypeVar cannot be both bounded and
|
// TODO: Emit a diagnostic that TypeVar cannot be both bounded and
|
||||||
|
@ -4554,7 +4556,7 @@ impl KnownClass {
|
||||||
Some(containing_assignment),
|
Some(containing_assignment),
|
||||||
bound_or_constraint,
|
bound_or_constraint,
|
||||||
variance,
|
variance,
|
||||||
*default,
|
default.map(Into::into),
|
||||||
TypeVarKind::Legacy,
|
TypeVarKind::Legacy,
|
||||||
),
|
),
|
||||||
)));
|
)));
|
||||||
|
|
|
@ -14,9 +14,8 @@ use crate::types::generics::{GenericContext, Specialization};
|
||||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||||
use crate::types::tuple::TupleSpec;
|
use crate::types::tuple::TupleSpec;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BoundTypeVarInstance, CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol,
|
CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType,
|
||||||
StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance,
|
SubclassOfInner, Type, UnionType, WrapperDescriptorKind,
|
||||||
UnionType, WrapperDescriptorKind,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<'db> Type<'db> {
|
impl<'db> Type<'db> {
|
||||||
|
@ -417,83 +416,6 @@ impl Display for DisplayGenericAlias<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> TypeVarInstance<'db> {
|
|
||||||
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayTypeVarInstance<'db> {
|
|
||||||
DisplayTypeVarInstance { typevar: self, db }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct DisplayTypeVarInstance<'db> {
|
|
||||||
typevar: TypeVarInstance<'db>,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for DisplayTypeVarInstance<'_> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
display_quoted_string(self.typevar.name(self.db)).fmt(f)?;
|
|
||||||
match self.typevar.bound_or_constraints(self.db) {
|
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
|
||||||
write!(f, ", bound={}", bound.display(self.db))?;
|
|
||||||
}
|
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
|
||||||
for constraint in constraints.iter(self.db) {
|
|
||||||
write!(f, ", {}", constraint.display(self.db))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
if let Some(default_type) = self.typevar.default_ty(self.db) {
|
|
||||||
write!(f, ", default={}", default_type.display(self.db))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> BoundTypeVarInstance<'db> {
|
|
||||||
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayBoundTypeVarInstance<'db> {
|
|
||||||
DisplayBoundTypeVarInstance {
|
|
||||||
bound_typevar: self,
|
|
||||||
db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct DisplayBoundTypeVarInstance<'db> {
|
|
||||||
bound_typevar: BoundTypeVarInstance<'db>,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for DisplayBoundTypeVarInstance<'_> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
// This looks very much like DisplayTypeVarInstance::fmt, but note that we have typevar
|
|
||||||
// default values in a subtly different way: if the default value contains other typevars,
|
|
||||||
// here those must be bound as well, whereas in DisplayTypeVarInstance they should not. See
|
|
||||||
// BoundTypeVarInstance::default_ty for more details.
|
|
||||||
let typevar = self.bound_typevar.typevar(self.db);
|
|
||||||
f.write_str(typevar.name(self.db))?;
|
|
||||||
match typevar.bound_or_constraints(self.db) {
|
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
|
||||||
write!(f, ": {}", bound.display(self.db))?;
|
|
||||||
}
|
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
|
||||||
f.write_str(": (")?;
|
|
||||||
for (idx, constraint) in constraints.iter(self.db).enumerate() {
|
|
||||||
if idx > 0 {
|
|
||||||
f.write_str(", ")?;
|
|
||||||
}
|
|
||||||
constraint.display(self.db).fmt(f)?;
|
|
||||||
}
|
|
||||||
f.write_char(')')?;
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
if let Some(default_type) = self.bound_typevar.default_ty(self.db) {
|
|
||||||
write!(f, " = {}", default_type.display(self.db))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> GenericContext<'db> {
|
impl<'db> GenericContext<'db> {
|
||||||
pub fn display(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> {
|
pub fn display(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> {
|
||||||
DisplayGenericContext {
|
DisplayGenericContext {
|
||||||
|
@ -545,7 +467,7 @@ impl Display for DisplayGenericContext<'_> {
|
||||||
if idx > 0 {
|
if idx > 0 {
|
||||||
f.write_str(", ")?;
|
f.write_str(", ")?;
|
||||||
}
|
}
|
||||||
bound_typevar.display(self.db).fmt(f)?;
|
f.write_str(bound_typevar.typevar(self.db).name(self.db))?;
|
||||||
}
|
}
|
||||||
f.write_char(']')
|
f.write_char(']')
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ impl<'db> GenericContext<'db> {
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
if let Some(default_ty) = bound_typevar.default_ty(db) {
|
if let Some(default_ty) = bound_typevar.default_type(db) {
|
||||||
parameter = parameter.with_default_type(default_ty);
|
parameter = parameter.with_default_type(default_ty);
|
||||||
}
|
}
|
||||||
parameter
|
parameter
|
||||||
|
@ -337,7 +337,7 @@ impl<'db> GenericContext<'db> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(default) = typevar.default_ty(db) else {
|
let Some(default) = typevar.default_type(db) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -756,7 +756,7 @@ impl<'db> SpecializationBuilder<'db> {
|
||||||
self.types
|
self.types
|
||||||
.get(variable)
|
.get(variable)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(variable.default_ty(self.db).unwrap_or(Type::unknown()))
|
.unwrap_or(variable.default_type(self.db).unwrap_or(Type::unknown()))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
// TODO Infer the tuple spec for a tuple type
|
// TODO Infer the tuple spec for a tuple type
|
||||||
|
|
|
@ -121,8 +121,9 @@ use crate::types::{
|
||||||
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard,
|
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard,
|
||||||
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
|
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
|
||||||
Parameters, SpecialFormType, SubclassOfType, Truthiness, Type, TypeAliasType,
|
Parameters, SpecialFormType, SubclassOfType, Truthiness, Type, TypeAliasType,
|
||||||
TypeAndQualifiers, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance,
|
TypeAndQualifiers, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraintsEvaluation,
|
||||||
TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
|
TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder,
|
||||||
|
UnionType, binding_type, todo_type,
|
||||||
};
|
};
|
||||||
use crate::unpack::{EvaluationMode, Unpack, UnpackPosition};
|
use crate::unpack::{EvaluationMode, Unpack, UnpackPosition};
|
||||||
use crate::util::diagnostics::format_enumeration;
|
use crate::util::diagnostics::format_enumeration;
|
||||||
|
@ -1748,6 +1749,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
DefinitionKind::Class(class) => {
|
DefinitionKind::Class(class) => {
|
||||||
self.infer_class_deferred(definition, class.node(self.module()));
|
self.infer_class_deferred(definition, class.node(self.module()));
|
||||||
}
|
}
|
||||||
|
DefinitionKind::TypeVar(typevar) => {
|
||||||
|
self.infer_typevar_deferred(typevar.node(self.module()));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2243,6 +2247,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.infer_type_parameters(type_params);
|
self.infer_type_parameters(type_params);
|
||||||
|
|
||||||
if let Some(arguments) = class.arguments.as_deref() {
|
if let Some(arguments) = class.arguments.as_deref() {
|
||||||
|
let in_stub = self.in_stub();
|
||||||
|
let previous_deferred_state =
|
||||||
|
std::mem::replace(&mut self.deferred_state, in_stub.into());
|
||||||
let mut call_arguments =
|
let mut call_arguments =
|
||||||
CallArguments::from_arguments(self.db(), arguments, |argument, splatted_value| {
|
CallArguments::from_arguments(self.db(), arguments, |argument, splatted_value| {
|
||||||
let ty = self.infer_expression(splatted_value);
|
let ty = self.infer_expression(splatted_value);
|
||||||
|
@ -2251,6 +2258,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
});
|
});
|
||||||
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
|
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
|
||||||
self.infer_argument_types(arguments, &mut call_arguments, &argument_forms);
|
self.infer_argument_types(arguments, &mut call_arguments, &argument_forms);
|
||||||
|
self.deferred_state = previous_deferred_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.typevar_binding_context = previous_typevar_binding_context;
|
self.typevar_binding_context = previous_typevar_binding_context;
|
||||||
|
@ -2271,7 +2279,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.typevar_binding_context.replace(binding_context);
|
self.typevar_binding_context.replace(binding_context);
|
||||||
self.infer_return_type_annotation(
|
self.infer_return_type_annotation(
|
||||||
function.returns.as_deref(),
|
function.returns.as_deref(),
|
||||||
DeferredExpressionState::None,
|
self.defer_annotations().into(),
|
||||||
);
|
);
|
||||||
self.infer_type_parameters(type_params);
|
self.infer_type_parameters(type_params);
|
||||||
self.infer_parameters(&function.parameters);
|
self.infer_parameters(&function.parameters);
|
||||||
|
@ -2316,7 +2324,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
let class_scope = match parent_scope.kind() {
|
let class_scope = match parent_scope.kind() {
|
||||||
ScopeKind::Class => parent_scope,
|
ScopeKind::Class => parent_scope,
|
||||||
ScopeKind::Annotation => {
|
ScopeKind::TypeParams => {
|
||||||
let class_scope_id = parent_scope.parent()?;
|
let class_scope_id = parent_scope.parent()?;
|
||||||
let potentially_class_scope = self.index.scope(class_scope_id);
|
let potentially_class_scope = self.index.scope(class_scope_id);
|
||||||
|
|
||||||
|
@ -2757,7 +2765,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
let annotated = self.infer_optional_annotation_expression(
|
let annotated = self.infer_optional_annotation_expression(
|
||||||
parameter.annotation.as_deref(),
|
parameter.annotation.as_deref(),
|
||||||
DeferredExpressionState::None,
|
self.defer_annotations().into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(qualifiers) = annotated.map(|annotated| annotated.qualifiers) {
|
if let Some(qualifiers) = annotated.map(|annotated| annotated.qualifiers) {
|
||||||
|
@ -2792,7 +2800,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
self.infer_optional_annotation_expression(
|
self.infer_optional_annotation_expression(
|
||||||
annotation.as_deref(),
|
annotation.as_deref(),
|
||||||
DeferredExpressionState::None,
|
self.defer_annotations().into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3402,44 +3410,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
{
|
{
|
||||||
builder.into_diagnostic("TypeVar must have at least two constrained types");
|
builder.into_diagnostic("TypeVar must have at least two constrained types");
|
||||||
}
|
}
|
||||||
self.infer_expression(expr);
|
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// We don't use UnionType::from_elements or UnionBuilder here, because we don't
|
Some(TypeVarBoundOrConstraintsEvaluation::LazyConstraints)
|
||||||
// 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(
|
|
||||||
self.db(),
|
|
||||||
elts.iter()
|
|
||||||
.map(|expr| self.infer_type_expression(expr))
|
|
||||||
.collect::<Box<[_]>>(),
|
|
||||||
);
|
|
||||||
let constraints = TypeVarBoundOrConstraints::Constraints(elements);
|
|
||||||
// But when we construct an actual union type for the constraint expression as
|
|
||||||
// a whole, we do use UnionType::from_elements to maintain the invariant that
|
|
||||||
// all union types are simplified.
|
|
||||||
self.store_expression_type(
|
|
||||||
expr,
|
|
||||||
UnionType::from_elements(self.db(), elements.elements(self.db())),
|
|
||||||
);
|
|
||||||
Some(constraints)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(expr) => Some(TypeVarBoundOrConstraints::UpperBound(
|
Some(_) => Some(TypeVarBoundOrConstraintsEvaluation::LazyUpperBound),
|
||||||
self.infer_type_expression(expr),
|
|
||||||
)),
|
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let default_ty = self.infer_optional_type_expression(default.as_deref());
|
if bound_or_constraint.is_some() || default.is_some() {
|
||||||
|
self.deferred.insert(definition);
|
||||||
|
}
|
||||||
let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new(
|
let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new(
|
||||||
self.db(),
|
self.db(),
|
||||||
&name.id,
|
&name.id,
|
||||||
Some(definition),
|
Some(definition),
|
||||||
bound_or_constraint,
|
bound_or_constraint,
|
||||||
TypeVarVariance::Invariant, // TODO: infer this
|
TypeVarVariance::Invariant, // TODO: infer this
|
||||||
default_ty,
|
default.as_deref().map(|_| TypeVarDefaultEvaluation::Lazy),
|
||||||
TypeVarKind::Pep695,
|
TypeVarKind::Pep695,
|
||||||
)));
|
)));
|
||||||
self.add_declaration_with_binding(
|
self.add_declaration_with_binding(
|
||||||
|
@ -3449,6 +3437,37 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_typevar_deferred(&mut self, node: &ast::TypeParamTypeVar) {
|
||||||
|
let ast::TypeParamTypeVar {
|
||||||
|
range: _,
|
||||||
|
node_index: _,
|
||||||
|
name: _,
|
||||||
|
bound,
|
||||||
|
default,
|
||||||
|
} = node;
|
||||||
|
match bound.as_deref() {
|
||||||
|
Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => {
|
||||||
|
// 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 ty = Type::Union(UnionType::new(
|
||||||
|
self.db(),
|
||||||
|
elts.iter()
|
||||||
|
.map(|expr| self.infer_type_expression(expr))
|
||||||
|
.collect::<Box<[_]>>(),
|
||||||
|
));
|
||||||
|
self.store_expression_type(expr, ty);
|
||||||
|
}
|
||||||
|
Some(expr) => {
|
||||||
|
self.infer_type_expression(expr);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
self.infer_optional_type_expression(default.as_deref());
|
||||||
|
}
|
||||||
|
|
||||||
fn infer_paramspec_definition(
|
fn infer_paramspec_definition(
|
||||||
&mut self,
|
&mut self,
|
||||||
node: &ast::TypeParamParamSpec,
|
node: &ast::TypeParamParamSpec,
|
||||||
|
@ -6573,7 +6592,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let place_table = self.index.place_table(file_scope_id);
|
let place_table = self.index.place_table(file_scope_id);
|
||||||
let use_def = self.index.use_def_map(file_scope_id);
|
let use_def = self.index.use_def_map(file_scope_id);
|
||||||
|
|
||||||
// If we're inferring types of deferred expressions, always treat them as public symbols
|
// If we're inferring types of deferred expressions, look them up from end-of-scope.
|
||||||
if self.is_deferred() {
|
if self.is_deferred() {
|
||||||
let place = if let Some(place_id) = place_table.place_id(expr) {
|
let place = if let Some(place_id) = place_table.place_id(expr) {
|
||||||
place_from_bindings(db, use_def.all_reachable_bindings(place_id))
|
place_from_bindings(db, use_def.all_reachable_bindings(place_id))
|
||||||
|
@ -6684,11 +6703,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
// Class scopes are not visible to nested scopes, and we need to handle global
|
// Class scopes are not visible to nested scopes, and we need to handle global
|
||||||
// scope differently (because an unbound name there falls back to builtins), so
|
// scope differently (because an unbound name there falls back to builtins), so
|
||||||
// check only function-like scopes.
|
// check only function-like scopes.
|
||||||
// There is one exception to this rule: type parameter scopes can see
|
// There is one exception to this rule: annotation scopes can see
|
||||||
// names defined in an immediately-enclosing class scope.
|
// names defined in an immediately-enclosing class scope.
|
||||||
let enclosing_scope_id = enclosing_scope_file_id.to_scope_id(db, current_file);
|
let enclosing_scope_id = enclosing_scope_file_id.to_scope_id(db, current_file);
|
||||||
|
|
||||||
let is_immediately_enclosing_scope = scope.is_type_parameter(db)
|
let is_immediately_enclosing_scope = scope.is_annotation(db)
|
||||||
&& scope
|
&& scope
|
||||||
.scope(db)
|
.scope(db)
|
||||||
.parent()
|
.parent()
|
||||||
|
@ -9535,17 +9554,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
ty
|
ty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Similar to [`infer_type_expression`], but accepts an optional type expression and returns
|
|
||||||
/// [`None`] if the expression is [`None`].
|
|
||||||
///
|
|
||||||
/// [`infer_type_expression`]: TypeInferenceBuilder::infer_type_expression
|
|
||||||
fn infer_optional_type_expression(
|
|
||||||
&mut self,
|
|
||||||
expression: Option<&ast::Expr>,
|
|
||||||
) -> Option<Type<'db>> {
|
|
||||||
expression.map(|expr| self.infer_type_expression(expr))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Similar to [`infer_type_expression`], but accepts a [`DeferredExpressionState`].
|
/// Similar to [`infer_type_expression`], but accepts a [`DeferredExpressionState`].
|
||||||
///
|
///
|
||||||
/// [`infer_type_expression`]: TypeInferenceBuilder::infer_type_expression
|
/// [`infer_type_expression`]: TypeInferenceBuilder::infer_type_expression
|
||||||
|
@ -9560,6 +9568,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
annotation_ty
|
annotation_ty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Similar to [`infer_type_expression`], but accepts an optional expression.
|
||||||
|
///
|
||||||
|
/// [`infer_type_expression`]: TypeInferenceBuilder::infer_type_expression_with_state
|
||||||
|
fn infer_optional_type_expression(
|
||||||
|
&mut self,
|
||||||
|
expression: Option<&ast::Expr>,
|
||||||
|
) -> Option<Type<'db>> {
|
||||||
|
expression.map(|expr| self.infer_type_expression(expr))
|
||||||
|
}
|
||||||
|
|
||||||
fn report_invalid_type_expression(
|
fn report_invalid_type_expression(
|
||||||
&self,
|
&self,
|
||||||
expression: &ast::Expr,
|
expression: &ast::Expr,
|
||||||
|
@ -11541,38 +11559,20 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
typevar
|
typevar
|
||||||
.default_ty(&db)
|
.default_type(&db)
|
||||||
.map(|ty| ty.display(&db).to_string()),
|
.map(|ty| ty.display(&db).to_string()),
|
||||||
default.map(std::borrow::ToOwned::to_owned)
|
default.map(std::borrow::ToOwned::to_owned)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
check_typevar("T", "typing.TypeVar(\"T\")", None, None, None);
|
check_typevar("T", "typing.TypeVar", None, None, None);
|
||||||
check_typevar("U", "typing.TypeVar(\"U\", bound=A)", Some("A"), None, None);
|
check_typevar("U", "typing.TypeVar", Some("A"), None, None);
|
||||||
check_typevar(
|
check_typevar("V", "typing.TypeVar", None, Some(&["A", "B"]), None);
|
||||||
"V",
|
check_typevar("W", "typing.TypeVar", None, None, Some("A"));
|
||||||
"typing.TypeVar(\"V\", A, B)",
|
check_typevar("X", "typing.TypeVar", Some("A"), None, Some("A1"));
|
||||||
None,
|
|
||||||
Some(&["A", "B"]),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
check_typevar(
|
|
||||||
"W",
|
|
||||||
"typing.TypeVar(\"W\", default=A)",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
Some("A"),
|
|
||||||
);
|
|
||||||
check_typevar(
|
|
||||||
"X",
|
|
||||||
"typing.TypeVar(\"X\", bound=A, default=A1)",
|
|
||||||
Some("A"),
|
|
||||||
None,
|
|
||||||
Some("A1"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// a typevar with less than two constraints is treated as unconstrained
|
// a typevar with less than two constraints is treated as unconstrained
|
||||||
check_typevar("Y", "typing.TypeVar(\"Y\")", None, None, None);
|
check_typevar("Y", "typing.TypeVar", None, None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that a symbol known to be unbound in a scope does not still trigger cycle-causing
|
/// Test that a symbol known to be unbound in a scope does not still trigger cycle-causing
|
||||||
|
|
|
@ -1797,11 +1797,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
assert_eq!(a_name, "a");
|
assert_eq!(a_name, "a");
|
||||||
assert_eq!(b_name, "b");
|
assert_eq!(b_name, "b");
|
||||||
// TODO resolution should not be deferred; we should see A, not A | B
|
assert_eq!(a_annotated_ty.unwrap().display(&db).to_string(), "A");
|
||||||
assert_eq!(
|
|
||||||
a_annotated_ty.unwrap().display(&db).to_string(),
|
|
||||||
"Unknown | A | B"
|
|
||||||
);
|
|
||||||
assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f");
|
assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,9 +97,12 @@ impl<'db> SubclassOfType<'db> {
|
||||||
db,
|
db,
|
||||||
Name::new_static("T_all"),
|
Name::new_static("T_all"),
|
||||||
None,
|
None,
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(
|
Some(
|
||||||
KnownClass::Type.to_instance(db),
|
TypeVarBoundOrConstraints::UpperBound(
|
||||||
)),
|
KnownClass::Type.to_instance(db),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
variance,
|
variance,
|
||||||
None,
|
None,
|
||||||
TypeVarKind::Pep695,
|
TypeVarKind::Pep695,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue