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};