diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index c1e052f582..c30f0ebbc7 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -1729,6 +1729,21 @@ def _(r: Recursive): reveal_type(r.method(r).callable1(1).direct.t[1][1]) # revealed: Recursive ``` +### Mutually-recursive protocols + +```py +from typing import Protocol +from ty_extensions import is_equivalent_to, static_assert + +class Foo(Protocol): + x: "Bar" + +class Bar(Protocol): + x: Foo + +static_assert(is_equivalent_to(Foo, Bar)) +``` + ### Regression test: narrowing with self-referential protocols This snippet caused us to panic on an early version of the implementation for protocols. diff --git a/crates/ty_python_semantic/resources/primer/bad.txt b/crates/ty_python_semantic/resources/primer/bad.txt index f77cf40f4f..213e5c4dca 100644 --- a/crates/ty_python_semantic/resources/primer/bad.txt +++ b/crates/ty_python_semantic/resources/primer/bad.txt @@ -10,7 +10,7 @@ jax # too many iterations mypy # too many iterations (self-recursive type alias) packaging # too many iterations pandas # slow (9s) -pandera # stack overflow +pandera # too many iterations pip # vendors packaging, see above pylint # cycle panics (self-recursive type alias) pyodide # too many cycle iterations @@ -19,5 +19,4 @@ setuptools # vendors packaging, see above spack # slow, success, but mypy-primer hangs processing the output spark # too many iterations steam.py # hangs (single threaded) -tornado # bad use-def map (https://github.com/astral-sh/ty/issues/365) xarray # too many iterations diff --git a/crates/ty_python_semantic/resources/primer/good.txt b/crates/ty_python_semantic/resources/primer/good.txt index 8ba4aab531..9a036f3c12 100644 --- a/crates/ty_python_semantic/resources/primer/good.txt +++ b/crates/ty_python_semantic/resources/primer/good.txt @@ -110,6 +110,7 @@ strawberry streamlit svcs sympy +tornado trio twine typeshed-stats diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c258c10931..b06799c238 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -21,6 +21,7 @@ use ruff_text_size::{Ranged, TextRange}; use type_ordering::union_or_intersection_elements_ordering; pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder}; +pub(crate) use self::cyclic::TypeVisitor; pub use self::diagnostic::TypeCheckDiagnostics; pub(crate) use self::diagnostic::register_lints; pub(crate) use self::infer::{ @@ -63,6 +64,7 @@ mod call; mod class; mod class_base; mod context; +mod cyclic; mod diagnostic; mod display; mod function; @@ -384,11 +386,11 @@ impl<'db> PropertyInstanceType<'db> { Self::new(db, getter, setter) } - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self::new( db, - self.getter(db).map(|ty| ty.normalized(db)), - self.setter(db).map(|ty| ty.normalized(db)), + self.getter(db).map(|ty| ty.normalized_impl(db, visitor)), + self.setter(db).map(|ty| ty.normalized_impl(db, visitor)), ) } @@ -572,9 +574,9 @@ pub enum Type<'db> { /// An instance of a typevar in a generic class or function. When the generic class or function /// is specialized, we will replace this typevar with its specialization. TypeVar(TypeVarInstance<'db>), - // A bound super object like `super()` or `super(A, A())` - // This type doesn't handle an unbound super object like `super(A)`; for that we just use - // a `Type::NominalInstance` of `builtins.super`. + /// A bound super object like `super()` or `super(A, A())` + /// This type doesn't handle an unbound super object like `super(A)`; for that we just use + /// a `Type::NominalInstance` of `builtins.super`. BoundSuper(BoundSuperType<'db>), /// A subtype of `bool` that allows narrowing in both positive and negative cases. TypeIs(TypeIsType<'db>), @@ -753,85 +755,6 @@ impl<'db> Type<'db> { } } - /// Replace references to the class `class` with a self-reference marker. This is currently - /// used for recursive protocols, but could probably be extended to self-referential type- - /// aliases and similar. - #[must_use] - pub fn replace_self_reference(&self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Type<'db> { - match self { - Self::ProtocolInstance(protocol) => { - Self::ProtocolInstance(protocol.replace_self_reference(db, class)) - } - - Self::Union(union) => UnionType::from_elements( - db, - union - .elements(db) - .iter() - .map(|ty| ty.replace_self_reference(db, class)), - ), - - Self::Intersection(intersection) => IntersectionBuilder::new(db) - .positive_elements( - intersection - .positive(db) - .iter() - .map(|ty| ty.replace_self_reference(db, class)), - ) - .negative_elements( - intersection - .negative(db) - .iter() - .map(|ty| ty.replace_self_reference(db, class)), - ) - .build(), - - Self::Tuple(tuple) => TupleType::from_elements( - db, - tuple - .tuple(db) - .all_elements() - .map(|ty| ty.replace_self_reference(db, class)), - ), - - Self::Callable(callable) => Self::Callable(callable.replace_self_reference(db, class)), - - Self::GenericAlias(_) | Self::TypeVar(_) => { - // TODO: replace self-references in generic aliases and typevars - *self - } - - Self::TypeIs(type_is) => type_is.with_type( - db, - type_is.return_type(db).replace_self_reference(db, class), - ), - - Self::Dynamic(_) - | Self::AlwaysFalsy - | Self::AlwaysTruthy - | Self::Never - | Self::BooleanLiteral(_) - | Self::BytesLiteral(_) - | Self::StringLiteral(_) - | Self::IntLiteral(_) - | Self::LiteralString - | Self::FunctionLiteral(_) - | Self::ModuleLiteral(_) - | Self::ClassLiteral(_) - | Self::NominalInstance(_) - | Self::SpecialForm(_) - | Self::KnownInstance(_) - | Self::PropertyInstance(_) - | Self::BoundMethod(_) - | Self::WrapperDescriptor(_) - | Self::MethodWrapper(_) - | Self::DataclassDecorator(_) - | Self::DataclassTransformer(_) - | Self::SubclassOf(_) - | Self::BoundSuper(_) => *self, - } - } - /// Return `true` if `self`, or any of the types contained in `self`, match the closure passed in. pub fn any_over_type(self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool { if type_fn(self) { @@ -1149,26 +1072,62 @@ impl<'db> Type<'db> { /// - Converts class-based protocols into synthesized protocols #[must_use] pub fn normalized(self, db: &'db dyn Db) -> Self { + let mut visitor = TypeVisitor::default(); + self.normalized_impl(db, &mut visitor) + } + + #[must_use] + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { - Type::Union(union) => Type::Union(union.normalized(db)), - Type::Intersection(intersection) => Type::Intersection(intersection.normalized(db)), - Type::Tuple(tuple) => Type::tuple(tuple.normalized(db)), - Type::Callable(callable) => Type::Callable(callable.normalized(db)), - Type::ProtocolInstance(protocol) => protocol.normalized(db), - Type::NominalInstance(instance) => Type::NominalInstance(instance.normalized(db)), - Type::Dynamic(dynamic) => Type::Dynamic(dynamic.normalized()), - Type::FunctionLiteral(function) => Type::FunctionLiteral(function.normalized(db)), - Type::PropertyInstance(property) => Type::PropertyInstance(property.normalized(db)), - Type::MethodWrapper(method_kind) => Type::MethodWrapper(method_kind.normalized(db)), - Type::BoundMethod(method) => Type::BoundMethod(method.normalized(db)), - Type::BoundSuper(bound_super) => Type::BoundSuper(bound_super.normalized(db)), - Type::GenericAlias(generic) => Type::GenericAlias(generic.normalized(db)), - Type::SubclassOf(subclass_of) => Type::SubclassOf(subclass_of.normalized(db)), - Type::TypeVar(typevar) => Type::TypeVar(typevar.normalized(db)), - Type::KnownInstance(known_instance) => { - Type::KnownInstance(known_instance.normalized(db)) + Type::Union(union) => { + visitor.visit(self, |v| Type::Union(union.normalized_impl(db, v))) } - Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).normalized(db)), + Type::Intersection(intersection) => visitor.visit(self, |v| { + Type::Intersection(intersection.normalized_impl(db, v)) + }), + Type::Tuple(tuple) => { + visitor.visit(self, |v| Type::tuple(tuple.normalized_impl(db, v))) + } + Type::Callable(callable) => { + visitor.visit(self, |v| Type::Callable(callable.normalized_impl(db, v))) + } + Type::ProtocolInstance(protocol) => { + visitor.visit(self, |v| protocol.normalized_impl(db, v)) + } + Type::NominalInstance(instance) => visitor.visit(self, |v| { + Type::NominalInstance(instance.normalized_impl(db, v)) + }), + Type::FunctionLiteral(function) => visitor.visit(self, |v| { + Type::FunctionLiteral(function.normalized_impl(db, v)) + }), + Type::PropertyInstance(property) => visitor.visit(self, |v| { + Type::PropertyInstance(property.normalized_impl(db, v)) + }), + Type::MethodWrapper(method_kind) => visitor.visit(self, |v| { + Type::MethodWrapper(method_kind.normalized_impl(db, v)) + }), + Type::BoundMethod(method) => { + visitor.visit(self, |v| Type::BoundMethod(method.normalized_impl(db, v))) + } + Type::BoundSuper(bound_super) => visitor.visit(self, |v| { + Type::BoundSuper(bound_super.normalized_impl(db, v)) + }), + Type::GenericAlias(generic) => { + visitor.visit(self, |v| Type::GenericAlias(generic.normalized_impl(db, v))) + } + Type::SubclassOf(subclass_of) => visitor.visit(self, |v| { + Type::SubclassOf(subclass_of.normalized_impl(db, v)) + }), + Type::TypeVar(typevar) => { + visitor.visit(self, |v| Type::TypeVar(typevar.normalized_impl(db, v))) + } + Type::KnownInstance(known_instance) => visitor.visit(self, |v| { + Type::KnownInstance(known_instance.normalized_impl(db, v)) + }), + Type::TypeIs(type_is) => visitor.visit(self, |v| { + type_is.with_type(db, type_is.return_type(db).normalized_impl(db, v)) + }), + Type::Dynamic(dynamic) => Type::Dynamic(dynamic.normalized()), Type::LiteralString | Type::AlwaysFalsy | Type::AlwaysTruthy @@ -5743,13 +5702,13 @@ impl<'db> TypeMapping<'_, 'db> { } } - fn normalized(&self, db: &'db dyn Db) -> Self { + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { TypeMapping::Specialization(specialization) => { - TypeMapping::Specialization(specialization.normalized(db)) + TypeMapping::Specialization(specialization.normalized_impl(db, visitor)) } TypeMapping::PartialSpecialization(partial) => { - TypeMapping::PartialSpecialization(partial.normalized(db)) + TypeMapping::PartialSpecialization(partial.normalized_impl(db, visitor)) } TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals, } @@ -5795,12 +5754,18 @@ pub enum KnownInstanceType<'db> { } impl<'db> KnownInstanceType<'db> { - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { - Self::SubscriptedProtocol(context) => Self::SubscriptedProtocol(context.normalized(db)), - Self::SubscriptedGeneric(context) => Self::SubscriptedGeneric(context.normalized(db)), - Self::TypeVar(typevar) => Self::TypeVar(typevar.normalized(db)), - Self::TypeAliasType(type_alias) => Self::TypeAliasType(type_alias.normalized(db)), + Self::SubscriptedProtocol(context) => { + Self::SubscriptedProtocol(context.normalized_impl(db, visitor)) + } + Self::SubscriptedGeneric(context) => { + Self::SubscriptedGeneric(context.normalized_impl(db, visitor)) + } + Self::TypeVar(typevar) => Self::TypeVar(typevar.normalized_impl(db, visitor)), + Self::TypeAliasType(type_alias) => { + Self::TypeAliasType(type_alias.normalized_impl(db, visitor)) + } } } @@ -6181,14 +6146,15 @@ impl<'db> TypeVarInstance<'db> { } } - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self::new( db, self.name(db), self.definition(db), - self.bound_or_constraints(db).map(|b| b.normalized(db)), + self.bound_or_constraints(db) + .map(|b| b.normalized_impl(db, visitor)), self.variance(db), - self.default_ty(db).map(|d| d.normalized(db)), + self.default_ty(db).map(|d| d.normalized_impl(db, visitor)), self.kind(db), ) } @@ -6236,13 +6202,13 @@ pub enum TypeVarBoundOrConstraints<'db> { } impl<'db> TypeVarBoundOrConstraints<'db> { - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { TypeVarBoundOrConstraints::UpperBound(bound) => { - TypeVarBoundOrConstraints::UpperBound(bound.normalized(db)) + TypeVarBoundOrConstraints::UpperBound(bound.normalized_impl(db, visitor)) } TypeVarBoundOrConstraints::Constraints(constraints) => { - TypeVarBoundOrConstraints::Constraints(constraints.normalized(db)) + TypeVarBoundOrConstraints::Constraints(constraints.normalized_impl(db, visitor)) } } } @@ -7154,11 +7120,11 @@ impl<'db> BoundMethodType<'db> { )) } - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self::new( db, - self.function(db).normalized(db), - self.self_instance(db).normalized(db), + self.function(db).normalized_impl(db, visitor), + self.self_instance(db).normalized_impl(db, visitor), ) } @@ -7261,10 +7227,10 @@ impl<'db> CallableType<'db> { /// Return a "normalized" version of this `Callable` type. /// /// See [`Type::normalized`] for more details. - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { CallableType::new( db, - self.signatures(db).normalized(db), + self.signatures(db).normalized_impl(db, visitor), self.is_function_like(db), ) } @@ -7305,15 +7271,6 @@ impl<'db> CallableType<'db> { .signatures(db) .is_equivalent_to(db, other.signatures(db)) } - - /// See [`Type::replace_self_reference`]. - fn replace_self_reference(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { - CallableType::new( - db, - self.signatures(db).replace_self_reference(db, class), - self.is_function_like(db), - ) - } } /// Represents a specific instance of `types.MethodWrapperType` @@ -7404,19 +7361,19 @@ impl<'db> MethodWrapperKind<'db> { } } - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { MethodWrapperKind::FunctionTypeDunderGet(function) => { - MethodWrapperKind::FunctionTypeDunderGet(function.normalized(db)) + MethodWrapperKind::FunctionTypeDunderGet(function.normalized_impl(db, visitor)) } MethodWrapperKind::FunctionTypeDunderCall(function) => { - MethodWrapperKind::FunctionTypeDunderCall(function.normalized(db)) + MethodWrapperKind::FunctionTypeDunderCall(function.normalized_impl(db, visitor)) } MethodWrapperKind::PropertyDunderGet(property) => { - MethodWrapperKind::PropertyDunderGet(property.normalized(db)) + MethodWrapperKind::PropertyDunderGet(property.normalized_impl(db, visitor)) } MethodWrapperKind::PropertyDunderSet(property) => { - MethodWrapperKind::PropertyDunderSet(property.normalized(db)) + MethodWrapperKind::PropertyDunderSet(property.normalized_impl(db, visitor)) } MethodWrapperKind::StrStartswith(_) => self, } @@ -7530,7 +7487,7 @@ impl<'db> PEP695TypeAliasType<'db> { definition_expression_type(db, definition, &type_alias_stmt_node.value) } - fn normalized(self, _db: &'db dyn Db) -> Self { + fn normalized_impl(self, _db: &'db dyn Db, _visitor: &mut TypeVisitor<'db>) -> Self { self } } @@ -7551,12 +7508,12 @@ pub struct BareTypeAliasType<'db> { impl get_size2::GetSize for BareTypeAliasType<'_> {} impl<'db> BareTypeAliasType<'db> { - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self::new( db, self.name(db), self.definition(db), - self.value(db).normalized(db), + self.value(db).normalized_impl(db, visitor), ) } } @@ -7570,10 +7527,14 @@ pub enum TypeAliasType<'db> { } impl<'db> TypeAliasType<'db> { - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { - TypeAliasType::PEP695(type_alias) => TypeAliasType::PEP695(type_alias.normalized(db)), - TypeAliasType::Bare(type_alias) => TypeAliasType::Bare(type_alias.normalized(db)), + TypeAliasType::PEP695(type_alias) => { + TypeAliasType::PEP695(type_alias.normalized_impl(db, visitor)) + } + TypeAliasType::Bare(type_alias) => { + TypeAliasType::Bare(type_alias.normalized_impl(db, visitor)) + } } } @@ -7782,10 +7743,14 @@ impl<'db> UnionType<'db> { /// See [`Type::normalized`] for more details. #[must_use] pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + self.normalized_impl(db, &mut TypeVisitor::default()) + } + + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { let mut new_elements: Vec> = self .elements(db) .iter() - .map(|element| element.normalized(db)) + .map(|element| element.normalized_impl(db, visitor)) .collect(); new_elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); UnionType::new(db, new_elements.into_boxed_slice()) @@ -7839,12 +7804,20 @@ impl<'db> IntersectionType<'db> { /// See [`Type::normalized`] for more details. #[must_use] pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + let mut visitor = TypeVisitor::default(); + self.normalized_impl(db, &mut visitor) + } + + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { fn normalized_set<'db>( db: &'db dyn Db, elements: &FxOrderSet>, + visitor: &mut TypeVisitor<'db>, ) -> FxOrderSet> { - let mut elements: FxOrderSet> = - elements.iter().map(|ty| ty.normalized(db)).collect(); + let mut elements: FxOrderSet> = elements + .iter() + .map(|ty| ty.normalized_impl(db, visitor)) + .collect(); elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); elements @@ -7852,8 +7825,8 @@ impl<'db> IntersectionType<'db> { IntersectionType::new( db, - normalized_set(db, self.positive(db)), - normalized_set(db, self.negative(db)), + normalized_set(db, self.positive(db), visitor), + normalized_set(db, self.negative(db), visitor), ) } @@ -8092,11 +8065,15 @@ pub enum SuperOwnerKind<'db> { } impl<'db> SuperOwnerKind<'db> { - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { SuperOwnerKind::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic.normalized()), - SuperOwnerKind::Class(class) => SuperOwnerKind::Class(class.normalized(db)), - SuperOwnerKind::Instance(instance) => SuperOwnerKind::Instance(instance.normalized(db)), + SuperOwnerKind::Class(class) => { + SuperOwnerKind::Class(class.normalized_impl(db, visitor)) + } + SuperOwnerKind::Instance(instance) => { + SuperOwnerKind::Instance(instance.normalized_impl(db, visitor)) + } } } @@ -8337,11 +8314,11 @@ impl<'db> BoundSuperType<'db> { } } - fn normalized(self, db: &'db dyn Db) -> Self { + pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self::new( db, - self.pivot_class(db).normalized(db), - self.owner(db).normalized(db), + self.pivot_class(db).normalized_impl(db, visitor), + self.owner(db).normalized_impl(db, visitor), ) } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index a6fdebda5e..7c4e0be72c 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -22,7 +22,7 @@ use crate::types::tuple::TupleType; use crate::types::{ BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, - TypeVarInstance, TypeVarKind, infer_definition_types, + TypeVarInstance, TypeVarKind, TypeVisitor, infer_definition_types, }; use crate::{ Db, FxOrderSet, KnownModule, Program, @@ -182,8 +182,12 @@ pub struct GenericAlias<'db> { impl get_size2::GetSize for GenericAlias<'_> {} impl<'db> GenericAlias<'db> { - pub(super) fn normalized(self, db: &'db dyn Db) -> Self { - Self::new(db, self.origin(db), self.specialization(db).normalized(db)) + pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + Self::new( + db, + self.origin(db), + self.specialization(db).normalized_impl(db, visitor), + ) } pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { @@ -249,10 +253,10 @@ pub enum ClassType<'db> { #[salsa::tracked] impl<'db> ClassType<'db> { - pub(super) fn normalized(self, db: &'db dyn Db) -> Self { + pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { Self::NonGeneric(_) => self, - Self::Generic(generic) => Self::Generic(generic.normalized(db)), + Self::Generic(generic) => Self::Generic(generic.normalized_impl(db, visitor)), } } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index d738edfaf6..24d3e00610 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -2,7 +2,7 @@ use crate::Db; use crate::types::generics::Specialization; use crate::types::{ ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, SpecialFormType, - Type, TypeMapping, todo_type, + Type, TypeMapping, TypeVisitor, todo_type, }; /// Enumeration of the possible kinds of types we allow in class bases. @@ -31,10 +31,10 @@ impl<'db> ClassBase<'db> { Self::Dynamic(DynamicType::Unknown) } - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()), - Self::Class(class) => Self::Class(class.normalized(db)), + Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)), Self::Protocol | Self::Generic => self, } } diff --git a/crates/ty_python_semantic/src/types/cyclic.rs b/crates/ty_python_semantic/src/types/cyclic.rs new file mode 100644 index 0000000000..f3c7f3a42a --- /dev/null +++ b/crates/ty_python_semantic/src/types/cyclic.rs @@ -0,0 +1,26 @@ +use crate::FxOrderSet; +use crate::types::Type; + +#[derive(Debug, Default)] +pub(crate) struct TypeVisitor<'db> { + seen: FxOrderSet>, +} + +impl<'db> TypeVisitor<'db> { + pub(crate) fn visit( + &mut self, + ty: Type<'db>, + func: impl FnOnce(&mut Self) -> Type<'db>, + ) -> Type<'db> { + if !self.seen.insert(ty) { + // TODO: proper recursive type handling + + // This must be Any, not e.g. a todo type, because Any is the normalized form of the + // dynamic type (that is, todo types are normalized to Any). + return Type::any(); + } + let ret = func(self); + self.seen.pop(); + ret + } +} diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 8100ff7be2..7297e32996 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -75,7 +75,7 @@ use crate::types::narrow::ClassInfoConstraintFunction; use crate::types::signatures::{CallableSignature, Signature}; use crate::types::{ BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation, - TypeVarInstance, + TypeVarInstance, TypeVisitor, }; use crate::{Db, FxOrderSet, ModuleName, resolve_module}; @@ -545,10 +545,10 @@ impl<'db> FunctionLiteral<'db> { })) } - fn normalized(self, db: &'db dyn Db) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { let context = self .inherited_generic_context(db) - .map(|ctx| ctx.normalized(db)); + .map(|ctx| ctx.normalized_impl(db, visitor)); Self::new(db, self.last_definition(db), context) } } @@ -819,12 +819,17 @@ impl<'db> FunctionType<'db> { } pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + let mut visitor = TypeVisitor::default(); + self.normalized_impl(db, &mut visitor) + } + + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { let mappings: Box<_> = self .type_mappings(db) .iter() - .map(|mapping| mapping.normalized(db)) + .map(|mapping| mapping.normalized_impl(db, visitor)) .collect(); - Self::new(db, self.literal(db).normalized(db), mappings) + Self::new(db, self.literal(db).normalized_impl(db, visitor), mappings) } } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 1481c51e6b..7b686e6b03 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -11,7 +11,7 @@ use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::{ KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, - TypeVarVariance, UnionType, declaration_type, + TypeVarVariance, TypeVisitor, UnionType, declaration_type, }; use crate::{Db, FxOrderSet}; @@ -233,11 +233,11 @@ impl<'db> GenericContext<'db> { Specialization::new(db, self, expanded.into_boxed_slice(), None) } - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { let variables: FxOrderSet<_> = self .variables(db) .iter() - .map(|ty| ty.normalized(db)) + .map(|ty| ty.normalized_impl(db, visitor)) .collect(); Self::new(db, variables) } @@ -376,9 +376,15 @@ impl<'db> Specialization<'db> { Specialization::new(db, self.generic_context(db), types, None) } - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { - let types: Box<[_]> = self.types(db).iter().map(|ty| ty.normalized(db)).collect(); - let tuple_inner = self.tuple_inner(db).and_then(|tuple| tuple.normalized(db)); + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + let types: Box<[_]> = self + .types(db) + .iter() + .map(|ty| ty.normalized_impl(db, visitor)) + .collect(); + let tuple_inner = self + .tuple_inner(db) + .and_then(|tuple| tuple.normalized_impl(db, visitor)); Self::new(db, self.generic_context(db), types, tuple_inner) } @@ -526,9 +532,17 @@ impl<'db> PartialSpecialization<'_, 'db> { } } - pub(crate) fn normalized(&self, db: &'db dyn Db) -> PartialSpecialization<'db, 'db> { - let generic_context = self.generic_context.normalized(db); - let types: Cow<_> = self.types.iter().map(|ty| ty.normalized(db)).collect(); + pub(crate) fn normalized_impl( + &self, + db: &'db dyn Db, + visitor: &mut TypeVisitor<'db>, + ) -> PartialSpecialization<'db, 'db> { + let generic_context = self.generic_context.normalized_impl(db, visitor); + let types: Cow<_> = self + .types + .iter() + .map(|ty| ty.normalized_impl(db, visitor)) + .collect(); PartialSpecialization { generic_context, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 8cf1091ad7..6c65786429 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -6,7 +6,7 @@ use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance}; use crate::place::{Place, PlaceAndQualifiers}; use crate::types::tuple::TupleType; -use crate::types::{ClassLiteral, DynamicType, TypeMapping, TypeRelation, TypeVarInstance}; +use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeVarInstance, TypeVisitor}; use crate::{Db, FxOrderSet}; pub(super) use synthesized_protocol::SynthesizedProtocolType; @@ -41,7 +41,11 @@ impl<'db> Type<'db> { M: IntoIterator)>, { Self::ProtocolInstance(ProtocolInstanceType::synthesized( - SynthesizedProtocolType::new(db, ProtocolInterface::with_property_members(db, members)), + SynthesizedProtocolType::new( + db, + ProtocolInterface::with_property_members(db, members), + &mut TypeVisitor::default(), + ), )) } @@ -80,8 +84,8 @@ impl<'db> NominalInstanceType<'db> { } } - pub(super) fn normalized(self, db: &'db dyn Db) -> Self { - Self::from_class(self.class.normalized(db)) + pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + Self::from_class(self.class.normalized_impl(db, visitor)) } pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { @@ -201,31 +205,30 @@ impl<'db> ProtocolInstanceType<'db> { /// /// See [`Type::normalized`] for more details. pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> { + let mut visitor = TypeVisitor::default(); + self.normalized_impl(db, &mut visitor) + } + + /// Return a "normalized" version of this `Protocol` type. + /// + /// See [`Type::normalized`] for more details. + pub(super) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeVisitor<'db>, + ) -> Type<'db> { let object = KnownClass::Object.to_instance(db); if object.satisfies_protocol(db, self, TypeRelation::Subtyping) { return object; } match self.inner { Protocol::FromClass(_) => Type::ProtocolInstance(Self::synthesized( - SynthesizedProtocolType::new(db, self.inner.interface(db)), + SynthesizedProtocolType::new(db, self.inner.interface(db), visitor), )), Protocol::Synthesized(_) => Type::ProtocolInstance(self), } } - /// Replace references to `class` with a self-reference marker - pub(super) fn replace_self_reference(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { - match self.inner { - Protocol::FromClass(class_type) if class_type.class_literal(db).0 == class => { - ProtocolInstanceType::synthesized(SynthesizedProtocolType::new( - db, - ProtocolInterface::SelfReference, - )) - } - _ => self, - } - } - /// Return `true` if the types of any of the members match the closure passed in. pub(super) fn any_over_type( self, @@ -352,7 +355,7 @@ impl<'db> Protocol<'db> { mod synthesized_protocol { use crate::types::protocol_class::ProtocolInterface; - use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance}; + use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance, TypeVisitor}; use crate::{Db, FxOrderSet}; /// A "synthesized" protocol type that is dissociated from a class definition in source code. @@ -370,8 +373,12 @@ mod synthesized_protocol { pub(in crate::types) struct SynthesizedProtocolType<'db>(ProtocolInterface<'db>); impl<'db> SynthesizedProtocolType<'db> { - pub(super) fn new(db: &'db dyn Db, interface: ProtocolInterface<'db>) -> Self { - Self(interface.normalized(db)) + pub(super) fn new( + db: &'db dyn Db, + interface: ProtocolInterface<'db>, + visitor: &mut TypeVisitor<'db>, + ) -> Self { + Self(interface.normalized_impl(db, visitor)) } pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index ff79a7b313..aadc54b210 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, ops::Deref}; -use itertools::{Either, Itertools}; +use itertools::Itertools; use ruff_python_ast::name::Name; @@ -10,7 +10,7 @@ use crate::{ semantic_index::{place_table, use_def_map}, types::{ CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature, - Type, TypeMapping, TypeQualifiers, TypeRelation, TypeVarInstance, + Type, TypeMapping, TypeQualifiers, TypeRelation, TypeVarInstance, TypeVisitor, signatures::{Parameter, Parameters}, }, }; @@ -62,26 +62,19 @@ impl<'db> Deref for ProtocolClassLiteral<'db> { } } +/// The interface of a protocol: the members of that protocol, and the types of those members. +/// /// # Ordering /// Ordering is based on the protocol interface member's salsa-assigned id and not on its members. /// The id may change between runs, or when the protocol instance members was garbage collected and recreated. #[salsa::interned(debug)] #[derive(PartialOrd, Ord)] -pub(super) struct ProtocolInterfaceMembers<'db> { +pub(super) struct ProtocolInterface<'db> { #[returns(ref)] inner: BTreeMap>, } -impl get_size2::GetSize for ProtocolInterfaceMembers<'_> {} - -/// The interface of a protocol: the members of that protocol, and the types of those members. -#[derive( - Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, PartialOrd, Ord, get_size2::GetSize, -)] -pub(super) enum ProtocolInterface<'db> { - Members(ProtocolInterfaceMembers<'db>), - SelfReference, -} +impl get_size2::GetSize for ProtocolInterface<'_> {} impl<'db> ProtocolInterface<'db> { /// Synthesize a new protocol interface with the given members. @@ -112,11 +105,11 @@ impl<'db> ProtocolInterface<'db> { ) }) .collect(); - Self::Members(ProtocolInterfaceMembers::new(db, members)) + Self::new(db, members) } fn empty(db: &'db dyn Db) -> Self { - Self::Members(ProtocolInterfaceMembers::new(db, BTreeMap::default())) + Self::new(db, BTreeMap::default()) } pub(super) fn members<'a>( @@ -126,16 +119,11 @@ impl<'db> ProtocolInterface<'db> { where 'db: 'a, { - match self { - Self::Members(members) => { - Either::Left(members.inner(db).iter().map(|(name, data)| ProtocolMember { - name, - kind: data.kind, - qualifiers: data.qualifiers, - })) - } - Self::SelfReference => Either::Right(std::iter::empty()), - } + self.inner(db).iter().map(|(name, data)| ProtocolMember { + name, + kind: data.kind, + qualifiers: data.qualifiers, + }) } pub(super) fn member_by_name<'a>( @@ -143,29 +131,20 @@ impl<'db> ProtocolInterface<'db> { db: &'db dyn Db, name: &'a str, ) -> Option> { - match self { - Self::Members(members) => members.inner(db).get(name).map(|data| ProtocolMember { - name, - kind: data.kind, - qualifiers: data.qualifiers, - }), - Self::SelfReference => None, - } + self.inner(db).get(name).map(|data| ProtocolMember { + name, + kind: data.kind, + qualifiers: data.qualifiers, + }) } /// Return `true` if if all members on `self` are also members of `other`. /// /// TODO: this method should consider the types of the members as well as their names. pub(super) fn is_sub_interface_of(self, db: &'db dyn Db, other: Self) -> bool { - match (self, other) { - (Self::Members(self_members), Self::Members(other_members)) => self_members - .inner(db) - .keys() - .all(|member_name| other_members.inner(db).contains_key(member_name)), - _ => { - unreachable!("Enclosing protocols should never be a self-reference marker") - } - } + self.inner(db) + .keys() + .all(|member_name| other.inner(db).contains_key(member_name)) } /// Return `true` if the types of any of the members match the closure passed in. @@ -178,32 +157,24 @@ impl<'db> ProtocolInterface<'db> { .any(|member| member.any_over_type(db, type_fn)) } - pub(super) fn normalized(self, db: &'db dyn Db) -> Self { - match self { - Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( - db, - members - .inner(db) - .iter() - .map(|(name, data)| (name.clone(), data.normalized(db))) - .collect::>(), - )), - Self::SelfReference => Self::SelfReference, - } + pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + Self::new( + db, + self.inner(db) + .iter() + .map(|(name, data)| (name.clone(), data.normalized_impl(db, visitor))) + .collect::>(), + ) } pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { - match self { - Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( - db, - members - .inner(db) - .iter() - .map(|(name, data)| (name.clone(), data.materialize(db, variance))) - .collect::>(), - )), - Self::SelfReference => Self::SelfReference, - } + Self::new( + db, + self.inner(db) + .iter() + .map(|(name, data)| (name.clone(), data.materialize(db, variance))) + .collect::>(), + ) } pub(super) fn specialized_and_normalized<'a>( @@ -211,22 +182,18 @@ impl<'db> ProtocolInterface<'db> { db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>, ) -> Self { - match self { - Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( - db, - members - .inner(db) - .iter() - .map(|(name, data)| { - ( - name.clone(), - data.apply_type_mapping(db, type_mapping).normalized(db), - ) - }) - .collect::>(), - )), - Self::SelfReference => Self::SelfReference, - } + Self::new( + db, + self.inner(db) + .iter() + .map(|(name, data)| { + ( + name.clone(), + data.apply_type_mapping(db, type_mapping).normalized(db), + ) + }) + .collect::>(), + ) } pub(super) fn find_legacy_typevars( @@ -234,13 +201,8 @@ impl<'db> ProtocolInterface<'db> { db: &'db dyn Db, typevars: &mut FxOrderSet>, ) { - match self { - Self::Members(members) => { - for data in members.inner(db).values() { - data.find_legacy_typevars(db, typevars); - } - } - Self::SelfReference => {} + for data in self.inner(db).values() { + data.find_legacy_typevars(db, typevars); } } } @@ -253,8 +215,12 @@ pub(super) struct ProtocolMemberData<'db> { impl<'db> ProtocolMemberData<'db> { fn normalized(&self, db: &'db dyn Db) -> Self { + self.normalized_impl(db, &mut TypeVisitor::default()) + } + + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self { - kind: self.kind.normalized(db), + kind: self.kind.normalized_impl(db, visitor), qualifiers: self.qualifiers, } } @@ -290,15 +256,17 @@ enum ProtocolMemberKind<'db> { } impl<'db> ProtocolMemberKind<'db> { - fn normalized(&self, db: &'db dyn Db) -> Self { + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { ProtocolMemberKind::Method(callable) => { - ProtocolMemberKind::Method(callable.normalized(db)) + ProtocolMemberKind::Method(callable.normalized_impl(db, visitor)) } ProtocolMemberKind::Property(property) => { - ProtocolMemberKind::Property(property.normalized(db)) + ProtocolMemberKind::Property(property.normalized_impl(db, visitor)) + } + ProtocolMemberKind::Other(ty) => { + ProtocolMemberKind::Other(ty.normalized_impl(db, visitor)) } - ProtocolMemberKind::Other(ty) => ProtocolMemberKind::Other(ty.normalized(db)), } } @@ -513,15 +481,15 @@ fn cached_protocol_interface<'db>( (Type::Callable(callable), BoundOnClass::Yes) if callable.is_function_like(db) => { - ProtocolMemberKind::Method(ty.replace_self_reference(db, class)) + ProtocolMemberKind::Method(ty) } // TODO: method members that have `FunctionLiteral` types should be upcast // to `CallableType` so that two protocols with identical method members // are recognized as equivalent. (Type::FunctionLiteral(_function), BoundOnClass::Yes) => { - ProtocolMemberKind::Method(ty.replace_self_reference(db, class)) + ProtocolMemberKind::Method(ty) } - _ => ProtocolMemberKind::Other(ty.replace_self_reference(db, class)), + _ => ProtocolMemberKind::Other(ty), }; let member = ProtocolMemberData { kind, qualifiers }; @@ -530,7 +498,7 @@ fn cached_protocol_interface<'db>( ); } - ProtocolInterface::Members(ProtocolInterfaceMembers::new(db, members)) + ProtocolInterface::new(db, members) } #[allow(clippy::trivially_copy_pass_by_ref)] diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index ce8bda10c8..a9cd30ecc4 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -15,10 +15,10 @@ use std::{collections::HashMap, slice::Iter}; use itertools::EitherOrBoth; use smallvec::{SmallVec, smallvec}; -use super::{DynamicType, Type, TypeVarVariance, definition_expression_type}; +use super::{DynamicType, Type, TypeVarVariance, TypeVisitor, definition_expression_type}; use crate::semantic_index::definition::Definition; use crate::types::generics::GenericContext; -use crate::types::{ClassLiteral, TypeMapping, TypeRelation, TypeVarInstance, todo_type}; +use crate::types::{TypeMapping, TypeRelation, TypeVarInstance, todo_type}; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; @@ -61,11 +61,11 @@ impl<'db> CallableSignature<'db> { ) } - pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self::from_overloads( self.overloads .iter() - .map(|signature| signature.normalized(db)), + .map(|signature| signature.normalized_impl(db, visitor)), ) } @@ -197,17 +197,6 @@ impl<'db> CallableSignature<'db> { } } } - - pub(crate) fn replace_self_reference(&self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self { - Self { - overloads: self - .overloads - .iter() - .cloned() - .map(|signature| signature.replace_self_reference(db, class)) - .collect(), - } - } } impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> { @@ -345,16 +334,22 @@ impl<'db> Signature<'db> { } } - pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self { - generic_context: self.generic_context.map(|ctx| ctx.normalized(db)), - inherited_generic_context: self.inherited_generic_context.map(|ctx| ctx.normalized(db)), + generic_context: self + .generic_context + .map(|ctx| ctx.normalized_impl(db, visitor)), + inherited_generic_context: self + .inherited_generic_context + .map(|ctx| ctx.normalized_impl(db, visitor)), parameters: self .parameters .iter() - .map(|param| param.normalized(db)) + .map(|param| param.normalized_impl(db, visitor)) .collect(), - return_ty: self.return_ty.map(|return_ty| return_ty.normalized(db)), + return_ty: self + .return_ty + .map(|return_ty| return_ty.normalized_impl(db, visitor)), } } @@ -873,28 +868,6 @@ impl<'db> Signature<'db> { true } - - /// See [`Type::replace_self_reference`]. - pub(crate) fn replace_self_reference( - mut self, - db: &'db dyn Db, - class: ClassLiteral<'db>, - ) -> Self { - // TODO: also replace self references in generic context - - self.parameters = self - .parameters - .iter() - .cloned() - .map(|param| param.replace_self_reference(db, class)) - .collect(); - - if let Some(ty) = self.return_ty.as_mut() { - *ty = ty.replace_self_reference(db, class); - } - - self - } } #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)] @@ -1303,7 +1276,7 @@ impl<'db> Parameter<'db> { /// Normalize nested unions and intersections in the annotated type, if any. /// /// See [`Type::normalized`] for more details. - pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { let Parameter { annotated_type, kind, @@ -1311,7 +1284,7 @@ impl<'db> Parameter<'db> { } = self; // Ensure unions and intersections are ordered in the annotated type (if there is one) - let annotated_type = annotated_type.map(|ty| ty.normalized(db)); + let annotated_type = annotated_type.map(|ty| ty.normalized_impl(db, visitor)); // Ensure that parameter names are stripped from positional-only, variadic and keyword-variadic parameters. // Ensure that we only record whether a parameter *has* a default @@ -1444,14 +1417,6 @@ impl<'db> Parameter<'db> { ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => None, } } - - /// See [`Type::replace_self_reference`]. - fn replace_self_reference(mut self, db: &'db (dyn Db), class: ClassLiteral<'db>) -> Self { - if let Some(ty) = self.annotated_type.as_mut() { - *ty = ty.replace_self_reference(db, class); - } - self - } } #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)] diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index bb16be2a9d..f8345bbed3 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -3,7 +3,7 @@ use ruff_python_ast::name::Name; use crate::place::PlaceAndQualifiers; use crate::types::{ ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation, - TypeVarInstance, + TypeVarInstance, TypeVisitor, }; use crate::{Db, FxOrderSet}; @@ -171,9 +171,9 @@ impl<'db> SubclassOfType<'db> { } } - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { Self { - subclass_of: self.subclass_of.normalized(db), + subclass_of: self.subclass_of.normalized_impl(db, visitor), } } @@ -228,9 +228,9 @@ impl<'db> SubclassOfInner<'db> { } } - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { - Self::Class(class) => Self::Class(class.normalized(db)), + Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)), Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()), } } diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index 0843eb6e75..b38aab2af0 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -24,7 +24,8 @@ use itertools::{Either, EitherOrBoth, Itertools}; use crate::types::class::{ClassType, KnownClass}; use crate::types::{ - Type, TypeMapping, TypeRelation, TypeVarInstance, TypeVarVariance, UnionBuilder, UnionType, + Type, TypeMapping, TypeRelation, TypeVarInstance, TypeVarVariance, TypeVisitor, UnionBuilder, + UnionType, }; use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError}; use crate::{Db, FxOrderSet}; @@ -174,8 +175,12 @@ impl<'db> TupleType<'db> { /// /// See [`Type::normalized`] for more details. #[must_use] - pub(crate) fn normalized(self, db: &'db dyn Db) -> Option { - TupleType::new(db, self.tuple(db).normalized(db)) + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeVisitor<'db>, + ) -> Option { + TupleType::new(db, self.tuple(db).normalized_impl(db, visitor)) } pub(crate) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Option { @@ -327,8 +332,8 @@ impl<'db> FixedLengthTuple> { } #[must_use] - fn normalized(&self, db: &'db dyn Db) -> Self { - Self::from_elements(self.0.iter().map(|ty| ty.normalized(db))) + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor))) } fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { @@ -640,14 +645,16 @@ impl<'db> VariableLengthTuple> { } #[must_use] - fn normalized(&self, db: &'db dyn Db) -> TupleSpec<'db> { - Self::mixed( - self.prenormalized_prefix_elements(db, None) - .map(|ty| ty.normalized(db)), - self.variable.normalized(db), - self.prenormalized_suffix_elements(db, None) - .map(|ty| ty.normalized(db)), - ) + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> TupleSpec<'db> { + let prefix = self + .prenormalized_prefix_elements(db, None) + .map(|ty| ty.normalized_impl(db, visitor)) + .collect::>(); + let suffix = self + .prenormalized_suffix_elements(db, None) + .map(|ty| ty.normalized_impl(db, visitor)) + .collect::>(); + Self::mixed(prefix, self.variable.normalized(db), suffix) } fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> TupleSpec<'db> { @@ -977,10 +984,10 @@ impl<'db> Tuple> { } } - pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { + pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { match self { - Tuple::Fixed(tuple) => Tuple::Fixed(tuple.normalized(db)), - Tuple::Variable(tuple) => tuple.normalized(db), + Tuple::Fixed(tuple) => Tuple::Fixed(tuple.normalized_impl(db, visitor)), + Tuple::Variable(tuple) => tuple.normalized_impl(db, visitor), } }