diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md
index 5f51db2e505b8c..c4672536a4fdc2 100644
--- a/crates/ty/docs/rules.md
+++ b/crates/ty/docs/rules.md
@@ -8,7 +8,7 @@
Default level: error ·
Added in 0.0.13 ·
Related issues ·
-View source
+View source
@@ -49,7 +49,7 @@ class Derived(Base): # Error: `Derived` does not implement `method`
Default level: warn ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -90,7 +90,7 @@ class SubProto(BaseProto, Protocol):
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -157,7 +157,7 @@ def test(): -> "int":
Default level: error ·
Preview (since 0.0.16) ·
Related issues ·
-View source
+View source
@@ -206,7 +206,7 @@ Foo.method() # Error: cannot call abstract classmethod
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -230,7 +230,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: error ·
Added in 0.0.7 ·
Related issues ·
-View source
+View source
@@ -261,7 +261,7 @@ def f(x: object):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -293,7 +293,7 @@ f(int) # error
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -324,7 +324,7 @@ a = 1
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -356,7 +356,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -388,7 +388,7 @@ class B(A): ...
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -416,7 +416,7 @@ type B = A
Default level: error ·
Preview (since 1.0.0) ·
Related issues ·
-View source
+View source
@@ -448,7 +448,7 @@ class Example:
Default level: warn ·
Added in 0.0.1-alpha.16 ·
Related issues ·
-View source
+View source
@@ -475,7 +475,7 @@ old_func() # emits [deprecated] diagnostic
Default level: ignore ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -504,7 +504,7 @@ false positives it can produce.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -531,7 +531,7 @@ class B(A, A): ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -569,7 +569,7 @@ class A: # Crash at runtime
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -640,7 +640,7 @@ def foo() -> "intt\b": ...
Default level: error ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -672,7 +672,7 @@ def my_function() -> int:
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -798,7 +798,7 @@ def test(): -> "Literal[5]":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -828,7 +828,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -854,7 +854,7 @@ t[3] # IndexError: tuple index out of range
Default level: warn ·
Added in 0.0.1-alpha.33 ·
Related issues ·
-View source
+View source
@@ -888,7 +888,7 @@ class MyClass: ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -977,7 +977,7 @@ an atypical memory layout.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1004,7 +1004,7 @@ func("foo") # error: [invalid-argument-type]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1032,7 +1032,7 @@ a: int = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1066,7 +1066,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -1102,7 +1102,7 @@ asyncio.run(main())
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1126,7 +1126,7 @@ class A(42): ... # error: [invalid-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1153,7 +1153,7 @@ with 1:
Default level: error ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -1190,7 +1190,7 @@ class Foo(NamedTuple):
Default level: error ·
Added in 0.0.13 ·
Related issues ·
-View source
+View source
@@ -1222,7 +1222,7 @@ class A:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1251,7 +1251,7 @@ a: str
Default level: warn ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -1300,7 +1300,7 @@ class Pet(Enum):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1344,7 +1344,7 @@ except ZeroDivisionError:
Default level: error ·
Added in 0.0.1-alpha.28 ·
Related issues ·
-View source
+View source
@@ -1386,7 +1386,7 @@ class D(A):
Default level: error ·
Added in 0.0.1-alpha.35 ·
Related issues ·
-View source
+View source
@@ -1430,7 +1430,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1468,7 +1468,7 @@ class D(Generic[U, T]): ...
Default level: error ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -1547,7 +1547,7 @@ a = 20 / 0 # type: ignore
Default level: error ·
Added in 0.0.1-alpha.17 ·
Related issues ·
-View source
+View source
@@ -1586,7 +1586,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: warn ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -1647,7 +1647,7 @@ def f(x, y, /): # Python 3.8+ syntax
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1682,7 +1682,7 @@ def f(t: TypeVar("U")): ...
Default level: error ·
Added in 0.0.18 ·
Related issues ·
-View source
+View source
@@ -1710,7 +1710,7 @@ match x:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1744,7 +1744,7 @@ class B(metaclass=f): ...
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -1851,7 +1851,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -1905,7 +1905,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: error ·
Added in 0.0.1-alpha.27 ·
Related issues ·
-View source
+View source
@@ -1935,7 +1935,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1985,7 +1985,7 @@ def foo(x: int) -> int: ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2011,7 +2011,7 @@ def f(a: int = ''): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2042,7 +2042,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2076,7 +2076,7 @@ TypeError: Protocols can only inherit from other protocols, got
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2125,7 +2125,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2154,7 +2154,7 @@ def func() -> int:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2250,7 +2250,7 @@ class C: ...
Default level: error ·
Added in 0.0.10 ·
Related issues ·
-View source
+View source
@@ -2296,7 +2296,7 @@ class MyClass:
Default level: error ·
Added in 0.0.1-alpha.6 ·
Related issues ·
-View source
+View source
@@ -2323,7 +2323,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -2370,7 +2370,7 @@ Bar[int] # error: too few arguments
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2400,7 +2400,7 @@ TYPE_CHECKING = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2430,7 +2430,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -2464,7 +2464,7 @@ f(10) # Error
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -2498,7 +2498,7 @@ class C:
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -2529,7 +2529,7 @@ def g[U, T: U](): ... # error: [invalid-type-variable-bound]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2576,7 +2576,7 @@ U = TypeVar('U', list[int], int) # valid constrained Type
Default level: error ·
Added in 0.0.16 ·
Related issues ·
-View source
+View source
@@ -2608,7 +2608,7 @@ U = TypeVar("U", int, str, default=bytes) # error: [invalid-type-variable-defau
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -2643,7 +2643,7 @@ def f(x: dict):
Default level: error ·
Added in 0.0.9 ·
Related issues ·
-View source
+View source
@@ -2674,7 +2674,7 @@ class Foo(TypedDict):
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -2729,7 +2729,7 @@ def h(arg2: type):
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -2772,7 +2772,7 @@ def g(arg: object):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2797,7 +2797,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -2830,7 +2830,7 @@ alice["age"] # KeyError
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2859,7 +2859,7 @@ func("string") # error: [no-matching-overload]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2885,7 +2885,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2909,7 +2909,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -2942,7 +2942,7 @@ class B(A):
Default level: error ·
Added in 0.0.16 ·
Related issues ·
-View source
+View source
@@ -2975,7 +2975,7 @@ class B(A):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3002,7 +3002,7 @@ f(1, x=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3029,7 +3029,7 @@ f(x=1) # Error raised here
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3057,7 +3057,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3089,7 +3089,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: ignore ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3126,7 +3126,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: ignore ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3190,7 +3190,7 @@ def test(): -> "int":
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3217,7 +3217,7 @@ cast(int, f()) # Redundant
Default level: warn ·
Added in 0.0.18 ·
Related issues ·
-View source
+View source
@@ -3249,7 +3249,7 @@ class C:
Default level: error ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -3283,7 +3283,7 @@ class Outer[T]:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3313,7 +3313,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3342,7 +3342,7 @@ class B(A): ... # Error raised here
Default level: error ·
Added in 0.0.1-alpha.30 ·
Related issues ·
-View source
+View source
@@ -3376,7 +3376,7 @@ class F(NamedTuple):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3403,7 +3403,7 @@ f("foo") # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3431,7 +3431,7 @@ def _(x: int):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3477,7 +3477,7 @@ class A:
Default level: error ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -3514,7 +3514,7 @@ class C(Generic[T]):
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3538,7 +3538,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3565,7 +3565,7 @@ f(x=1, y=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3593,7 +3593,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: warn ·
Added in 0.0.1-alpha.15 ·
Related issues ·
-View source
+View source
@@ -3651,7 +3651,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3676,7 +3676,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3701,7 +3701,7 @@ print(x) # NameError: name 'x' is not defined
Default level: warn ·
Added in 0.0.1-alpha.7 ·
Related issues ·
-View source
+View source
@@ -3740,7 +3740,7 @@ class D(C): ... # error: [unsupported-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3777,7 +3777,7 @@ b1 < b2 < b1 # exception raised here
Default level: ignore ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -3818,7 +3818,7 @@ def factory(base: type[Base]) -> type:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3846,7 +3846,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
Default level: warn ·
Preview (since 0.0.21) ·
Related issues ·
-View source
+View source
@@ -3952,7 +3952,7 @@ to `false`.
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -4015,7 +4015,7 @@ def foo(x: int | str) -> int | str:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs
index 12d4b2ea5f22c0..f3fa233d9eedf3 100644
--- a/crates/ty_python_semantic/src/types.rs
+++ b/crates/ty_python_semantic/src/types.rs
@@ -1,7 +1,7 @@
use compact_str::ToCompactString;
use itertools::Itertools;
use ruff_diagnostics::{Edit, Fix};
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashMap;
use std::borrow::Cow;
use std::cell::RefCell;
@@ -41,8 +41,8 @@ pub(crate) use self::signatures::{CallableSignature, Signature};
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
pub use crate::diagnostic::add_inferred_python_version_hint_to_diagnostic;
use crate::place::{
- DefinedPlace, Definedness, Place, PlaceAndQualifiers, TypeOrigin, Widening,
- builtins_module_scope, imported_symbol, known_module_symbol,
+ DefinedPlace, Definedness, Place, PlaceAndQualifiers, TypeOrigin, builtins_module_scope,
+ imported_symbol, known_module_symbol,
};
use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::semantic_index::place::ScopedPlaceId;
@@ -77,8 +77,11 @@ use crate::types::newtype::NewType;
pub(crate) use crate::types::signatures::{Parameter, Parameters};
use crate::types::signatures::{ParameterForm, walk_signature};
use crate::types::special_form::TypeQualifier;
-use crate::types::tuple::{Tuple, TupleSpec};
+use crate::types::tuple::TupleSpec;
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
+pub use crate::types::typevar::{
+ BindingContext, BoundTypeVarInstance, ParamSpecAttrKind, TypeVarBoundOrConstraints, TypeVarKind,
+};
pub use crate::types::variance::TypeVarVariance;
use crate::types::variance::VarianceInferable;
use crate::types::visitor::any_over_type;
@@ -129,6 +132,7 @@ mod string_annotation;
mod subclass_of;
mod tuple;
mod typed_dict;
+mod typevar;
mod unpacker;
mod variance;
mod visitor;
@@ -238,11 +242,6 @@ pub(crate) struct FindLegacyTypeVars;
pub(crate) type SpecializationVisitor<'db> = CycleDetector, ()>;
pub(crate) struct VisitSpecialization;
-/// A [`CycleDetector`] that is used in `TypeVarInstance::default_type`.
-pub(crate) type TypeVarDefaultVisitor<'db> =
- CycleDetector, Option>>;
-pub(crate) struct VisitTypeVarDefault;
-
/// How a generic type has been specialized.
///
/// This matters only if there is at least one invariant type parameter.
@@ -1182,49 +1181,10 @@ impl<'db> Type<'db> {
)
}
- pub(crate) const fn is_type_var(self) -> bool {
- matches!(self, Type::TypeVar(_))
- }
-
- pub(crate) const fn as_typevar(self) -> Option> {
- match self {
- Type::TypeVar(bound_typevar) => Some(bound_typevar),
- _ => None,
- }
- }
-
- pub(crate) fn has_typevar(self, db: &'db dyn Db) -> bool {
- any_over_type(db, self, false, |ty| matches!(ty, Type::TypeVar(_)))
- }
-
- pub(crate) fn has_non_self_typevar(self, db: &'db dyn Db) -> bool {
- any_over_type(
- db,
- self,
- false,
- |ty| matches!(ty, Type::TypeVar(tv) if !tv.typevar(db).is_self(db)),
- )
- }
-
- pub(crate) fn has_unspecialized_type_var(self, db: &'db dyn Db) -> bool {
- any_over_type(db, self, false, |ty| {
- matches!(ty, Type::Dynamic(DynamicType::UnspecializedTypeVar))
- })
- }
-
pub(crate) fn has_dynamic(self, db: &'db dyn Db) -> bool {
any_over_type(db, self, false, |ty| ty.is_dynamic())
}
- pub(crate) fn has_typevar_or_typevar_instance(self, db: &'db dyn Db) -> bool {
- any_over_type(db, self, false, |ty| {
- matches!(
- ty,
- Type::KnownInstance(KnownInstanceType::TypeVar(_)) | Type::TypeVar(_)
- )
- })
- }
-
pub(crate) const fn as_special_form(self) -> Option {
match self {
Type::SpecialForm(special_form) => Some(special_form),
@@ -6733,1287 +6693,6 @@ impl<'db> InvalidTypeExpression<'db> {
}
}
-/// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax,
-/// or an implicit typevar like `Self` was used.
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
-pub enum TypeVarKind {
- /// `T = TypeVar("T")`
- Legacy,
- /// `def foo[T](x: T) -> T: ...`
- Pep695,
- /// `typing.Self`
- TypingSelf,
- /// `P = ParamSpec("P")`
- ParamSpec,
- /// `def foo[**P]() -> None: ...`
- Pep695ParamSpec,
- /// `Alias: typing.TypeAlias = T`
- Pep613Alias,
-}
-
-impl TypeVarKind {
- const fn is_self(self) -> bool {
- matches!(self, Self::TypingSelf)
- }
-
- const fn is_paramspec(self) -> bool {
- matches!(self, Self::ParamSpec | Self::Pep695ParamSpec)
- }
-}
-
-/// The identity of a type variable.
-///
-/// This represents the core identity of a typevar, independent of its bounds or constraints. Two
-/// typevars have the same identity if they represent the same logical typevar, even if their
-/// bounds have been materialized differently.
-#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
-pub struct TypeVarIdentity<'db> {
- /// The name of this TypeVar (e.g. `T`)
- #[returns(ref)]
- pub(crate) name: ast::name::Name,
-
- /// The type var's definition (None if synthesized)
- pub(crate) definition: Option>,
-
- /// The kind of typevar (PEP 695, Legacy, or TypingSelf)
- pub(crate) kind: TypeVarKind,
-}
-
-impl get_size2::GetSize for TypeVarIdentity<'_> {}
-
-impl<'db> TypeVarIdentity<'db> {
- fn with_name_suffix(self, db: &'db dyn Db, suffix: &str) -> Self {
- let name = format!("{}'{}", self.name(db), suffix);
- Self::new(
- db,
- ast::name::Name::from(name),
- self.definition(db),
- self.kind(db),
- )
- }
-}
-
-/// A specific instance of a type variable that has not been bound to a generic context yet.
-///
-/// This is usually not the type that you want; if you are working with a typevar, in a generic
-/// context, which might be specialized to a concrete type, you want [`BoundTypeVarInstance`]. This
-/// type holds information that does not depend on which generic context the typevar is used in.
-///
-/// For a legacy typevar:
-///
-/// ```py
-/// T = TypeVar("T") # [1]
-/// def generic_function(t: T) -> T: ... # [2]
-/// ```
-///
-/// we will create a `TypeVarInstance` for the typevar `T` when it is instantiated. The type of `T`
-/// at `[1]` will be a `KnownInstanceType::TypeVar` wrapping this `TypeVarInstance`. The typevar is
-/// not yet bound to any generic context at this point.
-///
-/// The typevar is used in `generic_function`, which binds it to a new generic context. We will
-/// create a [`BoundTypeVarInstance`] for this new binding of the typevar. The type of `T` at `[2]`
-/// will be a `Type::TypeVar` wrapping this `BoundTypeVarInstance`.
-///
-/// For a PEP 695 typevar:
-///
-/// ```py
-/// def generic_function[T](t: T) -> T: ...
-/// # ╰─────╰─────────── [2]
-/// # ╰─────────────────────── [1]
-/// ```
-///
-/// the typevar is defined and immediately bound to a single generic context. Just like in the
-/// legacy case, we will create a `TypeVarInstance` and [`BoundTypeVarInstance`], and the type of
-/// `T` at `[1]` and `[2]` will be that `TypeVarInstance` and `BoundTypeVarInstance`, respectively.
-#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
-pub struct TypeVarInstance<'db> {
- /// The identity of this typevar
- pub(crate) identity: TypeVarIdentity<'db>,
-
- /// 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 explicitly specified variance of the TypeVar
- explicit_variance: 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>,
-}
-
-// The Salsa heap is tracked separately.
-impl get_size2::GetSize for TypeVarInstance<'_> {}
-
-fn walk_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
- db: &'db dyn Db,
- typevar: TypeVarInstance<'db>,
- visitor: &V,
-) {
- if let Some(bound_or_constraints) = if visitor.should_visit_lazy_type_attributes() {
- typevar.bound_or_constraints(db)
- } else {
- match typevar._bound_or_constraints(db) {
- _ if visitor.should_visit_lazy_type_attributes() => typevar.bound_or_constraints(db),
- Some(TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints)) => {
- Some(bound_or_constraints)
- }
- _ => None,
- }
- } {
- walk_type_var_bounds(db, bound_or_constraints, visitor);
- }
- if let Some(default_type) = if visitor.should_visit_lazy_type_attributes() {
- typevar.default_type(db)
- } else {
- match typevar._default(db) {
- Some(TypeVarDefaultEvaluation::Eager(default_type)) => Some(default_type),
- _ => None,
- }
- } {
- visitor.visit_type(db, default_type);
- }
-}
-
-#[salsa::tracked]
-impl<'db> TypeVarInstance<'db> {
- pub(crate) fn with_binding_context(
- self,
- db: &'db dyn Db,
- binding_context: Definition<'db>,
- ) -> BoundTypeVarInstance<'db> {
- BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context), None)
- }
-
- fn with_name_suffix(self, db: &'db dyn Db, suffix: &str) -> Self {
- Self::new(
- db,
- self.identity(db).with_name_suffix(db, suffix),
- self._bound_or_constraints(db),
- self.explicit_variance(db),
- self._default(db),
- )
- }
-
- pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
- self.identity(db).name(db)
- }
-
- pub(crate) fn definition(self, db: &'db dyn Db) -> Option> {
- self.identity(db).definition(db)
- }
-
- pub fn kind(self, db: &'db dyn Db) -> TypeVarKind {
- self.identity(db).kind(db)
- }
-
- pub(crate) fn is_self(self, db: &'db dyn Db) -> bool {
- matches!(self.kind(db), TypeVarKind::TypingSelf)
- }
-
- pub(crate) fn is_paramspec(self, db: &'db dyn Db) -> bool {
- self.kind(db).is_paramspec()
- }
-
- pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option> {
- if let Some(TypeVarBoundOrConstraints::UpperBound(ty)) = self.bound_or_constraints(db) {
- Some(ty)
- } else {
- None
- }
- }
-
- pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&'db [Type<'db>]> {
- if let Some(TypeVarBoundOrConstraints::Constraints(tuple)) = self.bound_or_constraints(db) {
- Some(tuple.elements(db))
- } else {
- None
- }
- }
-
- 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)
- .map(TypeVarBoundOrConstraints::UpperBound),
- TypeVarBoundOrConstraintsEvaluation::LazyConstraints => self
- .lazy_constraints(db)
- .map(TypeVarBoundOrConstraints::Constraints),
- })
- }
-
- /// Returns the bounds or constraints of this typevar. If the typevar is unbounded, returns
- /// `object` as its upper bound.
- pub(crate) fn require_bound_or_constraints(
- self,
- db: &'db dyn Db,
- ) -> TypeVarBoundOrConstraints<'db> {
- self.bound_or_constraints(db)
- .unwrap_or_else(|| TypeVarBoundOrConstraints::UpperBound(Type::object()))
- }
-
- pub(crate) fn default_type(self, db: &'db dyn Db) -> Option> {
- let visitor = TypeVarDefaultVisitor::new(None);
- self.default_type_impl(db, &visitor)
- }
-
- fn default_type_impl(
- self,
- db: &'db dyn Db,
- visitor: &TypeVarDefaultVisitor<'db>,
- ) -> Option> {
- visitor.visit(self, || {
- self._default(db).and_then(|default| match default {
- TypeVarDefaultEvaluation::Eager(ty) => Some(ty),
- TypeVarDefaultEvaluation::Lazy => self.lazy_default_impl(db, visitor),
- })
- })
- }
-
- fn materialize_impl(
- self,
- db: &'db dyn Db,
- materialization_kind: MaterializationKind,
- visitor: &ApplyTypeMappingVisitor<'db>,
- ) -> Self {
- Self::new(
- db,
- self.identity(db),
- self._bound_or_constraints(db)
- .and_then(|bound_or_constraints| match bound_or_constraints {
- TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => Some(
- bound_or_constraints
- .materialize_impl(db, materialization_kind, visitor)
- .into(),
- ),
- TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => {
- self.lazy_bound(db).map(|bound| {
- TypeVarBoundOrConstraints::UpperBound(bound)
- .materialize_impl(db, materialization_kind, visitor)
- .into()
- })
- }
- TypeVarBoundOrConstraintsEvaluation::LazyConstraints => {
- self.lazy_constraints(db).map(|constraints| {
- TypeVarBoundOrConstraints::Constraints(constraints)
- .materialize_impl(db, materialization_kind, visitor)
- .into()
- })
- }
- }),
- self.explicit_variance(db),
- self._default(db).and_then(|default| match default {
- TypeVarDefaultEvaluation::Eager(ty) => {
- Some(ty.materialize(db, materialization_kind, visitor).into())
- }
- TypeVarDefaultEvaluation::Lazy => self
- .lazy_default(db)
- .map(|ty| ty.materialize(db, materialization_kind, visitor).into()),
- }),
- )
- }
-
- fn to_instance(self, db: &'db dyn Db) -> Option {
- let bound_or_constraints = match self.bound_or_constraints(db)? {
- TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
- TypeVarBoundOrConstraints::UpperBound(upper_bound.to_instance(db)?)
- }
- TypeVarBoundOrConstraints::Constraints(constraints) => {
- TypeVarBoundOrConstraints::Constraints(constraints.to_instance(db)?)
- }
- };
- let identity = TypeVarIdentity::new(
- db,
- Name::new(format!("{}'instance", self.name(db))),
- None, // definition
- self.kind(db),
- );
- Some(Self::new(
- db,
- identity,
- Some(bound_or_constraints.into()),
- self.explicit_variance(db),
- None, // _default
- ))
- }
-
- fn type_is_self_referential(
- self,
- db: &'db dyn Db,
- ty: Type<'db>,
- visitor: &TypeVarDefaultVisitor<'db>,
- ) -> bool {
- #[derive(Copy, Clone)]
- struct State<'db, 'a> {
- db: &'db dyn Db,
- visitor: &'a TypeVarDefaultVisitor<'db>,
- seen_typevars: &'a RefCell>>,
- seen_type_aliases: &'a RefCell>>,
- }
-
- fn typevar_default_is_self_referential<'db>(
- state: State<'db, '_>,
- typevar: TypeVarInstance<'db>,
- self_identity: TypeVarIdentity<'db>,
- ) -> bool {
- if typevar.identity(state.db) == self_identity {
- return true;
- }
-
- if !state.seen_typevars.borrow_mut().insert(typevar) {
- return false;
- }
-
- typevar
- .default_type_impl(state.db, state.visitor)
- .is_some_and(|default_ty| {
- type_is_self_referential_impl(state, default_ty, self_identity)
- })
- }
-
- fn type_alias_is_self_referential<'db>(
- state: State<'db, '_>,
- type_alias: TypeAliasType<'db>,
- self_identity: TypeVarIdentity<'db>,
- ) -> bool {
- if !state.seen_type_aliases.borrow_mut().insert(type_alias) {
- return false;
- }
-
- type_is_self_referential_impl(state, type_alias.raw_value_type(state.db), self_identity)
- }
-
- fn type_is_self_referential_impl<'db>(
- state: State<'db, '_>,
- ty: Type<'db>,
- self_identity: TypeVarIdentity<'db>,
- ) -> bool {
- any_over_type(state.db, ty, false, |inner_ty| match inner_ty {
- Type::TypeVar(bound_typevar) => typevar_default_is_self_referential(
- state,
- bound_typevar.typevar(state.db),
- self_identity,
- ),
- Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => {
- typevar_default_is_self_referential(state, typevar, self_identity)
- }
- Type::TypeAlias(alias) => {
- type_alias_is_self_referential(state, alias, self_identity)
- }
- Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => {
- type_alias_is_self_referential(state, alias, self_identity)
- }
- _ => false,
- })
- }
-
- let seen_typevars = RefCell::new(FxHashSet::default());
- let seen_type_aliases = RefCell::new(FxHashSet::default());
-
- let state = State {
- db,
- visitor,
- seen_typevars: &seen_typevars,
- seen_type_aliases: &seen_type_aliases,
- };
-
- type_is_self_referential_impl(state, ty, self.identity(db))
- }
-
- /// Returns the "unchecked" upper bound of a type variable instance.
- /// `lazy_bound` checks if the upper bound type is generic (generic upper bound is not allowed).
- #[salsa::tracked(
- cycle_fn=lazy_bound_cycle_recover,
- cycle_initial=|_, _, _| None,
- heap_size=ruff_memory_usage::heap_size
- )]
- fn lazy_bound_unchecked(self, db: &'db dyn Db) -> Option> {
- let definition = self.definition(db)?;
- let module = parsed_module(db, definition.file(db)).load(db);
- let ty = match definition.kind(db) {
- // PEP 695 typevar
- DefinitionKind::TypeVar(typevar) => {
- let typevar_node = typevar.node(&module);
- definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
- }
- // legacy typevar
- DefinitionKind::Assignment(assignment) => {
- let call_expr = assignment.value(&module).as_call_expr()?;
- let expr = &call_expr.arguments.find_keyword("bound")?.value;
- definition_expression_type(db, definition, expr)
- }
- _ => return None,
- };
-
- Some(ty)
- }
-
- fn lazy_bound(self, db: &'db dyn Db) -> Option> {
- let bound = self.lazy_bound_unchecked(db)?;
-
- if bound.has_typevar_or_typevar_instance(db) {
- return None;
- }
-
- Some(bound)
- }
-
- /// Returns the "unchecked" constraints of a type variable instance.
- /// `lazy_constraints` checks if any of the constraint types are generic (generic constraints are not allowed).
- #[salsa::tracked(
- cycle_fn=lazy_constraints_cycle_recover,
- cycle_initial=|_, _, _| None,
- heap_size=ruff_memory_usage::heap_size
- )]
- fn lazy_constraints_unchecked(self, db: &'db dyn Db) -> Option> {
- let definition = self.definition(db)?;
- let module = parsed_module(db, definition.file(db)).load(db);
- let constraints = match definition.kind(db) {
- // PEP 695 typevar
- DefinitionKind::TypeVar(typevar) => {
- let typevar_node = typevar.node(&module);
- let bound =
- definition_expression_type(db, definition, typevar_node.bound.as_ref()?);
- let constraints = if let Some(tuple) = bound.tuple_instance_spec(db)
- && let Tuple::Fixed(tuple) = tuple.into_owned()
- {
- tuple.owned_elements()
- } else {
- vec![Type::unknown()].into_boxed_slice()
- };
- TypeVarConstraints::new(db, constraints)
- }
- // legacy typevar
- DefinitionKind::Assignment(assignment) => {
- let call_expr = assignment.value(&module).as_call_expr()?;
- TypeVarConstraints::new(
- db,
- call_expr
- .arguments
- .args
- .iter()
- .skip(1)
- .map(|arg| definition_expression_type(db, definition, arg))
- .collect::>(),
- )
- }
- _ => return None,
- };
-
- Some(constraints)
- }
-
- fn lazy_constraints(self, db: &'db dyn Db) -> Option> {
- let constraints = self.lazy_constraints_unchecked(db)?;
-
- if constraints
- .elements(db)
- .iter()
- .any(|ty| ty.has_typevar_or_typevar_instance(db))
- {
- return None;
- }
-
- Some(constraints)
- }
-
- /// Returns the "unchecked" default type of a type variable instance.
- /// `lazy_default` checks if the default type is not self-referential.
- #[salsa::tracked(cycle_initial=|_, id, _| Some(Type::divergent(id)), cycle_fn=lazy_default_cycle_recover, heap_size=ruff_memory_usage::heap_size)]
- fn lazy_default_unchecked(self, db: &'db dyn Db) -> Option> {
- fn convert_type_to_paramspec_value<'db>(db: &'db dyn Db, ty: Type<'db>) -> Type<'db> {
- let parameters = match ty {
- Type::NominalInstance(nominal_instance)
- if nominal_instance.has_known_class(db, KnownClass::EllipsisType) =>
- {
- Parameters::gradual_form()
- }
- Type::NominalInstance(nominal_instance) => nominal_instance
- .own_tuple_spec(db)
- .map_or_else(Parameters::unknown, |tuple_spec| {
- Parameters::new(
- db,
- tuple_spec
- .iter_all_elements()
- .map(|ty| Parameter::positional_only(None).with_annotated_type(ty)),
- )
- }),
- Type::Dynamic(dynamic) => match dynamic {
- DynamicType::Todo(_)
- | DynamicType::TodoUnpack
- | DynamicType::TodoStarredExpression
- | DynamicType::TodoFunctionalTypedDict
- | DynamicType::TodoTypeVarTuple => Parameters::todo(),
- DynamicType::Any
- | DynamicType::Unknown
- | DynamicType::UnknownGeneric(_)
- | DynamicType::UnspecializedTypeVar
- | DynamicType::Divergent(_) => Parameters::unknown(),
- },
- Type::TypeVar(typevar) if typevar.is_paramspec(db) => {
- return ty;
- }
- Type::KnownInstance(KnownInstanceType::TypeVar(typevar))
- if typevar.is_paramspec(db) =>
- {
- return ty;
- }
- _ => Parameters::unknown(),
- };
- Type::paramspec_value_callable(db, parameters)
- }
-
- let definition = self.definition(db)?;
- let module = parsed_module(db, definition.file(db)).load(db);
- let ty = match definition.kind(db) {
- // PEP 695 typevar
- DefinitionKind::TypeVar(typevar) => {
- let typevar_node = typevar.node(&module);
- definition_expression_type(db, definition, typevar_node.default.as_ref()?)
- }
- // legacy typevar / ParamSpec
- DefinitionKind::Assignment(assignment) => {
- let call_expr = assignment.value(&module).as_call_expr()?;
- let func_ty = definition_expression_type(db, definition, &call_expr.func);
- let known_class = func_ty.as_class_literal().and_then(|cls| cls.known(db));
- let expr = &call_expr.arguments.find_keyword("default")?.value;
- let default_type = definition_expression_type(db, definition, expr);
- if known_class == Some(KnownClass::ParamSpec) {
- convert_type_to_paramspec_value(db, default_type)
- } else {
- default_type
- }
- }
- // PEP 695 ParamSpec
- DefinitionKind::ParamSpec(paramspec) => {
- let paramspec_node = paramspec.node(&module);
- let default_ty =
- definition_expression_type(db, definition, paramspec_node.default.as_ref()?);
- convert_type_to_paramspec_value(db, default_ty)
- }
- _ => return None,
- };
-
- Some(ty)
- }
-
- fn lazy_default(self, db: &'db dyn Db) -> Option> {
- let visitor = TypeVarDefaultVisitor::new(None);
- self.lazy_default_impl(db, &visitor)
- }
-
- fn lazy_default_impl(
- self,
- db: &'db dyn Db,
- visitor: &TypeVarDefaultVisitor<'db>,
- ) -> Option> {
- let default = self.lazy_default_unchecked(db)?;
-
- // Unlike bounds/constraints, default types are allowed to be generic (https://peps.python.org/pep-0696/#using-another-type-parameter-as-default).
- // Here we simply check for non-self-referential.
- // TODO: We should also check for non-forward references.
- if self.type_is_self_referential(db, default, visitor) {
- return None;
- }
-
- Some(default)
- }
-
- pub fn bind_pep695(self, db: &'db dyn Db) -> Option> {
- if !matches!(
- self.identity(db).kind(db),
- TypeVarKind::Pep695 | TypeVarKind::Pep695ParamSpec
- ) {
- return None;
- }
- let typevar_definition = self.definition(db)?;
- let index = semantic_index(db, typevar_definition.file(db));
- let (_, child) = index
- .child_scopes(typevar_definition.file_scope(db))
- .next()?;
- child
- .node()
- .generic_context(db, index)?
- .binds_typevar(db, self)
- }
-}
-
-#[expect(clippy::ref_option)]
-fn lazy_bound_cycle_recover<'db>(
- db: &'db dyn Db,
- cycle: &salsa::Cycle,
- previous: &Option>,
- current: Option>,
- _typevar: TypeVarInstance<'db>,
-) -> Option> {
- // Normalize the bounds/constraints to ensure cycle convergence.
- match (previous, current) {
- (Some(prev), Some(current)) => Some(current.cycle_normalized(db, *prev, cycle)),
- (None, Some(current)) => Some(current.recursive_type_normalized(db, cycle)),
- (_, None) => None,
- }
-}
-
-#[allow(clippy::trivially_copy_pass_by_ref)]
-#[expect(clippy::ref_option)]
-fn lazy_constraints_cycle_recover<'db>(
- db: &'db dyn Db,
- cycle: &salsa::Cycle,
- previous: &Option>,
- current: Option>,
- _typevar: TypeVarInstance<'db>,
-) -> Option> {
- // Normalize the bounds/constraints to ensure cycle convergence.
- match (previous, current) {
- (Some(prev), Some(constraints)) => Some(constraints.cycle_normalized(db, *prev, cycle)),
- (None, Some(current)) => Some(current.recursive_type_normalized(db, cycle)),
- (_, None) => None,
- }
-}
-
-#[expect(clippy::ref_option)]
-fn lazy_default_cycle_recover<'db>(
- db: &'db dyn Db,
- cycle: &salsa::Cycle,
- previous_default: &Option>,
- default: Option>,
- _typevar: TypeVarInstance<'db>,
-) -> Option> {
- // Normalize the default to ensure cycle convergence.
- match (previous_default, default) {
- (Some(prev), Some(default)) => Some(default.cycle_normalized(db, *prev, cycle)),
- (None, Some(default)) => Some(default.recursive_type_normalized(db, cycle)),
- (_, None) => None,
- }
-}
-
-/// Where a type variable is bound and usable.
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, salsa::Update, get_size2::GetSize)]
-pub enum BindingContext<'db> {
- /// The definition of the generic class, function, or type alias that binds this typevar.
- Definition(Definition<'db>),
- /// The typevar is synthesized internally, and is not associated with a particular definition
- /// in the source, but is still bound and eligible for specialization inference.
- Synthetic,
-}
-
-impl<'db> From> for BindingContext<'db> {
- fn from(definition: Definition<'db>) -> Self {
- BindingContext::Definition(definition)
- }
-}
-
-impl<'db> BindingContext<'db> {
- pub(crate) fn definition(self) -> Option> {
- match self {
- BindingContext::Definition(definition) => Some(definition),
- BindingContext::Synthetic => None,
- }
- }
-
- fn name(self, db: &'db dyn Db) -> Option {
- self.definition().and_then(|definition| definition.name(db))
- }
-}
-
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, get_size2::GetSize)]
-pub enum ParamSpecAttrKind {
- Args,
- Kwargs,
-}
-
-impl std::fmt::Display for ParamSpecAttrKind {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- ParamSpecAttrKind::Args => f.write_str("args"),
- ParamSpecAttrKind::Kwargs => f.write_str("kwargs"),
- }
- }
-}
-
-/// The identity of a bound type variable.
-///
-/// This identifies a specific binding of a typevar to a context (e.g., `T@ClassC` vs `T@FunctionF`),
-/// independent of the typevar's bounds or constraints. Two bound typevars have the same identity
-/// if they represent the same logical typevar bound in the same context, even if their bounds
-/// have been materialized differently.
-#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, get_size2::GetSize, salsa::Update)]
-pub struct BoundTypeVarIdentity<'db> {
- pub(crate) identity: TypeVarIdentity<'db>,
- pub(crate) binding_context: BindingContext<'db>,
- /// If [`Some`], this indicates that this type variable is the `args` or `kwargs` component
- /// of a `ParamSpec` i.e., `P.args` or `P.kwargs`.
- paramspec_attr: Option,
-}
-
-/// A type variable that has been bound to a generic context, and which can be specialized to a
-/// concrete type.
-#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
-pub struct BoundTypeVarInstance<'db> {
- pub typevar: TypeVarInstance<'db>,
- binding_context: BindingContext<'db>,
- /// If [`Some`], this indicates that this type variable is the `args` or `kwargs` component
- /// of a `ParamSpec` i.e., `P.args` or `P.kwargs`.
- paramspec_attr: Option,
-}
-
-// The Salsa heap is tracked separately.
-impl get_size2::GetSize for BoundTypeVarInstance<'_> {}
-
-impl<'db> BoundTypeVarInstance<'db> {
- pub(crate) fn with_name_suffix(self, db: &'db dyn Db, suffix: &str) -> Self {
- Self::new(
- db,
- self.typevar(db).with_name_suffix(db, suffix),
- self.binding_context(db),
- self.paramspec_attr(db),
- )
- }
-
- /// Get the identity of this bound typevar.
- ///
- /// This is used for comparing whether two bound typevars represent the same logical typevar,
- /// regardless of e.g. differences in their bounds or constraints due to materialization.
- pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> {
- BoundTypeVarIdentity {
- identity: self.typevar(db).identity(db),
- binding_context: self.binding_context(db),
- paramspec_attr: self.paramspec_attr(db),
- }
- }
-
- pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
- self.typevar(db).name(db)
- }
-
- pub(crate) fn kind(self, db: &'db dyn Db) -> TypeVarKind {
- self.typevar(db).kind(db)
- }
-
- pub(crate) fn is_paramspec(self, db: &'db dyn Db) -> bool {
- self.kind(db).is_paramspec()
- }
-
- /// Returns a new bound typevar instance with the given `ParamSpec` attribute set.
- ///
- /// This method will also set an appropriate upper bound on the typevar, based on the
- /// attribute kind. For `P.args`, the upper bound will be `tuple[object, ...]`, and for
- /// `P.kwargs`, the upper bound will be `Top[dict[str, Any]]`.
- ///
- /// It's the caller's responsibility to ensure that this method is only called on a `ParamSpec`
- /// type variable.
- pub(crate) fn with_paramspec_attr(self, db: &'db dyn Db, kind: ParamSpecAttrKind) -> Self {
- debug_assert!(
- self.is_paramspec(db),
- "Expected a ParamSpec, got {:?}",
- self.kind(db)
- );
-
- let upper_bound = TypeVarBoundOrConstraints::UpperBound(match kind {
- ParamSpecAttrKind::Args => Type::homogeneous_tuple(db, Type::object()),
- ParamSpecAttrKind::Kwargs => KnownClass::Dict
- .to_specialized_instance(db, &[KnownClass::Str.to_instance(db), Type::any()])
- .top_materialization(db),
- });
-
- let typevar = TypeVarInstance::new(
- db,
- self.typevar(db).identity(db),
- Some(TypeVarBoundOrConstraintsEvaluation::Eager(upper_bound)),
- None, // ParamSpecs cannot have explicit variance
- None, // `P.args` and `P.kwargs` cannot have defaults even though `P` can
- );
-
- Self::new(db, typevar, self.binding_context(db), Some(kind))
- }
-
- /// Returns a new bound typevar instance without any `ParamSpec` attribute set.
- ///
- /// This method will also remove any upper bound that was set by `with_paramspec_attr`. This
- /// means that the returned typevar will have no upper bound or constraints.
- ///
- /// It's the caller's responsibility to ensure that this method is only called on a `ParamSpec`
- /// type variable.
- pub(crate) fn without_paramspec_attr(self, db: &'db dyn Db) -> Self {
- debug_assert!(
- self.is_paramspec(db),
- "Expected a ParamSpec, got {:?}",
- self.kind(db)
- );
-
- Self::new(
- db,
- TypeVarInstance::new(
- db,
- self.typevar(db).identity(db),
- None, // Remove the upper bound set by `with_paramspec_attr`
- None, // ParamSpecs cannot have explicit variance
- None, // `P.args` and `P.kwargs` cannot have defaults even though `P` can
- ),
- self.binding_context(db),
- None,
- )
- }
-
- /// Returns whether two bound typevars represent the same logical typevar, regardless of e.g.
- /// differences in their bounds or constraints due to materialization.
- pub(crate) fn is_same_typevar_as(self, db: &'db dyn Db, other: Self) -> bool {
- self.identity(db) == other.identity(db)
- }
-
- /// Create a new PEP 695 type variable that can be used in signatures
- /// of synthetic generic functions.
- pub(crate) fn synthetic(db: &'db dyn Db, name: Name, variance: TypeVarVariance) -> Self {
- let identity = TypeVarIdentity::new(
- db,
- name,
- None, // definition
- TypeVarKind::Pep695,
- );
- let typevar = TypeVarInstance::new(
- db,
- identity,
- None, // _bound_or_constraints
- Some(variance),
- None, // _default
- );
- Self::new(db, typevar, BindingContext::Synthetic, None)
- }
-
- /// Create a new synthetic `Self` type variable with the given upper bound.
- pub(crate) fn synthetic_self(
- db: &'db dyn Db,
- upper_bound: Type<'db>,
- binding_context: BindingContext<'db>,
- ) -> Self {
- let identity = TypeVarIdentity::new(
- db,
- Name::new_static("Self"),
- None, // definition
- TypeVarKind::TypingSelf,
- );
- let typevar = TypeVarInstance::new(
- db,
- identity,
- Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()),
- Some(TypeVarVariance::Invariant),
- None, // _default
- );
- Self::new(db, typevar, binding_context, None)
- }
-
- /// Returns an identical type variable with its `TypeVarBoundOrConstraints` mapped by the
- /// provided closure.
- pub(crate) fn map_bound_or_constraints(
- self,
- db: &'db dyn Db,
- f: impl FnOnce(Option>) -> Option>,
- ) -> Self {
- let bound_or_constraints = f(self.typevar(db).bound_or_constraints(db));
- let typevar = TypeVarInstance::new(
- db,
- self.typevar(db).identity(db),
- bound_or_constraints.map(TypeVarBoundOrConstraintsEvaluation::Eager),
- self.typevar(db).explicit_variance(db),
- self.typevar(db)._default(db),
- );
-
- Self::new(
- db,
- typevar,
- self.binding_context(db),
- self.paramspec_attr(db),
- )
- }
-
- pub(crate) fn variance_with_polarity(
- self,
- db: &'db dyn Db,
- polarity: TypeVarVariance,
- ) -> TypeVarVariance {
- let _span = tracing::trace_span!("variance_with_polarity").entered();
- match self.typevar(db).explicit_variance(db) {
- Some(explicit_variance) => explicit_variance.compose(polarity),
- None => match self.binding_context(db) {
- BindingContext::Definition(definition) => binding_type(db, definition)
- .with_polarity(polarity)
- .variance_of(db, self),
- BindingContext::Synthetic => TypeVarVariance::Invariant,
- },
- }
- }
-
- pub fn variance(self, db: &'db dyn Db) -> TypeVarVariance {
- self.variance_with_polarity(db, TypeVarVariance::Covariant)
- }
-
- fn apply_type_mapping_impl<'a>(
- self,
- db: &'db dyn Db,
- type_mapping: &TypeMapping<'a, 'db>,
- visitor: &ApplyTypeMappingVisitor<'db>,
- ) -> Type<'db> {
- match type_mapping {
- TypeMapping::ApplySpecialization(specialization) => {
- let typevar = if self.is_paramspec(db) {
- self.without_paramspec_attr(db)
- } else {
- self
- };
- specialization
- .get(db, typevar)
- .map(|ty| {
- if let Some(attr) = self.paramspec_attr(db)
- && let Type::TypeVar(typevar) = ty
- && typevar.is_paramspec(db)
- {
- return Type::TypeVar(typevar.with_paramspec_attr(db, attr));
- }
- ty
- })
- .unwrap_or(Type::TypeVar(self))
- }
- TypeMapping::BindSelf(binding) => {
- if binding.should_bind(db, self) {
- binding.self_type()
- } else {
- Type::TypeVar(self)
- }
- }
- TypeMapping::ReplaceSelf { new_upper_bound } => {
- if self.typevar(db).is_self(db) {
- Type::TypeVar(BoundTypeVarInstance::synthetic_self(
- db,
- *new_upper_bound,
- self.binding_context(db),
- ))
- } else {
- Type::TypeVar(self)
- }
- }
- TypeMapping::UniqueSpecialization { .. }
- | TypeMapping::PromoteLiterals(_)
- | TypeMapping::ReplaceParameterDefaults
- | TypeMapping::BindLegacyTypevars(_)
- | TypeMapping::EagerExpansion
- | TypeMapping::RescopeReturnCallables(_) => Type::TypeVar(self),
- TypeMapping::Materialize(materialization_kind) => {
- Type::TypeVar(self.materialize_impl(db, *materialization_kind, visitor))
- }
- }
- }
-}
-
-fn walk_bound_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
- db: &'db dyn Db,
- bound_typevar: BoundTypeVarInstance<'db>,
- visitor: &V,
-) {
- visitor.visit_type_var_type(db, bound_typevar.typevar(db));
-}
-
-impl<'db> BoundTypeVarInstance<'db> {
- /// Returns the default value of this typevar, recursively applying its binding context to any
- /// other typevars that appear in the default.
- ///
- /// For instance, in
- ///
- /// ```py
- /// T = TypeVar("T")
- /// U = TypeVar("U", default=T)
- ///
- /// # revealed: typing.TypeVar[U = typing.TypeVar[T]]
- /// reveal_type(U)
- ///
- /// # revealed: typing.Generic[T, U = T@C]
- /// class C(reveal_type(Generic[T, U])): ...
- /// ```
- ///
- /// In the first case, the use of `U` is unbound, and so we have a [`TypeVarInstance`], and its
- /// default value (`T`) is also unbound.
- ///
- /// 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_type(self, db: &'db dyn Db) -> Option> {
- bound_typevar_default_type(db, self)
- }
-
- fn materialize_impl(
- self,
- db: &'db dyn Db,
- materialization_kind: MaterializationKind,
- visitor: &ApplyTypeMappingVisitor<'db>,
- ) -> Self {
- Self::new(
- db,
- self.typevar(db)
- .materialize_impl(db, materialization_kind, visitor),
- self.binding_context(db),
- self.paramspec_attr(db),
- )
- }
-
- fn to_instance(self, db: &'db dyn Db) -> Option {
- Some(Self::new(
- db,
- self.typevar(db).to_instance(db)?,
- self.binding_context(db),
- self.paramspec_attr(db),
- ))
- }
-}
-
-#[salsa::tracked(
- cycle_initial=|_, _, _| None,
- cycle_fn=bound_typevar_default_type_cycle_recover,
- heap_size=ruff_memory_usage::heap_size
-)]
-fn bound_typevar_default_type<'db>(
- db: &'db dyn Db,
- bound_typevar: BoundTypeVarInstance<'db>,
-) -> Option> {
- let binding_context = bound_typevar.binding_context(db);
- bound_typevar.typevar(db).default_type(db).map(|ty| {
- ty.apply_type_mapping(
- db,
- &TypeMapping::BindLegacyTypevars(binding_context),
- TypeContext::default(),
- )
- })
-}
-
-#[expect(clippy::ref_option)]
-fn bound_typevar_default_type_cycle_recover<'db>(
- _db: &'db dyn Db,
- _cycle: &salsa::Cycle,
- _previous_default: &Option>,
- _default: Option>,
- _bound_typevar: BoundTypeVarInstance<'db>,
-) -> Option> {
- None
-}
-
-/// 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)
- }
-}
-
-/// Type variable constraints (e.g. `T: (int, str)`).
-/// This is structurally identical to [`UnionType`], except that it does not perform simplification and preserves the element types.
-#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
-pub struct TypeVarConstraints<'db> {
- #[returns(ref)]
- elements: Box<[Type<'db>]>,
-}
-
-impl get_size2::GetSize for TypeVarConstraints<'_> {}
-
-fn walk_type_var_constraints<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
- db: &'db dyn Db,
- constraints: TypeVarConstraints<'db>,
- visitor: &V,
-) {
- for ty in constraints.elements(db) {
- visitor.visit_type(db, *ty);
- }
-}
-
-impl<'db> TypeVarConstraints<'db> {
- fn as_type(self, db: &'db dyn Db) -> Type<'db> {
- UnionType::from_elements(db, self.elements(db))
- }
-
- fn to_instance(self, db: &'db dyn Db) -> Option> {
- let mut instance_elements = Vec::new();
- for ty in self.elements(db) {
- instance_elements.push(ty.to_instance(db)?);
- }
- Some(TypeVarConstraints::new(
- db,
- instance_elements.into_boxed_slice(),
- ))
- }
-
- fn map(self, db: &'db dyn Db, transform_fn: impl FnMut(&Type<'db>) -> Type<'db>) -> Self {
- let mapped = self
- .elements(db)
- .iter()
- .map(transform_fn)
- .collect::>();
- TypeVarConstraints::new(db, mapped)
- }
-
- pub(crate) fn map_with_boundness_and_qualifiers(
- self,
- db: &'db dyn Db,
- mut transform_fn: impl FnMut(&Type<'db>) -> PlaceAndQualifiers<'db>,
- ) -> PlaceAndQualifiers<'db> {
- let mut builder = UnionBuilder::new(db);
- let mut qualifiers = TypeQualifiers::empty();
-
- let mut all_unbound = true;
- let mut possibly_unbound = false;
- let mut origin = TypeOrigin::Declared;
- for ty in self.elements(db) {
- let PlaceAndQualifiers {
- place: ty_member,
- qualifiers: new_qualifiers,
- } = transform_fn(ty);
- qualifiers |= new_qualifiers;
- match ty_member {
- Place::Undefined => {
- possibly_unbound = true;
- }
- Place::Defined(DefinedPlace {
- ty: ty_member,
- origin: member_origin,
- definedness: member_boundness,
- ..
- }) => {
- origin = origin.merge(member_origin);
- if member_boundness == Definedness::PossiblyUndefined {
- possibly_unbound = true;
- }
-
- all_unbound = false;
- builder = builder.add(ty_member);
- }
- }
- }
- PlaceAndQualifiers {
- place: if all_unbound {
- Place::Undefined
- } else {
- Place::Defined(DefinedPlace {
- ty: builder.build(),
- origin,
- definedness: if possibly_unbound {
- Definedness::PossiblyUndefined
- } else {
- Definedness::AlwaysDefined
- },
- widening: Widening::None,
- })
- },
- qualifiers,
- }
- }
-
- fn materialize_impl(
- self,
- db: &'db dyn Db,
- materialization_kind: MaterializationKind,
- visitor: &ApplyTypeMappingVisitor<'db>,
- ) -> Self {
- let materialized = self
- .elements(db)
- .iter()
- .map(|ty| ty.materialize(db, materialization_kind, visitor))
- .collect::>();
- TypeVarConstraints::new(db, materialized)
- }
-
- /// Normalize for cycle recovery by combining with the previous value and
- /// removing divergent types introduced by the cycle.
- ///
- /// See [`Type::cycle_normalized`] for more details on how this works.
- fn cycle_normalized(self, db: &'db dyn Db, previous: Self, cycle: &salsa::Cycle) -> Self {
- let current_elements = self.elements(db);
- let prev_elements = previous.elements(db);
- TypeVarConstraints::new(
- db,
- current_elements
- .iter()
- .zip(prev_elements.iter())
- .map(|(ty, prev_ty)| ty.cycle_normalized(db, *prev_ty, cycle))
- .collect::>(),
- )
- }
-
- /// Normalize recursive types for cycle recovery when there's no previous value.
- ///
- /// See [`Type::recursive_type_normalized`] for more details.
- fn recursive_type_normalized(self, db: &'db dyn Db, cycle: &salsa::Cycle) -> Self {
- self.map(db, |ty| ty.recursive_type_normalized(db, cycle))
- }
-}
-
-#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
-pub enum TypeVarBoundOrConstraints<'db> {
- UpperBound(Type<'db>),
- Constraints(TypeVarConstraints<'db>),
-}
-
-fn walk_type_var_bounds<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
- db: &'db dyn Db,
- bounds: TypeVarBoundOrConstraints<'db>,
- visitor: &V,
-) {
- match bounds {
- TypeVarBoundOrConstraints::UpperBound(bound) => visitor.visit_type(db, bound),
- TypeVarBoundOrConstraints::Constraints(constraints) => {
- walk_type_var_constraints(db, constraints, visitor);
- }
- }
-}
-
-impl<'db> TypeVarBoundOrConstraints<'db> {
- fn materialize_impl(
- self,
- db: &'db dyn Db,
- materialization_kind: MaterializationKind,
- visitor: &ApplyTypeMappingVisitor<'db>,
- ) -> Self {
- match self {
- TypeVarBoundOrConstraints::UpperBound(bound) => TypeVarBoundOrConstraints::UpperBound(
- bound.materialize(db, materialization_kind, visitor),
- ),
- TypeVarBoundOrConstraints::Constraints(constraints) => {
- TypeVarBoundOrConstraints::Constraints(constraints.materialize_impl(
- db,
- materialization_kind,
- visitor,
- ))
- }
- }
- }
-}
-
/// Whether a given type originates from value expression inference or type expression inference.
/// For example, the symbol `int` would be inferred as `` in value expression context,
/// and as `int` (i.e. an instance of the class `int`) in type expression context.
diff --git a/crates/ty_python_semantic/src/types/bound_super.rs b/crates/ty_python_semantic/src/types/bound_super.rs
index f850a628bd4e08..07c6aa12c1d7bd 100644
--- a/crates/ty_python_semantic/src/types/bound_super.rs
+++ b/crates/ty_python_semantic/src/types/bound_super.rs
@@ -10,12 +10,14 @@ use crate::{
types::{
BoundTypeVarInstance, ClassBase, ClassType, DynamicType, IntersectionBuilder, KnownClass,
MemberLookupPolicy, NominalInstanceType, SpecialFormType, SubclassOfInner, SubclassOfType,
- Type, TypeVarBoundOrConstraints, TypeVarConstraints, TypeVarInstance, UnionBuilder,
+ Type, TypeVarBoundOrConstraints, UnionBuilder,
constraints::{ConstraintSet, ConstraintSetBuilder},
context::InferContext,
diagnostic::{INVALID_SUPER_ARGUMENT, UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS},
relation::{HasRelationToVisitor, IsDisjointVisitor},
- todo_type, visitor,
+ todo_type,
+ typevar::{TypeVarConstraints, TypeVarInstance},
+ visitor,
},
};
diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs
index 9095aadf181446..22c6017c03c703 100644
--- a/crates/ty_python_semantic/src/types/call/bind.rs
+++ b/crates/ty_python_semantic/src/types/call/bind.rs
@@ -44,13 +44,14 @@ use crate::types::generics::{
use crate::types::known_instance::FieldInstance;
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
use crate::types::tuple::{TupleLength, TupleSpec, TupleType};
+use crate::types::typevar::BoundTypeVarIdentity;
use crate::types::{
- BoundMethodType, BoundTypeVarIdentity, BoundTypeVarInstance, CallableSignature, CallableType,
- CallableTypeKind, ClassLiteral, DATACLASS_FLAGS, DataclassFlags, DataclassParams,
- EvaluationMode, GenericAlias, InternedConstraintSet, IntersectionType, KnownBoundMethodType,
- KnownClass, KnownInstanceType, LiteralValueTypeKind, MemberLookupPolicy, NominalInstanceType,
- PropertyInstanceType, SpecialFormType, TypeAliasType, TypeContext, TypeVarBoundOrConstraints,
- TypeVarVariance, UnionBuilder, UnionType, WrapperDescriptorKind, enums, list_members,
+ BoundMethodType, BoundTypeVarInstance, CallableSignature, CallableType, CallableTypeKind,
+ ClassLiteral, DATACLASS_FLAGS, DataclassFlags, DataclassParams, EvaluationMode, GenericAlias,
+ InternedConstraintSet, IntersectionType, KnownBoundMethodType, KnownClass, KnownInstanceType,
+ LiteralValueTypeKind, MemberLookupPolicy, NominalInstanceType, PropertyInstanceType,
+ SpecialFormType, TypeAliasType, TypeContext, TypeVarBoundOrConstraints, TypeVarVariance,
+ UnionBuilder, UnionType, WrapperDescriptorKind, enums, list_members,
};
use crate::{DisplaySettings, Program};
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs
index b75d19c18b4d4a..2b011db3b3001b 100644
--- a/crates/ty_python_semantic/src/types/constraints.rs
+++ b/crates/ty_python_semantic/src/types/constraints.rs
@@ -80,12 +80,12 @@ use smallvec::SmallVec;
use crate::types::class::GenericAlias;
use crate::types::generics::InferableTypeVars;
+use crate::types::typevar::{BoundTypeVarIdentity, walk_bound_type_var_type};
use crate::types::visitor::{
TypeCollector, TypeVisitor, any_over_type, walk_type_with_recursion_guard,
};
use crate::types::{
- BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeVarBoundOrConstraints,
- UnionType, walk_bound_type_var_type,
+ BoundTypeVarInstance, IntersectionType, Type, TypeVarBoundOrConstraints, UnionType,
};
use crate::{Db, FxIndexMap, FxIndexSet};
diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs
index a44931bef59e2f..20622861afbc75 100644
--- a/crates/ty_python_semantic/src/types/diagnostic.rs
+++ b/crates/ty_python_semantic/src/types/diagnostic.rs
@@ -26,12 +26,13 @@ use crate::types::string_annotation::{
};
use crate::types::tuple::TupleSpec;
use crate::types::typed_dict::TypedDictSchema;
+use crate::types::typevar::TypeVarInstance;
use crate::types::{
BoundTypeVarInstance, ClassType, DynamicType, LintDiagnosticGuard, Protocol,
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
protocol_class::ProtocolClass,
};
-use crate::types::{KnownInstanceType, MemberLookupPolicy, TypeVarInstance, UnionType};
+use crate::types::{KnownInstanceType, MemberLookupPolicy, UnionType};
use crate::{Db, DisplaySettings, FxIndexMap, Program, declare_lint};
use itertools::Itertools;
use ruff_db::{
diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs
index 2847d135dc5c05..d4d5716e17605b 100644
--- a/crates/ty_python_semantic/src/types/display.rs
+++ b/crates/ty_python_semantic/src/types/display.rs
@@ -29,12 +29,13 @@ use crate::types::signatures::{
CallableSignature, Parameter, Parameters, ParametersKind, Signature,
};
use crate::types::tuple::TupleSpec;
+use crate::types::typevar::BoundTypeVarIdentity;
use crate::types::visitor::TypeVisitor;
use crate::types::{
- BindingContext, BoundTypeVarIdentity, CallableType, CallableTypeKind, IntersectionType,
- KnownBoundMethodType, KnownClass, KnownInstanceType, LiteralValueType, LiteralValueTypeKind,
- MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType, StringLiteralType,
- SubclassOfInner, SubclassOfType, Type, TypeAliasType, TypeGuardLike, TypedDictType, UnionType,
+ BindingContext, CallableType, CallableTypeKind, IntersectionType, KnownBoundMethodType,
+ KnownClass, KnownInstanceType, LiteralValueType, LiteralValueTypeKind, MaterializationKind,
+ Protocol, ProtocolInstanceType, SpecialFormType, StringLiteralType, SubclassOfInner,
+ SubclassOfType, Type, TypeAliasType, TypeGuardLike, TypedDictType, UnionType,
WrapperDescriptorKind, visitor,
};
diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs
index d34195041af4ae..43687a23d2a8c5 100644
--- a/crates/ty_python_semantic/src/types/generics.rs
+++ b/crates/ty_python_semantic/src/types/generics.rs
@@ -21,15 +21,17 @@ use crate::types::constraints::{
use crate::types::relation::{HasRelationToVisitor, IsDisjointVisitor, TypeRelation};
use crate::types::signatures::{CallableSignature, Parameters};
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
+use crate::types::typevar::{
+ BoundTypeVarIdentity, TypeVarIdentity, TypeVarInstance, walk_type_var_bounds,
+};
use crate::types::variance::VarianceInferable;
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
use crate::types::{
- ApplyTypeMappingVisitor, BindingContext, BoundTypeVarIdentity, BoundTypeVarInstance,
- CallableType, CallableTypes, ClassLiteral, FindLegacyTypeVarsVisitor, IntersectionType,
- KnownClass, KnownInstanceType, MaterializationKind, Type, TypeAliasType, TypeContext,
- TypeMapping, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance, TypeVarKind,
- TypeVarVariance, UnionType, declaration_type, walk_manual_pep_695_type_alias,
- walk_pep_695_type_alias, walk_type_var_bounds,
+ ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableType, CallableTypes,
+ ClassLiteral, FindLegacyTypeVarsVisitor, IntersectionType, KnownClass, KnownInstanceType,
+ MaterializationKind, Type, TypeAliasType, TypeContext, TypeMapping, TypeVarBoundOrConstraints,
+ TypeVarKind, TypeVarVariance, UnionType, declaration_type, walk_manual_pep_695_type_alias,
+ walk_pep_695_type_alias,
};
use crate::{Db, FxIndexMap, FxOrderMap, FxOrderSet};
diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs
index 3a356dd8084ab8..dec757b5675141 100644
--- a/crates/ty_python_semantic/src/types/infer/builder.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder.rs
@@ -126,19 +126,21 @@ use crate::types::typed_dict::{
TypedDictAssignmentKind, TypedDictKeyAssignment, validate_typed_dict_constructor,
validate_typed_dict_dict_literal,
};
+use crate::types::typevar::{
+ BoundTypeVarIdentity, TypeVarBoundOrConstraintsEvaluation, TypeVarConstraints,
+ TypeVarDefaultEvaluation, TypeVarIdentity, TypeVarInstance,
+};
use crate::types::visitor::find_over_type;
use crate::types::{
- BoundTypeVarIdentity, CallDunderError, CallableBinding, CallableType, CallableTypeKind,
- ClassType, DataclassParams, DynamicType, EvaluationMode, GenericAlias, InternedConstraintSet,
- InternedType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, KnownUnion,
+ CallDunderError, CallableBinding, CallableType, CallableTypeKind, ClassType, DataclassParams,
+ DynamicType, EvaluationMode, GenericAlias, InternedConstraintSet, InternedType,
+ IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, KnownUnion,
LintDiagnosticGuard, LiteralValueTypeKind, ManualPEP695TypeAliasType, MemberLookupPolicy,
MetaclassCandidate, PEP695TypeAliasType, ParamSpecAttrKind, Parameter, ParameterForm,
Parameters, Signature, SpecialFormType, StaticClassLiteral, SubclassOfType, Truthiness, Type,
TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers, TypeVarBoundOrConstraints,
- TypeVarBoundOrConstraintsEvaluation, TypeVarConstraints, TypeVarDefaultEvaluation,
- TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder,
- UnionType, binding_type, definition_expression_type, infer_complete_scope_types,
- infer_scope_types, todo_type,
+ TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType, binding_type,
+ definition_expression_type, infer_complete_scope_types, infer_scope_types, todo_type,
};
use crate::types::{CallableTypes, overrides};
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
@@ -9645,13 +9647,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
typevar.identity(self.db()).definition(self.db()),
TypeVarKind::Pep613Alias,
);
- Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new(
- self.db(),
- identity,
- typevar._bound_or_constraints(self.db()),
- typevar.explicit_variance(self.db()),
- typevar._default(self.db()),
- )))
+ Type::KnownInstance(KnownInstanceType::TypeVar(
+ typevar.with_identity(self.db(), identity),
+ ))
} else {
inferred_ty
};
diff --git a/crates/ty_python_semantic/src/types/infer/builder/binary_expressions.rs b/crates/ty_python_semantic/src/types/infer/builder/binary_expressions.rs
index a297449d05d6c2..29d3f7f5c72b0b 100644
--- a/crates/ty_python_semantic/src/types/infer/builder/binary_expressions.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder/binary_expressions.rs
@@ -4,10 +4,11 @@ use super::TypeInferenceBuilder;
use crate::Db;
use crate::types::constraints::ConstraintSetBuilder;
use crate::types::diagnostic::{DIVISION_BY_ZERO, report_unsupported_binary_operation};
+use crate::types::typevar::TypeVarConstraints;
use crate::types::{
DynamicType, InternedConstraintSet, KnownClass, KnownInstanceType, LiteralValueTypeKind,
- MemberLookupPolicy, Type, TypeContext, TypeVarBoundOrConstraints, TypeVarConstraints,
- UnionBuilder, UnionTypeInstance,
+ MemberLookupPolicy, Type, TypeContext, TypeVarBoundOrConstraints, UnionBuilder,
+ UnionTypeInstance,
};
use ruff_python_ast::PythonVersion;
diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs
index 33d2fd55eada4c..736c72bcc7e56b 100644
--- a/crates/ty_python_semantic/src/types/known_instance.rs
+++ b/crates/ty_python_semantic/src/types/known_instance.rs
@@ -6,11 +6,12 @@ use crate::{
types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassType, GenericContext,
InvalidTypeExpressionError, KnownClass, StringLiteralType, Type, TypeAliasType,
- TypeContext, TypeMapping, TypeVarInstance, TypeVarVariance, UnionBuilder,
+ TypeContext, TypeMapping, TypeVarVariance, UnionBuilder,
class::NamedTupleSpec,
constraints::OwnedConstraintSet,
generics::{Specialization, walk_generic_context},
newtype::NewType,
+ typevar::TypeVarInstance,
variance::VarianceInferable,
visitor,
},
diff --git a/crates/ty_python_semantic/src/types/typevar.rs b/crates/ty_python_semantic/src/types/typevar.rs
new file mode 100644
index 00000000000000..7dfe530bed89db
--- /dev/null
+++ b/crates/ty_python_semantic/src/types/typevar.rs
@@ -0,0 +1,1356 @@
+use std::cell::RefCell;
+
+use ruff_db::parsed::parsed_module;
+use ruff_python_ast::name::Name;
+use rustc_hash::FxHashSet;
+
+use crate::{
+ Db, TypeQualifiers,
+ place::{DefinedPlace, Definedness, Place, PlaceAndQualifiers, TypeOrigin, Widening},
+ semantic_index::{
+ definition::{Definition, DefinitionKind},
+ semantic_index,
+ },
+ types::{
+ ApplyTypeMappingVisitor, CycleDetector, DynamicType, KnownClass, KnownInstanceType,
+ MaterializationKind, Parameter, Parameters, Type, TypeAliasType, TypeContext, TypeMapping,
+ TypeVarVariance, UnionBuilder, UnionType, any_over_type, binding_type,
+ definition_expression_type, tuple::Tuple, variance::VarianceInferable, visitor,
+ },
+};
+
+impl<'db> Type<'db> {
+ pub(crate) const fn is_type_var(self) -> bool {
+ matches!(self, Type::TypeVar(_))
+ }
+
+ pub(crate) const fn as_typevar(self) -> Option> {
+ match self {
+ Type::TypeVar(bound_typevar) => Some(bound_typevar),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn has_typevar(self, db: &'db dyn Db) -> bool {
+ any_over_type(db, self, false, |ty| matches!(ty, Type::TypeVar(_)))
+ }
+
+ pub(crate) fn has_non_self_typevar(self, db: &'db dyn Db) -> bool {
+ any_over_type(
+ db,
+ self,
+ false,
+ |ty| matches!(ty, Type::TypeVar(tv) if !tv.typevar(db).is_self(db)),
+ )
+ }
+
+ pub(crate) fn has_typevar_or_typevar_instance(self, db: &'db dyn Db) -> bool {
+ any_over_type(db, self, false, |ty| {
+ matches!(
+ ty,
+ Type::KnownInstance(KnownInstanceType::TypeVar(_)) | Type::TypeVar(_)
+ )
+ })
+ }
+
+ pub(crate) fn has_unspecialized_type_var(self, db: &'db dyn Db) -> bool {
+ any_over_type(db, self, false, |ty| {
+ matches!(ty, Type::Dynamic(DynamicType::UnspecializedTypeVar))
+ })
+ }
+}
+
+/// A specific instance of a type variable that has not been bound to a generic context yet.
+///
+/// This is usually not the type that you want; if you are working with a typevar, in a generic
+/// context, which might be specialized to a concrete type, you want [`BoundTypeVarInstance`]. This
+/// type holds information that does not depend on which generic context the typevar is used in.
+///
+/// For a legacy typevar:
+///
+/// ```py
+/// T = TypeVar("T") # [1]
+/// def generic_function(t: T) -> T: ... # [2]
+/// ```
+///
+/// we will create a `TypeVarInstance` for the typevar `T` when it is instantiated. The type of `T`
+/// at `[1]` will be a `KnownInstanceType::TypeVar` wrapping this `TypeVarInstance`. The typevar is
+/// not yet bound to any generic context at this point.
+///
+/// The typevar is used in `generic_function`, which binds it to a new generic context. We will
+/// create a [`BoundTypeVarInstance`] for this new binding of the typevar. The type of `T` at `[2]`
+/// will be a `Type::TypeVar` wrapping this `BoundTypeVarInstance`.
+///
+/// For a PEP 695 typevar:
+///
+/// ```py
+/// def generic_function[T](t: T) -> T: ...
+/// # ╰─────╰─────────── [2]
+/// # ╰─────────────────────── [1]
+/// ```
+///
+/// the typevar is defined and immediately bound to a single generic context. Just like in the
+/// legacy case, we will create a `TypeVarInstance` and [`BoundTypeVarInstance`], and the type of
+/// `T` at `[1]` and `[2]` will be that `TypeVarInstance` and `BoundTypeVarInstance`, respectively.
+#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
+pub struct TypeVarInstance<'db> {
+ /// The identity of this typevar
+ pub(crate) identity: TypeVarIdentity<'db>,
+
+ /// 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 explicitly specified variance of the TypeVar
+ pub(super) explicit_variance: 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>,
+}
+
+// The Salsa heap is tracked separately.
+impl get_size2::GetSize for TypeVarInstance<'_> {}
+
+pub(super) fn walk_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
+ db: &'db dyn Db,
+ typevar: TypeVarInstance<'db>,
+ visitor: &V,
+) {
+ if let Some(bound_or_constraints) = if visitor.should_visit_lazy_type_attributes() {
+ typevar.bound_or_constraints(db)
+ } else {
+ match typevar._bound_or_constraints(db) {
+ _ if visitor.should_visit_lazy_type_attributes() => typevar.bound_or_constraints(db),
+ Some(TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints)) => {
+ Some(bound_or_constraints)
+ }
+ _ => None,
+ }
+ } {
+ walk_type_var_bounds(db, bound_or_constraints, visitor);
+ }
+ if let Some(default_type) = if visitor.should_visit_lazy_type_attributes() {
+ typevar.default_type(db)
+ } else {
+ match typevar._default(db) {
+ Some(TypeVarDefaultEvaluation::Eager(default_type)) => Some(default_type),
+ _ => None,
+ }
+ } {
+ visitor.visit_type(db, default_type);
+ }
+}
+
+#[salsa::tracked]
+impl<'db> TypeVarInstance<'db> {
+ pub(crate) fn with_binding_context(
+ self,
+ db: &'db dyn Db,
+ binding_context: Definition<'db>,
+ ) -> BoundTypeVarInstance<'db> {
+ BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context), None)
+ }
+
+ fn with_name_suffix(self, db: &'db dyn Db, suffix: &str) -> Self {
+ Self::new(
+ db,
+ self.identity(db).with_name_suffix(db, suffix),
+ self._bound_or_constraints(db),
+ self.explicit_variance(db),
+ self._default(db),
+ )
+ }
+
+ pub(super) fn with_identity(self, db: &'db dyn Db, identity: TypeVarIdentity<'db>) -> Self {
+ Self::new(
+ db,
+ identity,
+ self._bound_or_constraints(db),
+ self.explicit_variance(db),
+ self._default(db),
+ )
+ }
+
+ pub(crate) fn name(self, db: &'db dyn Db) -> &'db Name {
+ self.identity(db).name(db)
+ }
+
+ pub(crate) fn definition(self, db: &'db dyn Db) -> Option> {
+ self.identity(db).definition(db)
+ }
+
+ pub fn kind(self, db: &'db dyn Db) -> TypeVarKind {
+ self.identity(db).kind(db)
+ }
+
+ pub(crate) fn is_self(self, db: &'db dyn Db) -> bool {
+ matches!(self.kind(db), TypeVarKind::TypingSelf)
+ }
+
+ pub(crate) fn is_paramspec(self, db: &'db dyn Db) -> bool {
+ self.kind(db).is_paramspec()
+ }
+
+ pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option> {
+ if let Some(TypeVarBoundOrConstraints::UpperBound(ty)) = self.bound_or_constraints(db) {
+ Some(ty)
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&'db [Type<'db>]> {
+ if let Some(TypeVarBoundOrConstraints::Constraints(tuple)) = self.bound_or_constraints(db) {
+ Some(tuple.elements(db))
+ } else {
+ None
+ }
+ }
+
+ 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)
+ .map(TypeVarBoundOrConstraints::UpperBound),
+ TypeVarBoundOrConstraintsEvaluation::LazyConstraints => self
+ .lazy_constraints(db)
+ .map(TypeVarBoundOrConstraints::Constraints),
+ })
+ }
+
+ /// Returns the bounds or constraints of this typevar. If the typevar is unbounded, returns
+ /// `object` as its upper bound.
+ pub(crate) fn require_bound_or_constraints(
+ self,
+ db: &'db dyn Db,
+ ) -> TypeVarBoundOrConstraints<'db> {
+ self.bound_or_constraints(db)
+ .unwrap_or_else(|| TypeVarBoundOrConstraints::UpperBound(Type::object()))
+ }
+
+ pub(crate) fn default_type(self, db: &'db dyn Db) -> Option> {
+ let visitor = TypeVarDefaultVisitor::new(None);
+ self.default_type_impl(db, &visitor)
+ }
+
+ fn default_type_impl(
+ self,
+ db: &'db dyn Db,
+ visitor: &TypeVarDefaultVisitor<'db>,
+ ) -> Option> {
+ visitor.visit(self, || {
+ self._default(db).and_then(|default| match default {
+ TypeVarDefaultEvaluation::Eager(ty) => Some(ty),
+ TypeVarDefaultEvaluation::Lazy => self.lazy_default_impl(db, visitor),
+ })
+ })
+ }
+
+ fn materialize_impl(
+ self,
+ db: &'db dyn Db,
+ materialization_kind: MaterializationKind,
+ visitor: &ApplyTypeMappingVisitor<'db>,
+ ) -> Self {
+ Self::new(
+ db,
+ self.identity(db),
+ self._bound_or_constraints(db)
+ .and_then(|bound_or_constraints| match bound_or_constraints {
+ TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => Some(
+ bound_or_constraints
+ .materialize_impl(db, materialization_kind, visitor)
+ .into(),
+ ),
+ TypeVarBoundOrConstraintsEvaluation::LazyUpperBound => {
+ self.lazy_bound(db).map(|bound| {
+ TypeVarBoundOrConstraints::UpperBound(bound)
+ .materialize_impl(db, materialization_kind, visitor)
+ .into()
+ })
+ }
+ TypeVarBoundOrConstraintsEvaluation::LazyConstraints => {
+ self.lazy_constraints(db).map(|constraints| {
+ TypeVarBoundOrConstraints::Constraints(constraints)
+ .materialize_impl(db, materialization_kind, visitor)
+ .into()
+ })
+ }
+ }),
+ self.explicit_variance(db),
+ self._default(db).and_then(|default| match default {
+ TypeVarDefaultEvaluation::Eager(ty) => {
+ Some(ty.materialize(db, materialization_kind, visitor).into())
+ }
+ TypeVarDefaultEvaluation::Lazy => self
+ .lazy_default(db)
+ .map(|ty| ty.materialize(db, materialization_kind, visitor).into()),
+ }),
+ )
+ }
+
+ fn to_instance(self, db: &'db dyn Db) -> Option {
+ let bound_or_constraints = match self.bound_or_constraints(db)? {
+ TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
+ TypeVarBoundOrConstraints::UpperBound(upper_bound.to_instance(db)?)
+ }
+ TypeVarBoundOrConstraints::Constraints(constraints) => {
+ TypeVarBoundOrConstraints::Constraints(constraints.to_instance(db)?)
+ }
+ };
+ let identity = TypeVarIdentity::new(
+ db,
+ Name::new(format!("{}'instance", self.name(db))),
+ None, // definition
+ self.kind(db),
+ );
+ Some(Self::new(
+ db,
+ identity,
+ Some(bound_or_constraints.into()),
+ self.explicit_variance(db),
+ None, // _default
+ ))
+ }
+
+ fn type_is_self_referential(
+ self,
+ db: &'db dyn Db,
+ ty: Type<'db>,
+ visitor: &TypeVarDefaultVisitor<'db>,
+ ) -> bool {
+ #[derive(Copy, Clone)]
+ struct State<'db, 'a> {
+ db: &'db dyn Db,
+ visitor: &'a TypeVarDefaultVisitor<'db>,
+ seen_typevars: &'a RefCell>>,
+ seen_type_aliases: &'a RefCell>>,
+ }
+
+ fn typevar_default_is_self_referential<'db>(
+ state: State<'db, '_>,
+ typevar: TypeVarInstance<'db>,
+ self_identity: TypeVarIdentity<'db>,
+ ) -> bool {
+ if typevar.identity(state.db) == self_identity {
+ return true;
+ }
+
+ if !state.seen_typevars.borrow_mut().insert(typevar) {
+ return false;
+ }
+
+ typevar
+ .default_type_impl(state.db, state.visitor)
+ .is_some_and(|default_ty| {
+ type_is_self_referential_impl(state, default_ty, self_identity)
+ })
+ }
+
+ fn type_alias_is_self_referential<'db>(
+ state: State<'db, '_>,
+ type_alias: TypeAliasType<'db>,
+ self_identity: TypeVarIdentity<'db>,
+ ) -> bool {
+ if !state.seen_type_aliases.borrow_mut().insert(type_alias) {
+ return false;
+ }
+
+ type_is_self_referential_impl(state, type_alias.raw_value_type(state.db), self_identity)
+ }
+
+ fn type_is_self_referential_impl<'db>(
+ state: State<'db, '_>,
+ ty: Type<'db>,
+ self_identity: TypeVarIdentity<'db>,
+ ) -> bool {
+ any_over_type(state.db, ty, false, |inner_ty| match inner_ty {
+ Type::TypeVar(bound_typevar) => typevar_default_is_self_referential(
+ state,
+ bound_typevar.typevar(state.db),
+ self_identity,
+ ),
+ Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => {
+ typevar_default_is_self_referential(state, typevar, self_identity)
+ }
+ Type::TypeAlias(alias) => {
+ type_alias_is_self_referential(state, alias, self_identity)
+ }
+ Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => {
+ type_alias_is_self_referential(state, alias, self_identity)
+ }
+ _ => false,
+ })
+ }
+
+ let seen_typevars = RefCell::new(FxHashSet::default());
+ let seen_type_aliases = RefCell::new(FxHashSet::default());
+
+ let state = State {
+ db,
+ visitor,
+ seen_typevars: &seen_typevars,
+ seen_type_aliases: &seen_type_aliases,
+ };
+
+ type_is_self_referential_impl(state, ty, self.identity(db))
+ }
+
+ /// Returns the "unchecked" upper bound of a type variable instance.
+ /// `lazy_bound` checks if the upper bound type is generic (generic upper bound is not allowed).
+ #[salsa::tracked(
+ cycle_fn=lazy_bound_cycle_recover,
+ cycle_initial=|_, _, _| None,
+ heap_size=ruff_memory_usage::heap_size
+ )]
+ fn lazy_bound_unchecked(self, db: &'db dyn Db) -> Option> {
+ let definition = self.definition(db)?;
+ let module = parsed_module(db, definition.file(db)).load(db);
+ let ty = match definition.kind(db) {
+ // PEP 695 typevar
+ DefinitionKind::TypeVar(typevar) => {
+ let typevar_node = typevar.node(&module);
+ definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
+ }
+ // legacy typevar
+ DefinitionKind::Assignment(assignment) => {
+ let call_expr = assignment.value(&module).as_call_expr()?;
+ let expr = &call_expr.arguments.find_keyword("bound")?.value;
+ definition_expression_type(db, definition, expr)
+ }
+ _ => return None,
+ };
+
+ Some(ty)
+ }
+
+ fn lazy_bound(self, db: &'db dyn Db) -> Option> {
+ let bound = self.lazy_bound_unchecked(db)?;
+
+ if bound.has_typevar_or_typevar_instance(db) {
+ return None;
+ }
+
+ Some(bound)
+ }
+
+ /// Returns the "unchecked" constraints of a type variable instance.
+ /// `lazy_constraints` checks if any of the constraint types are generic (generic constraints are not allowed).
+ #[salsa::tracked(
+ cycle_fn=lazy_constraints_cycle_recover,
+ cycle_initial=|_, _, _| None,
+ heap_size=ruff_memory_usage::heap_size
+ )]
+ fn lazy_constraints_unchecked(self, db: &'db dyn Db) -> Option> {
+ let definition = self.definition(db)?;
+ let module = parsed_module(db, definition.file(db)).load(db);
+ let constraints = match definition.kind(db) {
+ // PEP 695 typevar
+ DefinitionKind::TypeVar(typevar) => {
+ let typevar_node = typevar.node(&module);
+ let bound =
+ definition_expression_type(db, definition, typevar_node.bound.as_ref()?);
+ let constraints = if let Some(tuple) = bound.tuple_instance_spec(db)
+ && let Tuple::Fixed(tuple) = tuple.into_owned()
+ {
+ tuple.owned_elements()
+ } else {
+ vec![Type::unknown()].into_boxed_slice()
+ };
+ TypeVarConstraints::new(db, constraints)
+ }
+ // legacy typevar
+ DefinitionKind::Assignment(assignment) => {
+ let call_expr = assignment.value(&module).as_call_expr()?;
+ TypeVarConstraints::new(
+ db,
+ call_expr
+ .arguments
+ .args
+ .iter()
+ .skip(1)
+ .map(|arg| definition_expression_type(db, definition, arg))
+ .collect::>(),
+ )
+ }
+ _ => return None,
+ };
+
+ Some(constraints)
+ }
+
+ fn lazy_constraints(self, db: &'db dyn Db) -> Option> {
+ let constraints = self.lazy_constraints_unchecked(db)?;
+
+ if constraints
+ .elements(db)
+ .iter()
+ .any(|ty| ty.has_typevar_or_typevar_instance(db))
+ {
+ return None;
+ }
+
+ Some(constraints)
+ }
+
+ /// Returns the "unchecked" default type of a type variable instance.
+ /// `lazy_default` checks if the default type is not self-referential.
+ #[salsa::tracked(cycle_initial=|_, id, _| Some(Type::divergent(id)), cycle_fn=lazy_default_cycle_recover, heap_size=ruff_memory_usage::heap_size)]
+ fn lazy_default_unchecked(self, db: &'db dyn Db) -> Option> {
+ fn convert_type_to_paramspec_value<'db>(db: &'db dyn Db, ty: Type<'db>) -> Type<'db> {
+ let parameters = match ty {
+ Type::NominalInstance(nominal_instance)
+ if nominal_instance.has_known_class(db, KnownClass::EllipsisType) =>
+ {
+ Parameters::gradual_form()
+ }
+ Type::NominalInstance(nominal_instance) => nominal_instance
+ .own_tuple_spec(db)
+ .map_or_else(Parameters::unknown, |tuple_spec| {
+ Parameters::new(
+ db,
+ tuple_spec
+ .iter_all_elements()
+ .map(|ty| Parameter::positional_only(None).with_annotated_type(ty)),
+ )
+ }),
+ Type::Dynamic(dynamic) => match dynamic {
+ DynamicType::Todo(_)
+ | DynamicType::TodoUnpack
+ | DynamicType::TodoStarredExpression
+ | DynamicType::TodoFunctionalTypedDict
+ | DynamicType::TodoTypeVarTuple => Parameters::todo(),
+ DynamicType::Any
+ | DynamicType::Unknown
+ | DynamicType::UnknownGeneric(_)
+ | DynamicType::UnspecializedTypeVar
+ | DynamicType::Divergent(_) => Parameters::unknown(),
+ },
+ Type::TypeVar(typevar) if typevar.is_paramspec(db) => {
+ return ty;
+ }
+ Type::KnownInstance(KnownInstanceType::TypeVar(typevar))
+ if typevar.is_paramspec(db) =>
+ {
+ return ty;
+ }
+ _ => Parameters::unknown(),
+ };
+ Type::paramspec_value_callable(db, parameters)
+ }
+
+ let definition = self.definition(db)?;
+ let module = parsed_module(db, definition.file(db)).load(db);
+ let ty = match definition.kind(db) {
+ // PEP 695 typevar
+ DefinitionKind::TypeVar(typevar) => {
+ let typevar_node = typevar.node(&module);
+ definition_expression_type(db, definition, typevar_node.default.as_ref()?)
+ }
+ // legacy typevar / ParamSpec
+ DefinitionKind::Assignment(assignment) => {
+ let call_expr = assignment.value(&module).as_call_expr()?;
+ let func_ty = definition_expression_type(db, definition, &call_expr.func);
+ let known_class = func_ty.as_class_literal().and_then(|cls| cls.known(db));
+ let expr = &call_expr.arguments.find_keyword("default")?.value;
+ let default_type = definition_expression_type(db, definition, expr);
+ if known_class == Some(KnownClass::ParamSpec) {
+ convert_type_to_paramspec_value(db, default_type)
+ } else {
+ default_type
+ }
+ }
+ // PEP 695 ParamSpec
+ DefinitionKind::ParamSpec(paramspec) => {
+ let paramspec_node = paramspec.node(&module);
+ let default_ty =
+ definition_expression_type(db, definition, paramspec_node.default.as_ref()?);
+ convert_type_to_paramspec_value(db, default_ty)
+ }
+ _ => return None,
+ };
+
+ Some(ty)
+ }
+
+ fn lazy_default(self, db: &'db dyn Db) -> Option> {
+ let visitor = TypeVarDefaultVisitor::new(None);
+ self.lazy_default_impl(db, &visitor)
+ }
+
+ fn lazy_default_impl(
+ self,
+ db: &'db dyn Db,
+ visitor: &TypeVarDefaultVisitor<'db>,
+ ) -> Option> {
+ let default = self.lazy_default_unchecked(db)?;
+
+ // Unlike bounds/constraints, default types are allowed to be generic (https://peps.python.org/pep-0696/#using-another-type-parameter-as-default).
+ // Here we simply check for non-self-referential.
+ // TODO: We should also check for non-forward references.
+ if self.type_is_self_referential(db, default, visitor) {
+ return None;
+ }
+
+ Some(default)
+ }
+
+ pub fn bind_pep695(self, db: &'db dyn Db) -> Option> {
+ if !matches!(
+ self.identity(db).kind(db),
+ TypeVarKind::Pep695 | TypeVarKind::Pep695ParamSpec
+ ) {
+ return None;
+ }
+ let typevar_definition = self.definition(db)?;
+ let index = semantic_index(db, typevar_definition.file(db));
+ let (_, child) = index
+ .child_scopes(typevar_definition.file_scope(db))
+ .next()?;
+ child
+ .node()
+ .generic_context(db, index)?
+ .binds_typevar(db, self)
+ }
+}
+
+/// A type variable that has been bound to a generic context, and which can be specialized to a
+/// concrete type.
+#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
+pub struct BoundTypeVarInstance<'db> {
+ pub typevar: TypeVarInstance<'db>,
+ pub(super) binding_context: BindingContext<'db>,
+ /// If [`Some`], this indicates that this type variable is the `args` or `kwargs` component
+ /// of a `ParamSpec` i.e., `P.args` or `P.kwargs`.
+ pub(super) paramspec_attr: Option,
+}
+
+// The Salsa heap is tracked separately.
+impl get_size2::GetSize for BoundTypeVarInstance<'_> {}
+
+impl<'db> BoundTypeVarInstance<'db> {
+ pub(crate) fn with_name_suffix(self, db: &'db dyn Db, suffix: &str) -> Self {
+ Self::new(
+ db,
+ self.typevar(db).with_name_suffix(db, suffix),
+ self.binding_context(db),
+ self.paramspec_attr(db),
+ )
+ }
+
+ /// Get the identity of this bound typevar.
+ ///
+ /// This is used for comparing whether two bound typevars represent the same logical typevar,
+ /// regardless of e.g. differences in their bounds or constraints due to materialization.
+ pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> {
+ BoundTypeVarIdentity {
+ identity: self.typevar(db).identity(db),
+ binding_context: self.binding_context(db),
+ paramspec_attr: self.paramspec_attr(db),
+ }
+ }
+
+ pub(crate) fn name(self, db: &'db dyn Db) -> &'db Name {
+ self.typevar(db).name(db)
+ }
+
+ pub(crate) fn kind(self, db: &'db dyn Db) -> TypeVarKind {
+ self.typevar(db).kind(db)
+ }
+
+ pub(crate) fn is_paramspec(self, db: &'db dyn Db) -> bool {
+ self.kind(db).is_paramspec()
+ }
+
+ /// Returns a new bound typevar instance with the given `ParamSpec` attribute set.
+ ///
+ /// This method will also set an appropriate upper bound on the typevar, based on the
+ /// attribute kind. For `P.args`, the upper bound will be `tuple[object, ...]`, and for
+ /// `P.kwargs`, the upper bound will be `Top[dict[str, Any]]`.
+ ///
+ /// It's the caller's responsibility to ensure that this method is only called on a `ParamSpec`
+ /// type variable.
+ pub(crate) fn with_paramspec_attr(self, db: &'db dyn Db, kind: ParamSpecAttrKind) -> Self {
+ debug_assert!(
+ self.is_paramspec(db),
+ "Expected a ParamSpec, got {:?}",
+ self.kind(db)
+ );
+
+ let upper_bound = TypeVarBoundOrConstraints::UpperBound(match kind {
+ ParamSpecAttrKind::Args => Type::homogeneous_tuple(db, Type::object()),
+ ParamSpecAttrKind::Kwargs => KnownClass::Dict
+ .to_specialized_instance(db, &[KnownClass::Str.to_instance(db), Type::any()])
+ .top_materialization(db),
+ });
+
+ let typevar = TypeVarInstance::new(
+ db,
+ self.typevar(db).identity(db),
+ Some(TypeVarBoundOrConstraintsEvaluation::Eager(upper_bound)),
+ None, // ParamSpecs cannot have explicit variance
+ None, // `P.args` and `P.kwargs` cannot have defaults even though `P` can
+ );
+
+ Self::new(db, typevar, self.binding_context(db), Some(kind))
+ }
+
+ /// Returns a new bound typevar instance without any `ParamSpec` attribute set.
+ ///
+ /// This method will also remove any upper bound that was set by `with_paramspec_attr`. This
+ /// means that the returned typevar will have no upper bound or constraints.
+ ///
+ /// It's the caller's responsibility to ensure that this method is only called on a `ParamSpec`
+ /// type variable.
+ pub(crate) fn without_paramspec_attr(self, db: &'db dyn Db) -> Self {
+ debug_assert!(
+ self.is_paramspec(db),
+ "Expected a ParamSpec, got {:?}",
+ self.kind(db)
+ );
+
+ Self::new(
+ db,
+ TypeVarInstance::new(
+ db,
+ self.typevar(db).identity(db),
+ None, // Remove the upper bound set by `with_paramspec_attr`
+ None, // ParamSpecs cannot have explicit variance
+ None, // `P.args` and `P.kwargs` cannot have defaults even though `P` can
+ ),
+ self.binding_context(db),
+ None,
+ )
+ }
+
+ /// Returns whether two bound typevars represent the same logical typevar, regardless of e.g.
+ /// differences in their bounds or constraints due to materialization.
+ pub(crate) fn is_same_typevar_as(self, db: &'db dyn Db, other: Self) -> bool {
+ self.identity(db) == other.identity(db)
+ }
+
+ /// Create a new PEP 695 type variable that can be used in signatures
+ /// of synthetic generic functions.
+ pub(crate) fn synthetic(db: &'db dyn Db, name: Name, variance: TypeVarVariance) -> Self {
+ let identity = TypeVarIdentity::new(
+ db,
+ name,
+ None, // definition
+ TypeVarKind::Pep695,
+ );
+ let typevar = TypeVarInstance::new(
+ db,
+ identity,
+ None, // _bound_or_constraints
+ Some(variance),
+ None, // _default
+ );
+ Self::new(db, typevar, BindingContext::Synthetic, None)
+ }
+
+ /// Create a new synthetic `Self` type variable with the given upper bound.
+ pub(crate) fn synthetic_self(
+ db: &'db dyn Db,
+ upper_bound: Type<'db>,
+ binding_context: BindingContext<'db>,
+ ) -> Self {
+ let identity = TypeVarIdentity::new(
+ db,
+ Name::new_static("Self"),
+ None, // definition
+ TypeVarKind::TypingSelf,
+ );
+ let typevar = TypeVarInstance::new(
+ db,
+ identity,
+ Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()),
+ Some(TypeVarVariance::Invariant),
+ None, // _default
+ );
+ Self::new(db, typevar, binding_context, None)
+ }
+
+ /// Returns an identical type variable with its `TypeVarBoundOrConstraints` mapped by the
+ /// provided closure.
+ pub(crate) fn map_bound_or_constraints(
+ self,
+ db: &'db dyn Db,
+ f: impl FnOnce(Option>) -> Option>,
+ ) -> Self {
+ let bound_or_constraints = f(self.typevar(db).bound_or_constraints(db));
+ let typevar = TypeVarInstance::new(
+ db,
+ self.typevar(db).identity(db),
+ bound_or_constraints.map(TypeVarBoundOrConstraintsEvaluation::Eager),
+ self.typevar(db).explicit_variance(db),
+ self.typevar(db)._default(db),
+ );
+
+ Self::new(
+ db,
+ typevar,
+ self.binding_context(db),
+ self.paramspec_attr(db),
+ )
+ }
+
+ pub(crate) fn variance_with_polarity(
+ self,
+ db: &'db dyn Db,
+ polarity: TypeVarVariance,
+ ) -> TypeVarVariance {
+ let _span = tracing::trace_span!("variance_with_polarity").entered();
+ match self.typevar(db).explicit_variance(db) {
+ Some(explicit_variance) => explicit_variance.compose(polarity),
+ None => match self.binding_context(db) {
+ BindingContext::Definition(definition) => binding_type(db, definition)
+ .with_polarity(polarity)
+ .variance_of(db, self),
+ BindingContext::Synthetic => TypeVarVariance::Invariant,
+ },
+ }
+ }
+
+ pub fn variance(self, db: &'db dyn Db) -> TypeVarVariance {
+ self.variance_with_polarity(db, TypeVarVariance::Covariant)
+ }
+
+ pub(super) fn apply_type_mapping_impl<'a>(
+ self,
+ db: &'db dyn Db,
+ type_mapping: &TypeMapping<'a, 'db>,
+ visitor: &ApplyTypeMappingVisitor<'db>,
+ ) -> Type<'db> {
+ match type_mapping {
+ TypeMapping::ApplySpecialization(specialization) => {
+ let typevar = if self.is_paramspec(db) {
+ self.without_paramspec_attr(db)
+ } else {
+ self
+ };
+ specialization
+ .get(db, typevar)
+ .map(|ty| {
+ if let Some(attr) = self.paramspec_attr(db)
+ && let Type::TypeVar(typevar) = ty
+ && typevar.is_paramspec(db)
+ {
+ return Type::TypeVar(typevar.with_paramspec_attr(db, attr));
+ }
+ ty
+ })
+ .unwrap_or(Type::TypeVar(self))
+ }
+ TypeMapping::BindSelf(binding) => {
+ if binding.should_bind(db, self) {
+ binding.self_type()
+ } else {
+ Type::TypeVar(self)
+ }
+ }
+ TypeMapping::ReplaceSelf { new_upper_bound } => {
+ if self.typevar(db).is_self(db) {
+ Type::TypeVar(BoundTypeVarInstance::synthetic_self(
+ db,
+ *new_upper_bound,
+ self.binding_context(db),
+ ))
+ } else {
+ Type::TypeVar(self)
+ }
+ }
+ TypeMapping::UniqueSpecialization { .. }
+ | TypeMapping::PromoteLiterals(_)
+ | TypeMapping::ReplaceParameterDefaults
+ | TypeMapping::BindLegacyTypevars(_)
+ | TypeMapping::EagerExpansion
+ | TypeMapping::RescopeReturnCallables(_) => Type::TypeVar(self),
+ TypeMapping::Materialize(materialization_kind) => {
+ Type::TypeVar(self.materialize_impl(db, *materialization_kind, visitor))
+ }
+ }
+ }
+}
+
+pub(super) fn walk_bound_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
+ db: &'db dyn Db,
+ bound_typevar: BoundTypeVarInstance<'db>,
+ visitor: &V,
+) {
+ visitor.visit_type_var_type(db, bound_typevar.typevar(db));
+}
+
+impl<'db> BoundTypeVarInstance<'db> {
+ /// Returns the default value of this typevar, recursively applying its binding context to any
+ /// other typevars that appear in the default.
+ ///
+ /// For instance, in
+ ///
+ /// ```py
+ /// T = TypeVar("T")
+ /// U = TypeVar("U", default=T)
+ ///
+ /// # revealed: typing.TypeVar[U = typing.TypeVar[T]]
+ /// reveal_type(U)
+ ///
+ /// # revealed: typing.Generic[T, U = T@C]
+ /// class C(reveal_type(Generic[T, U])): ...
+ /// ```
+ ///
+ /// In the first case, the use of `U` is unbound, and so we have a [`TypeVarInstance`], and its
+ /// default value (`T`) is also unbound.
+ ///
+ /// 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_type(self, db: &'db dyn Db) -> Option> {
+ bound_typevar_default_type(db, self)
+ }
+
+ fn materialize_impl(
+ self,
+ db: &'db dyn Db,
+ materialization_kind: MaterializationKind,
+ visitor: &ApplyTypeMappingVisitor<'db>,
+ ) -> Self {
+ Self::new(
+ db,
+ self.typevar(db)
+ .materialize_impl(db, materialization_kind, visitor),
+ self.binding_context(db),
+ self.paramspec_attr(db),
+ )
+ }
+
+ pub(super) fn to_instance(self, db: &'db dyn Db) -> Option {
+ Some(Self::new(
+ db,
+ self.typevar(db).to_instance(db)?,
+ self.binding_context(db),
+ self.paramspec_attr(db),
+ ))
+ }
+}
+
+/// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax,
+/// or an implicit typevar like `Self` was used.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
+pub enum TypeVarKind {
+ /// `T = TypeVar("T")`
+ Legacy,
+ /// `def foo[T](x: T) -> T: ...`
+ Pep695,
+ /// `typing.Self`
+ TypingSelf,
+ /// `P = ParamSpec("P")`
+ ParamSpec,
+ /// `def foo[**P]() -> None: ...`
+ Pep695ParamSpec,
+ /// `Alias: typing.TypeAlias = T`
+ Pep613Alias,
+}
+
+impl TypeVarKind {
+ pub(super) const fn is_self(self) -> bool {
+ matches!(self, Self::TypingSelf)
+ }
+
+ pub(super) const fn is_paramspec(self) -> bool {
+ matches!(self, Self::ParamSpec | Self::Pep695ParamSpec)
+ }
+}
+
+/// The identity of a type variable.
+///
+/// This represents the core identity of a typevar, independent of its bounds or constraints. Two
+/// typevars have the same identity if they represent the same logical typevar, even if their
+/// bounds have been materialized differently.
+#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
+pub struct TypeVarIdentity<'db> {
+ /// The name of this TypeVar (e.g. `T`)
+ #[returns(ref)]
+ pub(crate) name: Name,
+
+ /// The type var's definition (None if synthesized)
+ pub(crate) definition: Option>,
+
+ /// The kind of typevar (PEP 695, Legacy, or TypingSelf)
+ pub(crate) kind: TypeVarKind,
+}
+
+impl get_size2::GetSize for TypeVarIdentity<'_> {}
+
+impl<'db> TypeVarIdentity<'db> {
+ fn with_name_suffix(self, db: &'db dyn Db, suffix: &str) -> Self {
+ let name = format!("{}'{}", self.name(db), suffix);
+ Self::new(db, Name::from(name), self.definition(db), self.kind(db))
+ }
+}
+
+#[expect(clippy::ref_option)]
+fn lazy_bound_cycle_recover<'db>(
+ db: &'db dyn Db,
+ cycle: &salsa::Cycle,
+ previous: &Option>,
+ current: Option>,
+ _typevar: TypeVarInstance<'db>,
+) -> Option> {
+ // Normalize the bounds/constraints to ensure cycle convergence.
+ match (previous, current) {
+ (Some(prev), Some(current)) => Some(current.cycle_normalized(db, *prev, cycle)),
+ (None, Some(current)) => Some(current.recursive_type_normalized(db, cycle)),
+ (_, None) => None,
+ }
+}
+
+#[allow(clippy::trivially_copy_pass_by_ref)]
+#[expect(clippy::ref_option)]
+fn lazy_constraints_cycle_recover<'db>(
+ db: &'db dyn Db,
+ cycle: &salsa::Cycle,
+ previous: &Option>,
+ current: Option>,
+ _typevar: TypeVarInstance<'db>,
+) -> Option> {
+ // Normalize the bounds/constraints to ensure cycle convergence.
+ match (previous, current) {
+ (Some(prev), Some(constraints)) => Some(constraints.cycle_normalized(db, *prev, cycle)),
+ (None, Some(current)) => Some(current.recursive_type_normalized(db, cycle)),
+ (_, None) => None,
+ }
+}
+
+#[expect(clippy::ref_option)]
+fn lazy_default_cycle_recover<'db>(
+ db: &'db dyn Db,
+ cycle: &salsa::Cycle,
+ previous_default: &Option>,
+ default: Option>,
+ _typevar: TypeVarInstance<'db>,
+) -> Option> {
+ // Normalize the default to ensure cycle convergence.
+ match (previous_default, default) {
+ (Some(prev), Some(default)) => Some(default.cycle_normalized(db, *prev, cycle)),
+ (None, Some(default)) => Some(default.recursive_type_normalized(db, cycle)),
+ (_, None) => None,
+ }
+}
+
+/// Where a type variable is bound and usable.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, salsa::Update, get_size2::GetSize)]
+pub enum BindingContext<'db> {
+ /// The definition of the generic class, function, or type alias that binds this typevar.
+ Definition(Definition<'db>),
+ /// The typevar is synthesized internally, and is not associated with a particular definition
+ /// in the source, but is still bound and eligible for specialization inference.
+ Synthetic,
+}
+
+impl<'db> From> for BindingContext<'db> {
+ fn from(definition: Definition<'db>) -> Self {
+ BindingContext::Definition(definition)
+ }
+}
+
+impl<'db> BindingContext<'db> {
+ pub(crate) fn definition(self) -> Option> {
+ match self {
+ BindingContext::Definition(definition) => Some(definition),
+ BindingContext::Synthetic => None,
+ }
+ }
+
+ pub(super) fn name(self, db: &'db dyn Db) -> Option {
+ self.definition().and_then(|definition| definition.name(db))
+ }
+}
+
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, get_size2::GetSize)]
+pub enum ParamSpecAttrKind {
+ Args,
+ Kwargs,
+}
+
+impl std::fmt::Display for ParamSpecAttrKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ParamSpecAttrKind::Args => f.write_str("args"),
+ ParamSpecAttrKind::Kwargs => f.write_str("kwargs"),
+ }
+ }
+}
+
+/// The identity of a bound type variable.
+///
+/// This identifies a specific binding of a typevar to a context (e.g., `T@ClassC` vs `T@FunctionF`),
+/// independent of the typevar's bounds or constraints. Two bound typevars have the same identity
+/// if they represent the same logical typevar bound in the same context, even if their bounds
+/// have been materialized differently.
+#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, get_size2::GetSize, salsa::Update)]
+pub struct BoundTypeVarIdentity<'db> {
+ pub(crate) identity: TypeVarIdentity<'db>,
+ pub(crate) binding_context: BindingContext<'db>,
+ /// If [`Some`], this indicates that this type variable is the `args` or `kwargs` component
+ /// of a `ParamSpec` i.e., `P.args` or `P.kwargs`.
+ pub(super) paramspec_attr: Option,
+}
+
+#[salsa::tracked(
+ cycle_initial=|_, _, _| None,
+ cycle_fn=bound_typevar_default_type_cycle_recover,
+ heap_size=ruff_memory_usage::heap_size
+)]
+fn bound_typevar_default_type<'db>(
+ db: &'db dyn Db,
+ bound_typevar: BoundTypeVarInstance<'db>,
+) -> Option> {
+ let binding_context = bound_typevar.binding_context(db);
+ bound_typevar.typevar(db).default_type(db).map(|ty| {
+ ty.apply_type_mapping(
+ db,
+ &TypeMapping::BindLegacyTypevars(binding_context),
+ TypeContext::default(),
+ )
+ })
+}
+
+#[expect(clippy::ref_option)]
+fn bound_typevar_default_type_cycle_recover<'db>(
+ _db: &'db dyn Db,
+ _cycle: &salsa::Cycle,
+ _previous_default: &Option>,
+ _default: Option>,
+ _bound_typevar: BoundTypeVarInstance<'db>,
+) -> Option> {
+ None
+}
+
+/// 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)
+ }
+}
+
+/// Type variable constraints (e.g. `T: (int, str)`).
+/// This is structurally identical to [`UnionType`], except that it does not perform simplification and preserves the element types.
+#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
+pub struct TypeVarConstraints<'db> {
+ #[returns(ref)]
+ pub(super) elements: Box<[Type<'db>]>,
+}
+
+impl get_size2::GetSize for TypeVarConstraints<'_> {}
+
+fn walk_type_var_constraints<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
+ db: &'db dyn Db,
+ constraints: TypeVarConstraints<'db>,
+ visitor: &V,
+) {
+ for ty in constraints.elements(db) {
+ visitor.visit_type(db, *ty);
+ }
+}
+
+impl<'db> TypeVarConstraints<'db> {
+ pub(super) fn as_type(self, db: &'db dyn Db) -> Type<'db> {
+ UnionType::from_elements(db, self.elements(db))
+ }
+
+ fn to_instance(self, db: &'db dyn Db) -> Option> {
+ let mut instance_elements = Vec::new();
+ for ty in self.elements(db) {
+ instance_elements.push(ty.to_instance(db)?);
+ }
+ Some(TypeVarConstraints::new(
+ db,
+ instance_elements.into_boxed_slice(),
+ ))
+ }
+
+ pub(super) fn map(
+ self,
+ db: &'db dyn Db,
+ transform_fn: impl FnMut(&Type<'db>) -> Type<'db>,
+ ) -> Self {
+ let mapped = self
+ .elements(db)
+ .iter()
+ .map(transform_fn)
+ .collect::>();
+ TypeVarConstraints::new(db, mapped)
+ }
+
+ pub(crate) fn map_with_boundness_and_qualifiers(
+ self,
+ db: &'db dyn Db,
+ mut transform_fn: impl FnMut(&Type<'db>) -> PlaceAndQualifiers<'db>,
+ ) -> PlaceAndQualifiers<'db> {
+ let mut builder = UnionBuilder::new(db);
+ let mut qualifiers = TypeQualifiers::empty();
+
+ let mut all_unbound = true;
+ let mut possibly_unbound = false;
+ let mut origin = TypeOrigin::Declared;
+ for ty in self.elements(db) {
+ let PlaceAndQualifiers {
+ place: ty_member,
+ qualifiers: new_qualifiers,
+ } = transform_fn(ty);
+ qualifiers |= new_qualifiers;
+ match ty_member {
+ Place::Undefined => {
+ possibly_unbound = true;
+ }
+ Place::Defined(DefinedPlace {
+ ty: ty_member,
+ origin: member_origin,
+ definedness: member_boundness,
+ ..
+ }) => {
+ origin = origin.merge(member_origin);
+ if member_boundness == Definedness::PossiblyUndefined {
+ possibly_unbound = true;
+ }
+
+ all_unbound = false;
+ builder = builder.add(ty_member);
+ }
+ }
+ }
+ PlaceAndQualifiers {
+ place: if all_unbound {
+ Place::Undefined
+ } else {
+ Place::Defined(DefinedPlace {
+ ty: builder.build(),
+ origin,
+ definedness: if possibly_unbound {
+ Definedness::PossiblyUndefined
+ } else {
+ Definedness::AlwaysDefined
+ },
+ widening: Widening::None,
+ })
+ },
+ qualifiers,
+ }
+ }
+
+ fn materialize_impl(
+ self,
+ db: &'db dyn Db,
+ materialization_kind: MaterializationKind,
+ visitor: &ApplyTypeMappingVisitor<'db>,
+ ) -> Self {
+ let materialized = self
+ .elements(db)
+ .iter()
+ .map(|ty| ty.materialize(db, materialization_kind, visitor))
+ .collect::>();
+ TypeVarConstraints::new(db, materialized)
+ }
+
+ /// Normalize for cycle recovery by combining with the previous value and
+ /// removing divergent types introduced by the cycle.
+ ///
+ /// See [`Type::cycle_normalized`] for more details on how this works.
+ fn cycle_normalized(self, db: &'db dyn Db, previous: Self, cycle: &salsa::Cycle) -> Self {
+ let current_elements = self.elements(db);
+ let prev_elements = previous.elements(db);
+ TypeVarConstraints::new(
+ db,
+ current_elements
+ .iter()
+ .zip(prev_elements.iter())
+ .map(|(ty, prev_ty)| ty.cycle_normalized(db, *prev_ty, cycle))
+ .collect::>(),
+ )
+ }
+
+ /// Normalize recursive types for cycle recovery when there's no previous value.
+ ///
+ /// See [`Type::recursive_type_normalized`] for more details.
+ fn recursive_type_normalized(self, db: &'db dyn Db, cycle: &salsa::Cycle) -> Self {
+ self.map(db, |ty| ty.recursive_type_normalized(db, cycle))
+ }
+}
+
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
+pub enum TypeVarBoundOrConstraints<'db> {
+ UpperBound(Type<'db>),
+ Constraints(TypeVarConstraints<'db>),
+}
+
+pub(super) fn walk_type_var_bounds<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
+ db: &'db dyn Db,
+ bounds: TypeVarBoundOrConstraints<'db>,
+ visitor: &V,
+) {
+ match bounds {
+ TypeVarBoundOrConstraints::UpperBound(bound) => visitor.visit_type(db, bound),
+ TypeVarBoundOrConstraints::Constraints(constraints) => {
+ walk_type_var_constraints(db, constraints, visitor);
+ }
+ }
+}
+
+impl<'db> TypeVarBoundOrConstraints<'db> {
+ fn materialize_impl(
+ self,
+ db: &'db dyn Db,
+ materialization_kind: MaterializationKind,
+ visitor: &ApplyTypeMappingVisitor<'db>,
+ ) -> Self {
+ match self {
+ TypeVarBoundOrConstraints::UpperBound(bound) => TypeVarBoundOrConstraints::UpperBound(
+ bound.materialize(db, materialization_kind, visitor),
+ ),
+ TypeVarBoundOrConstraints::Constraints(constraints) => {
+ TypeVarBoundOrConstraints::Constraints(constraints.materialize_impl(
+ db,
+ materialization_kind,
+ visitor,
+ ))
+ }
+ }
+ }
+}
+
+/// A [`CycleDetector`] that is used in `TypeVarInstance::default_type`.
+pub(crate) type TypeVarDefaultVisitor<'db> =
+ CycleDetector, Option>>;
+pub(crate) struct VisitTypeVarDefault;
diff --git a/crates/ty_python_semantic/src/types/visitor.rs b/crates/ty_python_semantic/src/types/visitor.rs
index 4819477fe70078..d457daccac4528 100644
--- a/crates/ty_python_semantic/src/types/visitor.rs
+++ b/crates/ty_python_semantic/src/types/visitor.rs
@@ -6,7 +6,7 @@ use crate::{
BoundMethodType, BoundSuperType, BoundTypeVarInstance, CallableType, GenericAlias,
IntersectionType, KnownBoundMethodType, KnownInstanceType, NominalInstanceType,
PropertyInstanceType, ProtocolInstanceType, SubclassOfType, Type, TypeAliasType,
- TypeGuardType, TypeIsType, TypeVarInstance, TypedDictType, UnionType,
+ TypeGuardType, TypeIsType, TypedDictType, UnionType,
bound_super::walk_bound_super_type,
callable::walk_callable_type,
class::walk_generic_alias,
@@ -16,9 +16,9 @@ use crate::{
method::{walk_bound_method_type, walk_method_wrapper_type},
newtype::{NewType, walk_newtype_instance_type},
subclass_of::walk_subclass_of_type,
- walk_bound_type_var_type, walk_intersection_type, walk_property_instance_type,
- walk_type_alias_type, walk_type_var_type, walk_typed_dict_type, walk_typeguard_type,
- walk_typeis_type, walk_union,
+ typevar::{TypeVarInstance, walk_bound_type_var_type, walk_type_var_type},
+ walk_intersection_type, walk_property_instance_type, walk_type_alias_type,
+ walk_typed_dict_type, walk_typeguard_type, walk_typeis_type, walk_union,
},
};
use std::cell::{Cell, RefCell};