diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 5bb1d4239f..0918d86a4d 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -728,11 +728,11 @@ mod tests { ); // TODO: This should render T@Alias once we create GenericContexts for type alias scopes. - assert_snapshot!(test.hover(), @r#" - typing.TypeVar("T", bound=int, default=bool) + assert_snapshot!(test.hover(), @r###" + typing.TypeVar --------------------------------------------- ```python - typing.TypeVar("T", bound=int, default=bool) + typing.TypeVar ``` --------------------------------------------- info[hover]: Hovered content is @@ -743,7 +743,7 @@ mod tests { | | | source | - "#); + "###); } #[test] diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index 2ae416e9b8..b38e50a011 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -793,17 +793,17 @@ mod tests { identity('hello')", ); - assert_snapshot!(test.inlay_hints(), @r#" + assert_snapshot!(test.inlay_hints(), @r###" from typing import TypeVar, Generic - T[: typing.TypeVar("T")] = TypeVar([name=]'T') + T[: typing.TypeVar] = TypeVar([name=]'T') def identity(x: T) -> T: return x identity([x=]42) identity([x=]'hello') - "#); + "###); } #[test] diff --git a/crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars.py b/crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars.py new file mode 100644 index 0000000000..5c9f592768 --- /dev/null +++ b/crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars.py @@ -0,0 +1,5 @@ +def name_1[name_0: name_0](name_2: name_0): + try: + pass + except name_2: + pass diff --git a/crates/ty_python_semantic/resources/corpus/except_handler_with_Any_bound_typevar.py b/crates/ty_python_semantic/resources/corpus/except_handler_with_Any_bound_typevar.py index a951d5034f..d6a3732a3c 100644 --- a/crates/ty_python_semantic/resources/corpus/except_handler_with_Any_bound_typevar.py +++ b/crates/ty_python_semantic/resources/corpus/except_handler_with_Any_bound_typevar.py @@ -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 def name_2[T: Any](x: T): diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index 1912f48d2e..d34c642bb3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -20,7 +20,7 @@ from typing import TypeVar T = TypeVar("T") reveal_type(type(T)) # revealed: -reveal_type(T) # revealed: typing.TypeVar("T") +reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__name__) # revealed: Literal["T"] ``` @@ -80,7 +80,7 @@ from typing import TypeVar T = TypeVar("T", default=int) reveal_type(type(T)) # revealed: -reveal_type(T) # revealed: typing.TypeVar("T", default=int) +reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__default__) # revealed: int reveal_type(T.__bound__) # revealed: None reveal_type(T.__constraints__) # revealed: tuple[()] @@ -116,7 +116,7 @@ from typing import TypeVar T = TypeVar("T", bound=int) reveal_type(type(T)) # revealed: -reveal_type(T) # revealed: typing.TypeVar("T", bound=int) +reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__bound__) # revealed: int reveal_type(T.__constraints__) # revealed: tuple[()] @@ -131,7 +131,7 @@ from typing import TypeVar T = TypeVar("T", int, str) reveal_type(type(T)) # revealed: -reveal_type(T) # revealed: typing.TypeVar("T", int, str) +reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__constraints__) # revealed: tuple[int, str] S = TypeVar("S") diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 9b82e4ff9a..261c3d370d 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -391,6 +391,7 @@ wrong_innards: C[int] = C("five", 1) ### Some `__init__` overloads only apply to certain specializations ```py +from __future__ import annotations from typing import overload class C[T]: @@ -541,6 +542,23 @@ class WithOverloadedMethod[T]: 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 ### 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. -```py +```pyi # error: [cyclic-class-definition] class C[T](C): ... diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md index f0c8ce2d04..ebf69f7e4e 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md @@ -2,7 +2,7 @@ ```toml [environment] -python-version = "3.12" +python-version = "3.13" ``` [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 def f[T](): reveal_type(type(T)) # revealed: - reveal_type(T) # revealed: typing.TypeVar("T") + reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__name__) # revealed: Literal["T"] ``` @@ -33,7 +33,7 @@ python-version = "3.13" ```py def f[T = int](): reveal_type(type(T)) # revealed: - reveal_type(T) # revealed: typing.TypeVar("T", default=int) + reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__default__) # revealed: int reveal_type(T.__bound__) # revealed: None reveal_type(T.__constraints__) # revealed: tuple[()] @@ -66,7 +66,7 @@ class Invalid[S = T]: ... ```py def f[T: int](): reveal_type(type(T)) # revealed: - reveal_type(T) # revealed: typing.TypeVar("T", bound=int) + reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__bound__) # revealed: int reveal_type(T.__constraints__) # revealed: tuple[()] @@ -79,7 +79,7 @@ def g[S](): ```py def f[T: (int, str)](): reveal_type(type(T)) # revealed: - 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.__bound__) # revealed: None @@ -745,4 +745,88 @@ def constrained[T: (int, str)](x: T): 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/ diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 3b4b18c2aa..e68cc538ee 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -2051,6 +2051,7 @@ python-version = "3.12" ``` ```py +from __future__ import annotations from typing import cast, Protocol class Iterator[T](Protocol): diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/eager.md b/crates/ty_python_semantic/resources/mdtest/scopes/eager.md index b8bc74d716..286f2eb261 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/eager.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/eager.md @@ -410,4 +410,47 @@ reveal_type(C.var) # revealed: int | 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 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap index f3f4563dc5..52a55ed12c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap @@ -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: 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 ``` @@ -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: 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 ``` @@ -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: 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 ``` @@ -152,7 +152,7 @@ info: Overload implementation defined here 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: 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 ``` @@ -176,7 +176,7 @@ info: Function defined here 8 | return 0 | 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 ``` @@ -199,8 +199,8 @@ info: Type variable defined here | ^^^^^^ 14 | return 0 | -info: Union variant `def f4[T: str](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: 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](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 ``` @@ -227,7 +227,7 @@ info: Matching overload defined here info: Non-matching overloads for function `f5`: info: () -> None 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 ``` @@ -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: 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 ``` diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index 2d429a4fec..c195059b38 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -167,7 +167,7 @@ pub(crate) fn attribute_scopes<'db, 's>( ChildrenIter::new(index, class_scope_id).filter_map(move |(child_scope_id, 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. // Go one level deeper to find the function scope. The first // 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) // Exception: type parameter scopes can see names defined in an immediately-enclosing class scope if scope.kind() == ScopeKind::Class { - // Allow type parameter scopes to see their immediately-enclosing class scope exactly once - if self.starting_scope_kind.is_type_parameter() && self.yielded_count == 2 { + // Allow annotation scopes to see their immediately-enclosing class scope exactly once + if self.starting_scope_kind.is_annotation() && self.yielded_count == 2 { return Some((scope_id, scope)); } continue; @@ -1317,7 +1317,7 @@ def func[T](): 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), "func" @@ -1361,7 +1361,7 @@ class C[T]: 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"); let ann_table = index.place_table(ann_scope_id); assert_eq!(names(&ann_table), vec!["T"]); diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index e8a993fa10..1a9b6b01a9 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -199,7 +199,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { match self.scopes[parent.file_scope_id].kind() { ScopeKind::Class => Some(parent.file_scope_id), - ScopeKind::Annotation => { + ScopeKind::TypeParams => { // 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. let grandparent = scopes_rev.next()?; @@ -2637,7 +2637,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> { ScopeKind::Comprehension | ScopeKind::Module | ScopeKind::TypeAlias - | ScopeKind::Annotation => {} + | ScopeKind::TypeParams => {} } } false @@ -2652,7 +2652,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> { ScopeKind::Comprehension | ScopeKind::Module | ScopeKind::TypeAlias - | ScopeKind::Annotation => {} + | ScopeKind::TypeParams => {} } } false @@ -2664,7 +2664,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> { match scope.kind() { ScopeKind::Class | ScopeKind::Comprehension => return false, ScopeKind::Function | ScopeKind::Lambda => return true, - ScopeKind::Module | ScopeKind::TypeAlias | ScopeKind::Annotation => {} + ScopeKind::Module | ScopeKind::TypeAlias | ScopeKind::TypeParams => {} } } false diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index edefca65e9..0e44e1a9c4 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -702,6 +702,13 @@ impl DefinitionKind<'_> { ) } + pub(crate) fn as_typevar(&self) -> Option<&AstNodeRef> { + match self { + DefinitionKind::TypeVar(type_var) => Some(type_var), + _ => None, + } + } + /// Returns the [`TextRange`] of the definition target. /// /// A definition target would mainly be the node representing the place being defined i.e., diff --git a/crates/ty_python_semantic/src/semantic_index/scope.rs b/crates/ty_python_semantic/src/semantic_index/scope.rs index 6c3fcdbd71..b29e732305 100644 --- a/crates/ty_python_semantic/src/semantic_index/scope.rs +++ b/crates/ty_python_semantic/src/semantic_index/scope.rs @@ -29,8 +29,8 @@ impl<'db> ScopeId<'db> { self.node(db).scope_kind().is_function_like() } - pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool { - self.node(db).scope_kind().is_type_parameter() + pub(crate) fn is_annotation(self, db: &'db dyn Db) -> bool { + self.node(db).scope_kind().is_annotation() } pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind { @@ -200,7 +200,7 @@ impl ScopeLaziness { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum ScopeKind { Module, - Annotation, + TypeParams, Class, Function, Lambda, @@ -215,18 +215,18 @@ impl ScopeKind { pub(crate) const fn laziness(self) -> ScopeLaziness { match self { - ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => ScopeLaziness::Eager, - ScopeKind::Annotation - | ScopeKind::Function - | ScopeKind::Lambda - | ScopeKind::TypeAlias => ScopeLaziness::Lazy, + ScopeKind::Module + | ScopeKind::Class + | ScopeKind::Comprehension + | ScopeKind::TypeParams => ScopeLaziness::Eager, + ScopeKind::Function | ScopeKind::Lambda | ScopeKind::TypeAlias => ScopeLaziness::Lazy, } } pub(crate) const fn visibility(self) -> ScopeVisibility { match self { ScopeKind::Module | ScopeKind::Class => ScopeVisibility::Public, - ScopeKind::Annotation + ScopeKind::TypeParams | ScopeKind::TypeAlias | ScopeKind::Function | ScopeKind::Lambda @@ -239,7 +239,7 @@ impl ScopeKind { // symbol table also uses the term "function-like" for these scopes. matches!( self, - ScopeKind::Annotation + ScopeKind::TypeParams | ScopeKind::Function | ScopeKind::Lambda | ScopeKind::TypeAlias @@ -255,8 +255,8 @@ impl ScopeKind { matches!(self, ScopeKind::Module) } - pub(crate) const fn is_type_parameter(self) -> bool { - matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias) + pub(crate) const fn is_annotation(self) -> bool { + matches!(self, ScopeKind::TypeParams | ScopeKind::TypeAlias) } pub(crate) const fn is_non_lambda_function(self) -> bool { @@ -388,7 +388,7 @@ impl NodeWithScopeKind { Self::Lambda(_) => ScopeKind::Lambda, Self::FunctionTypeParameters(_) | Self::ClassTypeParameters(_) - | Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation, + | Self::TypeAliasTypeParameters(_) => ScopeKind::TypeParams, Self::TypeAlias(_) => ScopeKind::TypeAlias, Self::ListComprehension(_) | Self::SetComprehension(_) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 35f52a652a..d966d9008a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5291,7 +5291,7 @@ impl<'db> Type<'db> { db, Name::new(format!("{}'instance", typevar.name(db))), None, - Some(bound_or_constraints), + Some(bound_or_constraints.into()), typevar.variance(db), None, typevar.kind(db), @@ -5482,7 +5482,7 @@ impl<'db> Type<'db> { db, ast::name::Name::new_static("Self"), Some(class_definition), - Some(TypeVarBoundOrConstraints::UpperBound(instance)), + Some(TypeVarBoundOrConstraints::UpperBound(instance).into()), TypeVarVariance::Invariant, None, 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 // 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. - KnownInstanceType::TypeVar(typevar) => { - write!(f, "typing.TypeVar({})", typevar.display(self.db)) - } + KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"), KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"), KnownInstanceType::Field(field) => { f.write_str("dataclasses.Field[")?; @@ -6862,14 +6860,17 @@ pub struct TypeVarInstance<'db> { /// The type var's definition (None if synthesized) pub definition: Option>, - /// The upper bound or constraint on the type of this TypeVar - bound_or_constraints: Option>, + /// The upper bound or constraint on the type of this TypeVar, if any. Don't use this field + /// directly; use the `bound_or_constraints` (or `upper_bound` and `constraints`) methods + /// instead (to evaluate any lazy bound or constraints). + _bound_or_constraints: Option>, /// The variance of the TypeVar variance: TypeVarVariance, - /// The default type for this TypeVar - default_ty: Option>, + /// The default type for this TypeVar, if any. Don't use this field directly, use the + /// `default_type` method instead (to evaluate any lazy default). + _default: Option>, 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) { 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); } } +#[salsa::tracked] impl<'db> TypeVarInstance<'db> { pub(crate) fn with_binding_context( self, @@ -6919,15 +6921,50 @@ impl<'db> TypeVarInstance<'db> { } } + pub(crate) fn bound_or_constraints( + self, + db: &'db dyn Db, + ) -> Option> { + 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> { + 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 { Self::new( db, self.name(db), self.definition(db), - self.bound_or_constraints(db) - .map(|b| b.normalized_impl(db, visitor)), + self._bound_or_constraints(db) + .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.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), ) } @@ -6937,13 +6974,59 @@ impl<'db> TypeVarInstance<'db> { db, self.name(db), self.definition(db), - self.bound_or_constraints(db) - .map(|b| b.materialize(db, variance)), + self._bound_or_constraints(db) + .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.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), ) } + + #[salsa::tracked] + fn lazy_bound(self, db: &'db dyn Db) -> Option> { + 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> { + 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> { + 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. @@ -7008,10 +7091,10 @@ impl<'db> BoundTypeVarInstance<'db> { /// 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 /// (resulting in `T@C`). - pub(crate) fn default_ty(self, db: &'db dyn Db) -> Option> { + pub(crate) fn default_type(self, db: &'db dyn Db) -> Option> { let binding_context = self.binding_context(db); self.typevar(db) - .default_ty(db) + .default_type(db) .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> 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> for TypeVarBoundOrConstraintsEvaluation<'db> { + fn from(value: TypeVarBoundOrConstraints<'db>) -> Self { + TypeVarBoundOrConstraintsEvaluation::Eager(value) + } +} + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)] pub enum TypeVarBoundOrConstraints<'db> { UpperBound(Type<'db>), diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index d517caa887..efaf549680 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -397,7 +397,7 @@ impl<'db> Bindings<'db> { } Some("__default__") => { overload.set_return_type( - typevar.default_ty(db).unwrap_or_else(|| { + typevar.default_type(db).unwrap_or_else(|| { KnownClass::NoDefaultType.to_instance(db) }), ); diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 72e8d20d0b..f8f1087a24 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -4520,7 +4520,9 @@ impl KnownClass { } 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)) => { // We don't use UnionType::from_elements or UnionBuilder here, @@ -4536,7 +4538,7 @@ impl KnownClass { .map(|(_, ty)| ty) .collect::>(), ); - Some(TypeVarBoundOrConstraints::Constraints(elements)) + Some(TypeVarBoundOrConstraints::Constraints(elements).into()) } // TODO: Emit a diagnostic that TypeVar cannot be both bounded and @@ -4554,7 +4556,7 @@ impl KnownClass { Some(containing_assignment), bound_or_constraint, variance, - *default, + default.map(Into::into), TypeVarKind::Legacy, ), ))); diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index db8c67f8f3..b50ca3d996 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -14,9 +14,8 @@ use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::tuple::TupleSpec; use crate::types::{ - BoundTypeVarInstance, CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, - StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, - UnionType, WrapperDescriptorKind, + CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType, + SubclassOfInner, Type, UnionType, WrapperDescriptorKind, }; 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> { pub fn display(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> { DisplayGenericContext { @@ -545,7 +467,7 @@ impl Display for DisplayGenericContext<'_> { if idx > 0 { f.write_str(", ")?; } - bound_typevar.display(self.db).fmt(f)?; + f.write_str(bound_typevar.typevar(self.db).name(self.db))?; } f.write_char(']') } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index c846ca6ee2..00089be337 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -225,7 +225,7 @@ impl<'db> GenericContext<'db> { } 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 @@ -337,7 +337,7 @@ impl<'db> GenericContext<'db> { continue; } - let Some(default) = typevar.default_ty(db) else { + let Some(default) = typevar.default_type(db) else { continue; }; @@ -756,7 +756,7 @@ impl<'db> SpecializationBuilder<'db> { self.types .get(variable) .copied() - .unwrap_or(variable.default_ty(self.db).unwrap_or(Type::unknown())) + .unwrap_or(variable.default_type(self.db).unwrap_or(Type::unknown())) }) .collect(); // TODO Infer the tuple spec for a tuple type diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index f8bf887e0f..8bba369c3b 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -121,8 +121,9 @@ use crate::types::{ IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType, Truthiness, Type, TypeAliasType, - TypeAndQualifiers, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, - TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type, + TypeAndQualifiers, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraintsEvaluation, + TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, + UnionType, binding_type, todo_type, }; use crate::unpack::{EvaluationMode, Unpack, UnpackPosition}; use crate::util::diagnostics::format_enumeration; @@ -1748,6 +1749,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { DefinitionKind::Class(class) => { 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); 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 = CallArguments::from_arguments(self.db(), arguments, |argument, 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()]; self.infer_argument_types(arguments, &mut call_arguments, &argument_forms); + self.deferred_state = previous_deferred_state; } 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.infer_return_type_annotation( function.returns.as_deref(), - DeferredExpressionState::None, + self.defer_annotations().into(), ); self.infer_type_parameters(type_params); self.infer_parameters(&function.parameters); @@ -2316,7 +2324,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let class_scope = match parent_scope.kind() { ScopeKind::Class => parent_scope, - ScopeKind::Annotation => { + ScopeKind::TypeParams => { let class_scope_id = parent_scope.parent()?; 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( parameter.annotation.as_deref(), - DeferredExpressionState::None, + self.defer_annotations().into(), ); if let Some(qualifiers) = annotated.map(|annotated| annotated.qualifiers) { @@ -2792,7 +2800,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_optional_annotation_expression( 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"); } - self.infer_expression(expr); None } else { - // We don't use UnionType::from_elements or UnionBuilder here, because we don't - // want to simplify the list of constraints like we do with the elements of an - // actual union type. - // TODO: Consider using a new `OneOfType` connective here instead, since that - // more accurately represents the actual semantics of typevar constraints. - let elements = UnionType::new( - self.db(), - elts.iter() - .map(|expr| self.infer_type_expression(expr)) - .collect::>(), - ); - 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(TypeVarBoundOrConstraintsEvaluation::LazyConstraints) } } - Some(expr) => Some(TypeVarBoundOrConstraints::UpperBound( - self.infer_type_expression(expr), - )), + Some(_) => Some(TypeVarBoundOrConstraintsEvaluation::LazyUpperBound), 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( self.db(), &name.id, Some(definition), bound_or_constraint, TypeVarVariance::Invariant, // TODO: infer this - default_ty, + default.as_deref().map(|_| TypeVarDefaultEvaluation::Lazy), TypeVarKind::Pep695, ))); 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::>(), + )); + 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( &mut self, node: &ast::TypeParamParamSpec, @@ -6573,7 +6592,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let place_table = self.index.place_table(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() { let place = if let Some(place_id) = place_table.place_id(expr) { 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 // scope differently (because an unbound name there falls back to builtins), so // 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. 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(db) .parent() @@ -9535,17 +9554,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> { 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> { - expression.map(|expr| self.infer_type_expression(expr)) - } - /// Similar to [`infer_type_expression`], but accepts a [`DeferredExpressionState`]. /// /// [`infer_type_expression`]: TypeInferenceBuilder::infer_type_expression @@ -9560,6 +9568,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> { 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> { + expression.map(|expr| self.infer_type_expression(expr)) + } + fn report_invalid_type_expression( &self, expression: &ast::Expr, @@ -11541,38 +11559,20 @@ mod tests { ); assert_eq!( typevar - .default_ty(&db) + .default_type(&db) .map(|ty| ty.display(&db).to_string()), default.map(std::borrow::ToOwned::to_owned) ); }; - check_typevar("T", "typing.TypeVar(\"T\")", None, None, None); - check_typevar("U", "typing.TypeVar(\"U\", bound=A)", Some("A"), None, None); - check_typevar( - "V", - "typing.TypeVar(\"V\", A, B)", - 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"), - ); + check_typevar("T", "typing.TypeVar", None, None, None); + check_typevar("U", "typing.TypeVar", Some("A"), None, None); + check_typevar("V", "typing.TypeVar", None, Some(&["A", "B"]), None); + check_typevar("W", "typing.TypeVar", None, None, Some("A")); + check_typevar("X", "typing.TypeVar", Some("A"), None, Some("A1")); // 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 diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 2120c62548..11ca44bb8f 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1797,11 +1797,7 @@ mod tests { }; assert_eq!(a_name, "a"); 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(), - "Unknown | A | B" - ); + assert_eq!(a_annotated_ty.unwrap().display(&db).to_string(), "A"); assert_eq!(b_annotated_ty.unwrap().display(&db).to_string(), "T@f"); } diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index f1815f50a5..1a67759432 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -97,9 +97,12 @@ impl<'db> SubclassOfType<'db> { db, Name::new_static("T_all"), None, - Some(TypeVarBoundOrConstraints::UpperBound( - KnownClass::Type.to_instance(db), - )), + Some( + TypeVarBoundOrConstraints::UpperBound( + KnownClass::Type.to_instance(db), + ) + .into(), + ), variance, None, TypeVarKind::Pep695,