diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index f0537886a9..fe8305e77e 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -43,6 +43,7 @@ pub mod pull_types; type FxOrderSet = ordermap::set::OrderSet>; type FxIndexMap = indexmap::IndexMap>; +type FxIndexSet = indexmap::IndexSet>; /// Returns the default registry with all known semantic lints. pub fn default_lint_registry() -> &'static LintRegistry { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index eb2321767c..b68489a78b 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -19,7 +19,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(crate) use self::cyclic::TypeTransformer; pub use self::diagnostic::TypeCheckDiagnostics; pub(crate) use self::diagnostic::register_lints; pub(crate) use self::infer::{ @@ -42,12 +42,15 @@ use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::function::{ DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, }; -use crate::types::generics::{GenericContext, PartialSpecialization, Specialization}; +use crate::types::generics::{ + GenericContext, PartialSpecialization, Specialization, walk_generic_context, + walk_partial_specialization, walk_specialization, +}; pub use crate::types::ide_support::all_members; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; -use crate::types::signatures::{Parameter, ParameterForm, Parameters}; +use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature}; use crate::types::tuple::{TupleSpec, TupleType}; pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic; use crate::{Db, FxOrderSet, Module, Program}; @@ -79,6 +82,7 @@ mod subclass_of; mod tuple; mod type_ordering; mod unpacker; +mod visitor; mod definition; #[cfg(test)] @@ -368,6 +372,19 @@ pub struct PropertyInstanceType<'db> { setter: Option>, } +fn walk_property_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + property: PropertyInstanceType<'db>, + visitor: &mut V, +) { + if let Some(getter) = property.getter(db) { + visitor.visit_type(db, getter); + } + if let Some(setter) = property.setter(db) { + visitor.visit_type(db, setter); + } +} + // The Salsa heap is tracked separately. impl get_size2::GetSize for PropertyInstanceType<'_> {} @@ -382,7 +399,7 @@ impl<'db> PropertyInstanceType<'db> { Self::new(db, getter, setter) } - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { Self::new( db, self.getter(db).map(|ty| ty.normalized_impl(db, visitor)), @@ -410,14 +427,6 @@ impl<'db> PropertyInstanceType<'db> { self.setter(db).map(|ty| ty.materialize(db, variance)), ) } - - fn any_over_type(self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool { - self.getter(db) - .is_some_and(|ty| ty.any_over_type(db, type_fn)) - || self - .setter(db) - .is_some_and(|ty| ty.any_over_type(db, type_fn)) - } } bitflags! { @@ -751,110 +760,6 @@ impl<'db> Type<'db> { } } - /// 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) { - return true; - } - - match self { - Self::AlwaysFalsy - | Self::AlwaysTruthy - | Self::Never - | Self::BooleanLiteral(_) - | Self::BytesLiteral(_) - | Self::ModuleLiteral(_) - | Self::FunctionLiteral(_) - | Self::ClassLiteral(_) - | Self::SpecialForm(_) - | Self::KnownInstance(_) - | Self::StringLiteral(_) - | Self::IntLiteral(_) - | Self::LiteralString - | Self::Dynamic(_) - | Self::BoundMethod(_) - | Self::WrapperDescriptor(_) - | Self::MethodWrapper(_) - | Self::DataclassDecorator(_) - | Self::DataclassTransformer(_) => false, - - Self::GenericAlias(generic) => generic - .specialization(db) - .types(db) - .iter() - .copied() - .any(|ty| ty.any_over_type(db, type_fn)), - - Self::Callable(callable) => { - let signatures = callable.signatures(db); - signatures.iter().any(|signature| { - signature.parameters().iter().any(|param| { - param - .annotated_type() - .is_some_and(|ty| ty.any_over_type(db, type_fn)) - }) || signature - .return_ty - .is_some_and(|ty| ty.any_over_type(db, type_fn)) - }) - } - - Self::SubclassOf(subclass_of) => { - Type::from(subclass_of.subclass_of()).any_over_type(db, type_fn) - } - - Self::TypeVar(typevar) => match typevar.bound_or_constraints(db) { - None => false, - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - bound.any_over_type(db, type_fn) - } - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints - .elements(db) - .iter() - .any(|constraint| constraint.any_over_type(db, type_fn)), - }, - - Self::BoundSuper(bound_super) => { - Type::from(bound_super.pivot_class(db)).any_over_type(db, type_fn) - || Type::from(bound_super.owner(db)).any_over_type(db, type_fn) - } - - Self::Tuple(tuple) => tuple - .tuple(db) - .all_elements() - .any(|ty| ty.any_over_type(db, type_fn)), - - Self::Union(union) => union - .elements(db) - .iter() - .any(|ty| ty.any_over_type(db, type_fn)), - - Self::Intersection(intersection) => { - intersection - .positive(db) - .iter() - .any(|ty| ty.any_over_type(db, type_fn)) - || intersection - .negative(db) - .iter() - .any(|ty| ty.any_over_type(db, type_fn)) - } - - Self::ProtocolInstance(protocol) => protocol.any_over_type(db, type_fn), - Self::PropertyInstance(property) => property.any_over_type(db, type_fn), - - Self::NominalInstance(instance) => match instance.class { - ClassType::NonGeneric(_) => false, - ClassType::Generic(generic) => generic - .specialization(db) - .types(db) - .iter() - .any(|ty| ty.any_over_type(db, type_fn)), - }, - - Self::TypeIs(type_is) => type_is.return_type(db).any_over_type(db, type_fn), - } - } - pub const fn into_class_literal(self) -> Option> { match self { Type::ClassLiteral(class_type) => Some(class_type), @@ -1068,12 +973,16 @@ 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(); + let mut visitor = TypeTransformer::default(); self.normalized_impl(db, &mut visitor) } #[must_use] - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { match self { Type::Union(union) => { visitor.visit(self, |v| Type::Union(union.normalized_impl(db, v))) @@ -5733,6 +5642,22 @@ pub enum TypeMapping<'a, 'db> { PromoteLiterals, } +fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + mapping: &TypeMapping<'_, 'db>, + visitor: &mut V, +) { + match mapping { + TypeMapping::Specialization(specialization) => { + walk_specialization(db, *specialization, visitor); + } + TypeMapping::PartialSpecialization(specialization) => { + walk_partial_specialization(db, specialization, visitor); + } + TypeMapping::PromoteLiterals => {} + } +} + impl<'db> TypeMapping<'_, 'db> { fn to_owned(&self) -> TypeMapping<'db, 'db> { match self { @@ -5746,7 +5671,7 @@ impl<'db> TypeMapping<'_, 'db> { } } - fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { match self { TypeMapping::Specialization(specialization) => { TypeMapping::Specialization(specialization.normalized_impl(db, visitor)) @@ -5797,8 +5722,27 @@ pub enum KnownInstanceType<'db> { TypeAliasType(TypeAliasType<'db>), } +fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + known_instance: KnownInstanceType<'db>, + visitor: &mut V, +) { + match known_instance { + KnownInstanceType::SubscriptedProtocol(context) + | KnownInstanceType::SubscriptedGeneric(context) => { + walk_generic_context(db, context, visitor); + } + KnownInstanceType::TypeVar(typevar) => { + visitor.visit_type_var_type(db, typevar); + } + KnownInstanceType::TypeAliasType(type_alias) => { + visitor.visit_type_alias_type(db, type_alias); + } + } +} + impl<'db> KnownInstanceType<'db> { - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { match self { Self::SubscriptedProtocol(context) => { Self::SubscriptedProtocol(context.normalized_impl(db, visitor)) @@ -6169,6 +6113,19 @@ pub struct TypeVarInstance<'db> { // 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, + type_var: TypeVarInstance<'db>, + visitor: &mut V, +) { + if let Some(bounds) = type_var.bound_or_constraints(db) { + walk_type_var_bounds(db, bounds, visitor); + } + if let Some(default_type) = type_var.default_ty(db) { + visitor.visit_type(db, default_type); + } +} + impl<'db> TypeVarInstance<'db> { pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool { matches!(self.kind(db), TypeVarKind::Legacy) @@ -6190,7 +6147,11 @@ impl<'db> TypeVarInstance<'db> { } } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self::new( db, self.name(db), @@ -6245,8 +6206,21 @@ pub enum TypeVarBoundOrConstraints<'db> { Constraints(UnionType<'db>), } +fn walk_type_var_bounds<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + bounds: TypeVarBoundOrConstraints<'db>, + visitor: &mut V, +) { + match bounds { + TypeVarBoundOrConstraints::UpperBound(bound) => visitor.visit_type(db, bound), + TypeVarBoundOrConstraints::Constraints(constraints) => { + visitor.visit_union_type(db, constraints); + } + } +} + impl<'db> TypeVarBoundOrConstraints<'db> { - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { match self { TypeVarBoundOrConstraints::UpperBound(bound) => { TypeVarBoundOrConstraints::UpperBound(bound.normalized_impl(db, visitor)) @@ -7149,6 +7123,15 @@ pub struct BoundMethodType<'db> { // The Salsa heap is tracked separately. impl get_size2::GetSize for BoundMethodType<'_> {} +fn walk_bound_method_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + method: BoundMethodType<'db>, + visitor: &mut V, +) { + visitor.visit_function_type(db, method.function(db)); + visitor.visit_type(db, method.self_instance(db)); +} + impl<'db> BoundMethodType<'db> { pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { Type::Callable(CallableType::new( @@ -7164,7 +7147,7 @@ impl<'db> BoundMethodType<'db> { )) } - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { Self::new( db, self.function(db).normalized_impl(db, visitor), @@ -7214,6 +7197,16 @@ pub struct CallableType<'db> { is_function_like: bool, } +pub(super) fn walk_callable_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + ty: CallableType<'db>, + visitor: &mut V, +) { + for signature in &ty.signatures(db).overloads { + walk_signature(db, signature, visitor); + } +} + // The Salsa heap is tracked separately. impl get_size2::GetSize for CallableType<'_> {} @@ -7271,7 +7264,7 @@ impl<'db> CallableType<'db> { /// Return a "normalized" version of this `Callable` type. /// /// See [`Type::normalized`] for more details. - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { CallableType::new( db, self.signatures(db).normalized_impl(db, visitor), @@ -7338,6 +7331,30 @@ pub enum MethodWrapperKind<'db> { StrStartswith(StringLiteralType<'db>), } +pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + method_wrapper: MethodWrapperKind<'db>, + visitor: &mut V, +) { + match method_wrapper { + MethodWrapperKind::FunctionTypeDunderGet(function) => { + visitor.visit_function_type(db, function); + } + MethodWrapperKind::FunctionTypeDunderCall(function) => { + visitor.visit_function_type(db, function); + } + MethodWrapperKind::PropertyDunderGet(property) => { + visitor.visit_property_instance_type(db, property); + } + MethodWrapperKind::PropertyDunderSet(property) => { + visitor.visit_property_instance_type(db, property); + } + MethodWrapperKind::StrStartswith(string_literal) => { + visitor.visit_type(db, Type::StringLiteral(string_literal)); + } + } +} + impl<'db> MethodWrapperKind<'db> { fn has_relation_to(self, db: &'db dyn Db, other: Self, relation: TypeRelation) -> bool { match (self, other) { @@ -7405,7 +7422,7 @@ impl<'db> MethodWrapperKind<'db> { } } - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { match self { MethodWrapperKind::FunctionTypeDunderGet(function) => { MethodWrapperKind::FunctionTypeDunderGet(function.normalized_impl(db, visitor)) @@ -7512,6 +7529,14 @@ pub struct PEP695TypeAliasType<'db> { // The Salsa heap is tracked separately. impl get_size2::GetSize for PEP695TypeAliasType<'_> {} +fn walk_pep_695_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + type_alias: PEP695TypeAliasType<'db>, + visitor: &mut V, +) { + visitor.visit_type(db, type_alias.value_type(db)); +} + #[salsa::tracked] impl<'db> PEP695TypeAliasType<'db> { pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { @@ -7531,7 +7556,7 @@ impl<'db> PEP695TypeAliasType<'db> { definition_expression_type(db, definition, &type_alias_stmt_node.value) } - fn normalized_impl(self, _db: &'db dyn Db, _visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, _db: &'db dyn Db, _visitor: &mut TypeTransformer<'db>) -> Self { self } } @@ -7551,8 +7576,16 @@ pub struct BareTypeAliasType<'db> { // The Salsa heap is tracked separately. impl get_size2::GetSize for BareTypeAliasType<'_> {} +fn walk_bare_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + type_alias: BareTypeAliasType<'db>, + visitor: &mut V, +) { + visitor.visit_type(db, type_alias.value(db)); +} + impl<'db> BareTypeAliasType<'db> { - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { Self::new( db, self.name(db), @@ -7570,8 +7603,27 @@ pub enum TypeAliasType<'db> { Bare(BareTypeAliasType<'db>), } +fn walk_type_alias_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + type_alias: TypeAliasType<'db>, + visitor: &mut V, +) { + match type_alias { + TypeAliasType::PEP695(type_alias) => { + walk_pep_695_type_alias(db, type_alias, visitor); + } + TypeAliasType::Bare(type_alias) => { + walk_bare_type_alias(db, type_alias, visitor); + } + } +} + impl<'db> TypeAliasType<'db> { - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { match self { TypeAliasType::PEP695(type_alias) => { TypeAliasType::PEP695(type_alias.normalized_impl(db, visitor)) @@ -7618,6 +7670,16 @@ pub struct UnionType<'db> { pub elements: Box<[Type<'db>]>, } +pub(crate) fn walk_union<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + union: UnionType<'db>, + visitor: &mut V, +) { + for element in union.elements(db) { + visitor.visit_type(db, *element); + } +} + // The Salsa heap is tracked separately. impl get_size2::GetSize for UnionType<'_> {} @@ -7787,10 +7849,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()) + self.normalized_impl(db, &mut TypeTransformer::default()) } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { let mut new_elements: Vec> = self .elements(db) .iter() @@ -7841,6 +7907,19 @@ pub struct IntersectionType<'db> { // The Salsa heap is tracked separately. impl get_size2::GetSize for IntersectionType<'_> {} +pub(super) fn walk_intersection_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + intersection: IntersectionType<'db>, + visitor: &mut V, +) { + for element in intersection.positive(db) { + visitor.visit_type(db, *element); + } + for element in intersection.negative(db) { + visitor.visit_type(db, *element); + } +} + impl<'db> IntersectionType<'db> { /// Return a new `IntersectionType` instance with the positive and negative types sorted /// according to a canonical ordering, and other normalizations applied to each element as applicable. @@ -7848,15 +7927,19 @@ 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(); + let mut visitor = TypeTransformer::default(); self.normalized_impl(db, &mut visitor) } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { fn normalized_set<'db>( db: &'db dyn Db, elements: &FxOrderSet>, - visitor: &mut TypeVisitor<'db>, + visitor: &mut TypeTransformer<'db>, ) -> FxOrderSet> { let mut elements: FxOrderSet> = elements .iter() @@ -8109,7 +8192,7 @@ pub enum SuperOwnerKind<'db> { } impl<'db> SuperOwnerKind<'db> { - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { match self { SuperOwnerKind::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic.normalized()), SuperOwnerKind::Class(class) => { @@ -8197,6 +8280,15 @@ pub struct BoundSuperType<'db> { // The Salsa heap is tracked separately. impl get_size2::GetSize for BoundSuperType<'_> {} +fn walk_bound_super_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + bound_super: BoundSuperType<'db>, + visitor: &mut V, +) { + visitor.visit_type(db, bound_super.pivot_class(db).into()); + visitor.visit_type(db, bound_super.owner(db).into_type()); +} + impl<'db> BoundSuperType<'db> { /// Attempts to build a `Type::BoundSuper` based on the given `pivot_class` and `owner`. /// @@ -8358,7 +8450,11 @@ impl<'db> BoundSuperType<'db> { } } - pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(super) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self::new( db, self.pivot_class(db).normalized_impl(db, visitor), @@ -8375,6 +8471,14 @@ pub struct TypeIsType<'db> { place_info: Option<(ScopeId<'db>, ScopedPlaceId)>, } +fn walk_typeis_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + typeis_type: TypeIsType<'db>, + visitor: &mut V, +) { + visitor.visit_type(db, typeis_type.return_type(db)); +} + // The Salsa heap is tracked separately. impl get_size2::GetSize for TypeIsType<'_> {} diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 65b8815257..b013a39b90 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -15,14 +15,14 @@ use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex}; use crate::types::context::InferContext; use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE}; use crate::types::function::{DataclassTransformerParams, KnownFunction}; -use crate::types::generics::{GenericContext, Specialization}; +use crate::types::generics::{GenericContext, Specialization, walk_specialization}; use crate::types::infer::nearest_enclosing_class; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::tuple::TupleType; use crate::types::{ BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams, - KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, - TypeVarInstance, TypeVarKind, TypeVisitor, infer_definition_types, + KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeTransformer, + TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, infer_definition_types, }; use crate::{ Db, FxOrderSet, KnownModule, Program, @@ -177,11 +177,23 @@ pub struct GenericAlias<'db> { pub(crate) specialization: Specialization<'db>, } +pub(super) fn walk_generic_alias<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + alias: GenericAlias<'db>, + visitor: &mut V, +) { + walk_specialization(db, alias.specialization(db), visitor); +} + // The Salsa heap is tracked separately. impl get_size2::GetSize for GenericAlias<'_> {} impl<'db> GenericAlias<'db> { - pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(super) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self::new( db, self.origin(db), @@ -252,7 +264,11 @@ pub enum ClassType<'db> { #[salsa::tracked] impl<'db> ClassType<'db> { - pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(super) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { match self { Self::NonGeneric(_) => self, 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 24d3e00610..ce5686e9c1 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, TypeVisitor, todo_type, + Type, TypeMapping, TypeTransformer, todo_type, }; /// Enumeration of the possible kinds of types we allow in class bases. @@ -31,7 +31,11 @@ impl<'db> ClassBase<'db> { Self::Dynamic(DynamicType::Unknown) } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { match self { Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()), Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)), diff --git a/crates/ty_python_semantic/src/types/cyclic.rs b/crates/ty_python_semantic/src/types/cyclic.rs index f3c7f3a42a..ac6ba624a9 100644 --- a/crates/ty_python_semantic/src/types/cyclic.rs +++ b/crates/ty_python_semantic/src/types/cyclic.rs @@ -1,12 +1,12 @@ -use crate::FxOrderSet; +use crate::FxIndexSet; use crate::types::Type; #[derive(Debug, Default)] -pub(crate) struct TypeVisitor<'db> { - seen: FxOrderSet>, +pub(crate) struct TypeTransformer<'db> { + seen: FxIndexSet>, } -impl<'db> TypeVisitor<'db> { +impl<'db> TypeTransformer<'db> { pub(crate) fn visit( &mut self, ty: Type<'db>, diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 7297e32996..7b59835bd8 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -70,12 +70,13 @@ use crate::types::diagnostic::{ report_bad_argument_to_get_protocol_members, report_runtime_check_against_non_runtime_checkable_protocol, }; -use crate::types::generics::GenericContext; +use crate::types::generics::{GenericContext, walk_generic_context}; use crate::types::narrow::ClassInfoConstraintFunction; use crate::types::signatures::{CallableSignature, Signature}; +use crate::types::visitor::any_over_type; use crate::types::{ BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation, - TypeVarInstance, TypeVisitor, + TypeTransformer, TypeVarInstance, walk_type_mapping, }; use crate::{Db, FxOrderSet, ModuleName, resolve_module}; @@ -421,6 +422,16 @@ pub struct FunctionLiteral<'db> { inherited_generic_context: Option>, } +fn walk_function_literal<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + function: FunctionLiteral<'db>, + visitor: &mut V, +) { + if let Some(context) = function.inherited_generic_context(db) { + walk_generic_context(db, context, visitor); + } +} + #[salsa::tracked] impl<'db> FunctionLiteral<'db> { fn with_inherited_generic_context( @@ -545,7 +556,7 @@ impl<'db> FunctionLiteral<'db> { })) } - fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { let context = self .inherited_generic_context(db) .map(|ctx| ctx.normalized_impl(db, visitor)); @@ -570,6 +581,17 @@ pub struct FunctionType<'db> { // The Salsa heap is tracked separately. impl get_size2::GetSize for FunctionType<'_> {} +pub(super) fn walk_function_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + function: FunctionType<'db>, + visitor: &mut V, +) { + walk_function_literal(db, function.literal(db), visitor); + for mapping in function.type_mappings(db) { + walk_type_mapping(db, mapping, visitor); + } +} + #[salsa::tracked] impl<'db> FunctionType<'db> { pub(crate) fn with_inherited_generic_context( @@ -819,11 +841,15 @@ impl<'db> FunctionType<'db> { } pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { - let mut visitor = TypeVisitor::default(); + let mut visitor = TypeTransformer::default(); self.normalized_impl(db, &mut visitor) } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { let mappings: Box<_> = self .type_mappings(db) .iter() @@ -1148,8 +1174,8 @@ impl KnownFunction { let contains_unknown_or_todo = |ty| matches!(ty, Type::Dynamic(dynamic) if dynamic != DynamicType::Any); if source_type.is_equivalent_to(db, *casted_type) - && !casted_type.any_over_type(db, &|ty| contains_unknown_or_todo(ty)) - && !source_type.any_over_type(db, &|ty| contains_unknown_or_todo(ty)) + && !any_over_type(db, *source_type, &contains_unknown_or_todo) + && !any_over_type(db, *casted_type, &contains_unknown_or_todo) { let builder = context.report_lint(&REDUNDANT_CAST, call_expression)?; builder.into_diagnostic(format_args!( diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 356fc64a22..e6714330d9 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -10,8 +10,8 @@ use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::{ - KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, - TypeVarVariance, TypeVisitor, UnionType, declaration_type, + KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints, + TypeVarInstance, TypeVarVariance, UnionType, declaration_type, }; use crate::{Db, FxOrderSet}; @@ -30,6 +30,16 @@ pub struct GenericContext<'db> { pub(crate) variables: FxOrderSet>, } +pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + context: GenericContext<'db>, + visitor: &mut V, +) { + for typevar in context.variables(db) { + visitor.visit_type_var_type(db, *typevar); + } +} + // The Salsa heap is tracked separately. impl get_size2::GetSize for GenericContext<'_> {} @@ -233,7 +243,11 @@ impl<'db> GenericContext<'db> { Specialization::new(db, self, expanded.into_boxed_slice(), None) } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { let variables: FxOrderSet<_> = self .variables(db) .iter() @@ -279,6 +293,20 @@ pub struct Specialization<'db> { tuple_inner: Option>, } +pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + specialization: Specialization<'db>, + visitor: &mut V, +) { + walk_generic_context(db, specialization.generic_context(db), visitor); + for ty in specialization.types(db) { + visitor.visit_type(db, *ty); + } + if let Some(tuple) = specialization.tuple_inner(db) { + visitor.visit_tuple_type(db, tuple); + } +} + impl<'db> Specialization<'db> { /// Returns the tuple spec for a specialization of the `tuple` class. pub(crate) fn tuple(self, db: &'db dyn Db) -> &'db TupleSpec<'db> { @@ -376,7 +404,11 @@ impl<'db> Specialization<'db> { Specialization::new(db, self.generic_context(db), types, None) } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { let types: Box<[_]> = self .types(db) .iter() @@ -518,6 +550,17 @@ pub struct PartialSpecialization<'a, 'db> { types: Cow<'a, [Type<'db>]>, } +pub(super) fn walk_partial_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + specialization: &PartialSpecialization<'_, 'db>, + visitor: &mut V, +) { + walk_generic_context(db, specialization.generic_context, visitor); + for ty in &*specialization.types { + visitor.visit_type(db, *ty); + } +} + impl<'db> PartialSpecialization<'_, 'db> { /// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this /// mapping. @@ -536,7 +579,7 @@ impl<'db> PartialSpecialization<'_, 'db> { pub(crate) fn normalized_impl( &self, db: &'db dyn Db, - visitor: &mut TypeVisitor<'db>, + visitor: &mut TypeTransformer<'db>, ) -> PartialSpecialization<'db, 'db> { let generic_context = self.generic_context.normalized_impl(db, visitor); let types: Cow<_> = self diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index f5038da6fc..9c7c57ec92 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -5,8 +5,9 @@ use std::marker::PhantomData; use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance}; use crate::place::PlaceAndQualifiers; +use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::TupleType; -use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeVarInstance, TypeVisitor}; +use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance}; use crate::{Db, FxOrderSet}; pub(super) use synthesized_protocol::SynthesizedProtocolType; @@ -44,7 +45,7 @@ impl<'db> Type<'db> { SynthesizedProtocolType::new( db, ProtocolInterface::with_property_members(db, members), - &mut TypeVisitor::default(), + &mut TypeTransformer::default(), ), )) } @@ -74,6 +75,14 @@ pub struct NominalInstanceType<'db> { _phantom: PhantomData<()>, } +pub(super) fn walk_nominal_instance_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + nominal: NominalInstanceType<'db>, + visitor: &mut V, +) { + visitor.visit_type(db, nominal.class.into()); +} + impl<'db> NominalInstanceType<'db> { // Keep this method private, so that the only way of constructing `NominalInstanceType` // instances is through the `Type::instance` constructor function. @@ -84,7 +93,11 @@ impl<'db> NominalInstanceType<'db> { } } - pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(super) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self::from_class(self.class.normalized_impl(db, visitor)) } @@ -160,6 +173,14 @@ pub struct ProtocolInstanceType<'db> { _phantom: PhantomData<()>, } +pub(super) fn walk_protocol_instance_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + protocol: ProtocolInstanceType<'db>, + visitor: &mut V, +) { + walk_protocol_interface(db, protocol.inner.interface(db), visitor); +} + impl<'db> ProtocolInstanceType<'db> { // Keep this method private, so that the only way of constructing `ProtocolInstanceType` // instances is through the `Type::instance` constructor function. @@ -205,7 +226,7 @@ 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(); + let mut visitor = TypeTransformer::default(); self.normalized_impl(db, &mut visitor) } @@ -215,7 +236,7 @@ impl<'db> ProtocolInstanceType<'db> { pub(super) fn normalized_impl( self, db: &'db dyn Db, - visitor: &mut TypeVisitor<'db>, + visitor: &mut TypeTransformer<'db>, ) -> Type<'db> { let object = KnownClass::Object.to_instance(db); if object.satisfies_protocol(db, self, TypeRelation::Subtyping) { @@ -229,15 +250,6 @@ impl<'db> ProtocolInstanceType<'db> { } } - /// Return `true` if the types of any of the members match the closure passed in. - pub(super) fn any_over_type( - self, - db: &'db dyn Db, - type_fn: &dyn Fn(Type<'db>) -> bool, - ) -> bool { - self.inner.interface(db).any_over_type(db, type_fn) - } - /// Return `true` if this protocol type has the given type relation to the protocol `other`. /// /// TODO: consider the types of the members as well as their existence @@ -348,7 +360,7 @@ impl<'db> Protocol<'db> { mod synthesized_protocol { use crate::types::protocol_class::ProtocolInterface; - use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance, TypeVisitor}; + use crate::types::{TypeMapping, TypeTransformer, TypeVarInstance, TypeVarVariance}; use crate::{Db, FxOrderSet}; /// A "synthesized" protocol type that is dissociated from a class definition in source code. @@ -369,7 +381,7 @@ mod synthesized_protocol { pub(super) fn new( db: &'db dyn Db, interface: ProtocolInterface<'db>, - visitor: &mut TypeVisitor<'db>, + visitor: &mut TypeTransformer<'db>, ) -> Self { Self(interface.normalized_impl(db, visitor)) } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 5ac8324328..a21593291d 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -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, TypeVisitor, + Type, TypeMapping, TypeQualifiers, TypeRelation, TypeTransformer, TypeVarInstance, signatures::{Parameter, Parameters}, }, }; @@ -76,6 +76,16 @@ pub(super) struct ProtocolInterface<'db> { impl get_size2::GetSize for ProtocolInterface<'_> {} +pub(super) fn walk_protocol_interface<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + interface: ProtocolInterface<'db>, + visitor: &mut V, +) { + for member in interface.members(db) { + walk_protocol_member(db, &member, visitor); + } +} + impl<'db> ProtocolInterface<'db> { /// Synthesize a new protocol interface with the given members. /// @@ -152,17 +162,11 @@ impl<'db> ProtocolInterface<'db> { .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. - pub(super) fn any_over_type( + pub(super) fn normalized_impl( self, db: &'db dyn Db, - type_fn: &dyn Fn(Type<'db>) -> bool, - ) -> bool { - self.members(db) - .any(|member| member.any_over_type(db, type_fn)) - } - - pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self::new( db, self.inner(db) @@ -220,10 +224,10 @@ pub(super) struct ProtocolMemberData<'db> { impl<'db> ProtocolMemberData<'db> { fn normalized(&self, db: &'db dyn Db) -> Self { - self.normalized_impl(db, &mut TypeVisitor::default()) + self.normalized_impl(db, &mut TypeTransformer::default()) } - fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { Self { kind: self.kind.normalized_impl(db, visitor), qualifiers: self.qualifiers, @@ -261,7 +265,7 @@ enum ProtocolMemberKind<'db> { } impl<'db> ProtocolMemberKind<'db> { - fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { match self { ProtocolMemberKind::Method(callable) => { ProtocolMemberKind::Method(callable.normalized_impl(db, visitor)) @@ -324,6 +328,20 @@ pub(super) struct ProtocolMember<'a, 'db> { qualifiers: TypeQualifiers, } +fn walk_protocol_member<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + member: &ProtocolMember<'_, 'db>, + visitor: &mut V, +) { + match member.kind { + ProtocolMemberKind::Method(method) => visitor.visit_type(db, method), + ProtocolMemberKind::Property(property) => { + visitor.visit_property_instance_type(db, property); + } + ProtocolMemberKind::Other(ty) => visitor.visit_type(db, ty), + } +} + impl<'a, 'db> ProtocolMember<'a, 'db> { pub(super) fn name(&self) -> &'a str { self.name @@ -371,14 +389,6 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { } } } - - fn any_over_type(&self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool { - match &self.kind { - ProtocolMemberKind::Method(callable) => callable.any_over_type(db, type_fn), - ProtocolMemberKind::Property(property) => property.any_over_type(db, type_fn), - ProtocolMemberKind::Other(ty) => ty.any_over_type(db, type_fn), - } - } } /// Returns `true` if a declaration or binding to a given name in a protocol class body diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 5678d5c217..58e3881a21 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -15,9 +15,9 @@ use std::{collections::HashMap, slice::Iter}; use itertools::EitherOrBoth; use smallvec::{SmallVec, smallvec}; -use super::{DynamicType, Type, TypeVarVariance, TypeVisitor, definition_expression_type}; +use super::{DynamicType, Type, TypeTransformer, TypeVarVariance, definition_expression_type}; use crate::semantic_index::definition::Definition; -use crate::types::generics::GenericContext; +use crate::types::generics::{GenericContext, walk_generic_context}; use crate::types::{TypeMapping, TypeRelation, TypeVarInstance, todo_type}; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; @@ -61,7 +61,11 @@ impl<'db> CallableSignature<'db> { ) } - pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + &self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self::from_overloads( self.overloads .iter() @@ -233,6 +237,29 @@ pub struct Signature<'db> { pub(crate) return_ty: Option>, } +pub(super) fn walk_signature<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + signature: &Signature<'db>, + visitor: &mut V, +) { + if let Some(generic_context) = &signature.generic_context { + walk_generic_context(db, *generic_context, visitor); + } + if let Some(inherited_generic_context) = &signature.inherited_generic_context { + walk_generic_context(db, *inherited_generic_context, visitor); + } + // By default we usually don't visit the type of the default value, + // as it isn't relevant to most things + for parameter in &signature.parameters { + if let Some(ty) = parameter.annotated_type() { + visitor.visit_type(db, ty); + } + } + if let Some(return_ty) = &signature.return_ty { + visitor.visit_type(db, *return_ty); + } +} + impl<'db> Signature<'db> { pub(crate) fn new(parameters: Parameters<'db>, return_ty: Option>) -> Self { Self { @@ -334,7 +361,11 @@ impl<'db> Signature<'db> { } } - pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + &self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self { generic_context: self .generic_context @@ -1276,7 +1307,11 @@ 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_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + &self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { let Parameter { annotated_type, kind, diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index f8345bbed3..474bb5f34e 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, TypeVisitor, + TypeTransformer, TypeVarInstance, }; use crate::{Db, FxOrderSet}; @@ -16,6 +16,14 @@ pub struct SubclassOfType<'db> { subclass_of: SubclassOfInner<'db>, } +pub(super) fn walk_subclass_of_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + subclass_of: SubclassOfType<'db>, + visitor: &mut V, +) { + visitor.visit_type(db, Type::from(subclass_of.subclass_of)); +} + impl<'db> SubclassOfType<'db> { /// Construct a new [`Type`] instance representing a given class object (or a given dynamic type) /// and all possible subclasses of that class object/dynamic type. @@ -171,7 +179,11 @@ impl<'db> SubclassOfType<'db> { } } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { Self { subclass_of: self.subclass_of.normalized_impl(db, visitor), } @@ -228,7 +240,11 @@ impl<'db> SubclassOfInner<'db> { } } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { match self { 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 aa3b0e56d6..2d3caf4e86 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -24,8 +24,8 @@ use itertools::{Either, EitherOrBoth, Itertools}; use crate::types::class::{ClassType, KnownClass}; use crate::types::{ - Type, TypeMapping, TypeRelation, TypeVarInstance, TypeVarVariance, TypeVisitor, UnionBuilder, - UnionType, + Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance, + UnionBuilder, UnionType, }; use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError}; use crate::{Db, FxOrderSet}; @@ -88,6 +88,16 @@ pub struct TupleType<'db> { pub(crate) tuple: TupleSpec<'db>, } +pub(super) fn walk_tuple_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + tuple: TupleType<'db>, + visitor: &mut V, +) { + for element in tuple.tuple(db).all_elements() { + visitor.visit_type(db, *element); + } +} + // The Salsa heap is tracked separately. impl get_size2::GetSize for TupleType<'_> {} @@ -178,7 +188,7 @@ impl<'db> TupleType<'db> { pub(crate) fn normalized_impl( self, db: &'db dyn Db, - visitor: &mut TypeVisitor<'db>, + visitor: &mut TypeTransformer<'db>, ) -> Option { TupleType::new(db, self.tuple(db).normalized_impl(db, visitor)) } @@ -332,7 +342,7 @@ impl<'db> FixedLengthTuple> { } #[must_use] - fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self { Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor))) } @@ -645,7 +655,11 @@ impl<'db> VariableLengthTuple> { } #[must_use] - fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> TupleSpec<'db> { + fn normalized_impl( + &self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> TupleSpec<'db> { let prefix = self .prenormalized_prefix_elements(db, None) .map(|ty| ty.normalized_impl(db, visitor)) @@ -985,7 +999,11 @@ impl<'db> Tuple> { } } - pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self { + pub(crate) fn normalized_impl( + &self, + db: &'db dyn Db, + visitor: &mut TypeTransformer<'db>, + ) -> Self { match self { Tuple::Fixed(tuple) => Tuple::Fixed(tuple.normalized_impl(db, visitor)), Tuple::Variable(tuple) => tuple.normalized_impl(db, visitor), diff --git a/crates/ty_python_semantic/src/types/visitor.rs b/crates/ty_python_semantic/src/types/visitor.rs new file mode 100644 index 0000000000..1deaef62fc --- /dev/null +++ b/crates/ty_python_semantic/src/types/visitor.rs @@ -0,0 +1,275 @@ +use crate::{ + Db, FxIndexSet, + types::{ + BoundMethodType, BoundSuperType, CallableType, GenericAlias, IntersectionType, + KnownInstanceType, MethodWrapperKind, NominalInstanceType, PropertyInstanceType, + ProtocolInstanceType, SubclassOfType, Type, TypeAliasType, TypeIsType, TypeVarInstance, + UnionType, + class::walk_generic_alias, + function::{FunctionType, walk_function_type}, + instance::{walk_nominal_instance_type, walk_protocol_instance_type}, + subclass_of::walk_subclass_of_type, + tuple::{TupleType, walk_tuple_type}, + walk_bound_method_type, walk_bound_super_type, walk_callable_type, walk_intersection_type, + walk_known_instance_type, walk_method_wrapper_type, walk_property_instance_type, + walk_type_alias_type, walk_type_var_type, walk_typeis_type, walk_union, + }, +}; + +/// A visitor trait that recurses into nested types. +/// +/// The trait does not guard against infinite recursion out of the box, +/// but it makes it easy for implementors of the trait to do so. +/// See [`any_over_type`] for an example of how to do this. +pub(crate) trait TypeVisitor<'db> { + fn visit_type(&mut self, db: &'db dyn Db, ty: Type<'db>); + + fn visit_union_type(&mut self, db: &'db dyn Db, union: UnionType<'db>) { + walk_union(db, union, self); + } + + fn visit_intersection_type(&mut self, db: &'db dyn Db, intersection: IntersectionType<'db>) { + walk_intersection_type(db, intersection, self); + } + + fn visit_tuple_type(&mut self, db: &'db dyn Db, tuple: TupleType<'db>) { + walk_tuple_type(db, tuple, self); + } + + fn visit_callable_type(&mut self, db: &'db dyn Db, callable: CallableType<'db>) { + walk_callable_type(db, callable, self); + } + + fn visit_property_instance_type( + &mut self, + db: &'db dyn Db, + property: PropertyInstanceType<'db>, + ) { + walk_property_instance_type(db, property, self); + } + + fn visit_typeis_type(&mut self, db: &'db dyn Db, type_is: TypeIsType<'db>) { + walk_typeis_type(db, type_is, self); + } + + fn visit_subclass_of_type(&mut self, db: &'db dyn Db, subclass_of: SubclassOfType<'db>) { + walk_subclass_of_type(db, subclass_of, self); + } + + fn visit_generic_alias_type(&mut self, db: &'db dyn Db, alias: GenericAlias<'db>) { + walk_generic_alias(db, alias, self); + } + + fn visit_function_type(&mut self, db: &'db dyn Db, function: FunctionType<'db>) { + walk_function_type(db, function, self); + } + + fn visit_bound_method_type(&mut self, db: &'db dyn Db, method: BoundMethodType<'db>) { + walk_bound_method_type(db, method, self); + } + + fn visit_bound_super_type(&mut self, db: &'db dyn Db, bound_super: BoundSuperType<'db>) { + walk_bound_super_type(db, bound_super, self); + } + + fn visit_nominal_instance_type(&mut self, db: &'db dyn Db, nominal: NominalInstanceType<'db>) { + walk_nominal_instance_type(db, nominal, self); + } + + fn visit_type_var_type(&mut self, db: &'db dyn Db, type_var: TypeVarInstance<'db>) { + walk_type_var_type(db, type_var, self); + } + + fn visit_protocol_instance_type( + &mut self, + db: &'db dyn Db, + protocol: ProtocolInstanceType<'db>, + ) { + walk_protocol_instance_type(db, protocol, self); + } + + fn visit_method_wrapper_type( + &mut self, + db: &'db dyn Db, + method_wrapper: MethodWrapperKind<'db>, + ) { + walk_method_wrapper_type(db, method_wrapper, self); + } + + fn visit_known_instance_type( + &mut self, + db: &'db dyn Db, + known_instance: KnownInstanceType<'db>, + ) { + walk_known_instance_type(db, known_instance, self); + } + + fn visit_type_alias_type(&mut self, db: &'db dyn Db, type_alias: TypeAliasType<'db>) { + walk_type_alias_type(db, type_alias, self); + } +} + +/// Enumeration of types that may contain other types, such as unions, intersections, and generics. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +enum NonAtomicType<'db> { + Union(UnionType<'db>), + Intersection(IntersectionType<'db>), + Tuple(TupleType<'db>), + FunctionLiteral(FunctionType<'db>), + BoundMethod(BoundMethodType<'db>), + BoundSuper(BoundSuperType<'db>), + MethodWrapper(MethodWrapperKind<'db>), + Callable(CallableType<'db>), + GenericAlias(GenericAlias<'db>), + KnownInstance(KnownInstanceType<'db>), + SubclassOf(SubclassOfType<'db>), + NominalInstance(NominalInstanceType<'db>), + PropertyInstance(PropertyInstanceType<'db>), + TypeIs(TypeIsType<'db>), + TypeVar(TypeVarInstance<'db>), + ProtocolInstance(ProtocolInstanceType<'db>), +} + +enum TypeKind<'db> { + Atomic, + NonAtomic(NonAtomicType<'db>), +} + +impl<'db> From> for TypeKind<'db> { + fn from(ty: Type<'db>) -> Self { + match ty { + Type::AlwaysFalsy + | Type::AlwaysTruthy + | Type::Never + | Type::LiteralString + | Type::IntLiteral(_) + | Type::BooleanLiteral(_) + | Type::StringLiteral(_) + | Type::BytesLiteral(_) + | Type::DataclassDecorator(_) + | Type::DataclassTransformer(_) + | Type::WrapperDescriptor(_) + | Type::ModuleLiteral(_) + | Type::ClassLiteral(_) + | Type::SpecialForm(_) + | Type::Dynamic(_) => TypeKind::Atomic, + + // Non-atomic types + Type::FunctionLiteral(function) => { + TypeKind::NonAtomic(NonAtomicType::FunctionLiteral(function)) + } + Type::Intersection(intersection) => { + TypeKind::NonAtomic(NonAtomicType::Intersection(intersection)) + } + Type::Union(union) => TypeKind::NonAtomic(NonAtomicType::Union(union)), + Type::Tuple(tuple) => TypeKind::NonAtomic(NonAtomicType::Tuple(tuple)), + Type::BoundMethod(method) => TypeKind::NonAtomic(NonAtomicType::BoundMethod(method)), + Type::BoundSuper(bound_super) => { + TypeKind::NonAtomic(NonAtomicType::BoundSuper(bound_super)) + } + Type::MethodWrapper(method_wrapper) => { + TypeKind::NonAtomic(NonAtomicType::MethodWrapper(method_wrapper)) + } + Type::Callable(callable) => TypeKind::NonAtomic(NonAtomicType::Callable(callable)), + Type::GenericAlias(alias) => TypeKind::NonAtomic(NonAtomicType::GenericAlias(alias)), + Type::KnownInstance(known_instance) => { + TypeKind::NonAtomic(NonAtomicType::KnownInstance(known_instance)) + } + Type::SubclassOf(subclass_of) => { + TypeKind::NonAtomic(NonAtomicType::SubclassOf(subclass_of)) + } + Type::NominalInstance(nominal) => { + TypeKind::NonAtomic(NonAtomicType::NominalInstance(nominal)) + } + Type::ProtocolInstance(protocol) => { + TypeKind::NonAtomic(NonAtomicType::ProtocolInstance(protocol)) + } + Type::PropertyInstance(property) => { + TypeKind::NonAtomic(NonAtomicType::PropertyInstance(property)) + } + Type::TypeVar(type_var) => TypeKind::NonAtomic(NonAtomicType::TypeVar(type_var)), + Type::TypeIs(type_is) => TypeKind::NonAtomic(NonAtomicType::TypeIs(type_is)), + } + } +} + +fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>( + db: &'db dyn Db, + non_atomic_type: NonAtomicType<'db>, + visitor: &mut V, +) { + match non_atomic_type { + NonAtomicType::FunctionLiteral(function) => visitor.visit_function_type(db, function), + NonAtomicType::Intersection(intersection) => { + visitor.visit_intersection_type(db, intersection); + } + NonAtomicType::Union(union) => visitor.visit_union_type(db, union), + NonAtomicType::Tuple(tuple) => visitor.visit_tuple_type(db, tuple), + NonAtomicType::BoundMethod(method) => visitor.visit_bound_method_type(db, method), + NonAtomicType::BoundSuper(bound_super) => visitor.visit_bound_super_type(db, bound_super), + NonAtomicType::MethodWrapper(method_wrapper) => { + visitor.visit_method_wrapper_type(db, method_wrapper); + } + NonAtomicType::Callable(callable) => visitor.visit_callable_type(db, callable), + NonAtomicType::GenericAlias(alias) => visitor.visit_generic_alias_type(db, alias), + NonAtomicType::KnownInstance(known_instance) => { + visitor.visit_known_instance_type(db, known_instance); + } + NonAtomicType::SubclassOf(subclass_of) => visitor.visit_subclass_of_type(db, subclass_of), + NonAtomicType::NominalInstance(nominal) => visitor.visit_nominal_instance_type(db, nominal), + NonAtomicType::PropertyInstance(property) => { + visitor.visit_property_instance_type(db, property); + } + NonAtomicType::TypeIs(type_is) => visitor.visit_typeis_type(db, type_is), + NonAtomicType::TypeVar(type_var) => visitor.visit_type_var_type(db, type_var), + NonAtomicType::ProtocolInstance(protocol) => { + visitor.visit_protocol_instance_type(db, protocol); + } + } +} + +/// Return `true` if `ty`, or any of the types contained in `ty`, match the closure passed in. +/// +/// The function guards against infinite recursion +/// by keeping track of the non-atomic types it has already seen. +pub(super) fn any_over_type<'db>( + db: &'db dyn Db, + ty: Type<'db>, + query: &dyn Fn(Type<'db>) -> bool, +) -> bool { + struct AnyOverTypeVisitor<'db, 'a> { + query: &'a dyn Fn(Type<'db>) -> bool, + seen_types: FxIndexSet>, + found_matching_type: bool, + } + + impl<'db> TypeVisitor<'db> for AnyOverTypeVisitor<'db, '_> { + fn visit_type(&mut self, db: &'db dyn Db, ty: Type<'db>) { + if self.found_matching_type { + return; + } + self.found_matching_type |= (self.query)(ty); + if self.found_matching_type { + return; + } + match TypeKind::from(ty) { + TypeKind::Atomic => {} + TypeKind::NonAtomic(non_atomic_type) => { + if !self.seen_types.insert(non_atomic_type) { + // If we have already seen this type, we can skip it. + return; + } + walk_non_atomic_type(db, non_atomic_type, self); + } + } + } + } + + let mut visitor = AnyOverTypeVisitor { + query, + seen_types: FxIndexSet::default(), + found_matching_type: false, + }; + visitor.visit_type(db, ty); + visitor.found_matching_type +}