[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:
Carl Meyer 2025-08-13 15:51:59 -07:00 committed by GitHub
parent baadb5a78d
commit 5a570c8e6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 429 additions and 239 deletions

View file

@ -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]

View file

@ -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]

View file

@ -0,0 +1,5 @@
def name_1[name_0: name_0](name_2: name_0):
try:
pass
except name_2:
pass

View file

@ -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):

View file

@ -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")

View file

@ -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): ...

View file

@ -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/

View file

@ -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):

View file

@ -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

View file

@ -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
``` ```

View file

@ -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"]);

View file

@ -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

View file

@ -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.,

View file

@ -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(_)

View file

@ -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>),

View file

@ -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)
}), }),
); );

View file

@ -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,
), ),
))); )));

View file

@ -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(']')
} }

View file

@ -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

View file

@ -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

View file

@ -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");
} }

View file

@ -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,