[WIP] add recursive visitor to more type methods

This commit is contained in:
Carl Meyer 2025-07-27 22:38:11 -07:00
parent ef3a195f28
commit 89228c344c
No known key found for this signature in database
GPG Key ID: 2D1FB7916A52E121
3 changed files with 136 additions and 59 deletions

View File

@ -1258,6 +1258,17 @@ impl<'db> Type<'db> {
} }
fn has_relation_to(self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation) -> bool { fn has_relation_to(self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation) -> bool {
let mut visitor = PairVisitor::new(true);
self.has_relation_to_impl(db, target, relation, &mut visitor)
}
fn has_relation_to_impl(
self,
db: &'db dyn Db,
target: Type<'db>,
relation: TypeRelation,
visitor: &mut PairVisitor<'db>,
) -> bool {
// Subtyping implies assignability, so if subtyping is reflexive and the two types are // Subtyping implies assignability, so if subtyping is reflexive and the two types are
// equal, it is both a subtype and assignable. Assignability is always reflexive. // equal, it is both a subtype and assignable. Assignability is always reflexive.
// //
@ -1326,12 +1337,13 @@ impl<'db> Type<'db> {
match typevar.bound_or_constraints(db) { match typevar.bound_or_constraints(db) {
None => unreachable!(), None => unreachable!(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.has_relation_to(db, target, relation) bound.has_relation_to_impl(db, target, relation, visitor)
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
constraints.elements(db).iter().all(|constraint| {
constraint.has_relation_to_impl(db, target, relation, visitor)
})
} }
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.elements(db)
.iter()
.all(|constraint| constraint.has_relation_to(db, target, relation)),
} }
} }
@ -1340,9 +1352,9 @@ impl<'db> Type<'db> {
// disjoint, which means an lhs type might be a subtype of all of the constraints. // disjoint, which means an lhs type might be a subtype of all of the constraints.
(_, Type::TypeVar(typevar)) (_, Type::TypeVar(typevar))
if typevar.constraints(db).is_some_and(|constraints| { if typevar.constraints(db).is_some_and(|constraints| {
constraints constraints.iter().all(|constraint| {
.iter() self.has_relation_to_impl(db, *constraint, relation, visitor)
.all(|constraint| self.has_relation_to(db, *constraint, relation)) })
}) => }) =>
{ {
true true
@ -1356,12 +1368,12 @@ impl<'db> Type<'db> {
(Type::Union(union), _) => union (Type::Union(union), _) => union
.elements(db) .elements(db)
.iter() .iter()
.all(|&elem_ty| elem_ty.has_relation_to(db, target, relation)), .all(|&elem_ty| elem_ty.has_relation_to_impl(db, target, relation, visitor)),
(_, Type::Union(union)) => union (_, Type::Union(union)) => union
.elements(db) .elements(db)
.iter() .iter()
.any(|&elem_ty| self.has_relation_to(db, elem_ty, relation)), .any(|&elem_ty| self.has_relation_to_impl(db, elem_ty, relation, visitor)),
// If both sides are intersections we need to handle the right side first // If both sides are intersections we need to handle the right side first
// (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B, // (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B,
@ -1370,7 +1382,7 @@ impl<'db> Type<'db> {
intersection intersection
.positive(db) .positive(db)
.iter() .iter()
.all(|&pos_ty| self.has_relation_to(db, pos_ty, relation)) .all(|&pos_ty| self.has_relation_to_impl(db, pos_ty, relation, visitor))
&& intersection && intersection
.negative(db) .negative(db)
.iter() .iter()
@ -1380,7 +1392,7 @@ impl<'db> Type<'db> {
(Type::Intersection(intersection), _) => intersection (Type::Intersection(intersection), _) => intersection
.positive(db) .positive(db)
.iter() .iter()
.any(|&elem_ty| elem_ty.has_relation_to(db, target, relation)), .any(|&elem_ty| elem_ty.has_relation_to_impl(db, target, relation, visitor)),
// Other than the special cases checked above, no other types are a subtype of a // Other than the special cases checked above, no other types are a subtype of a
// typevar, since there's no guarantee what type the typevar will be specialized to. // typevar, since there's no guarantee what type the typevar will be specialized to.
@ -1438,9 +1450,9 @@ impl<'db> Type<'db> {
self_callable.has_relation_to(db, other_callable, relation) self_callable.has_relation_to(db, other_callable, relation)
} }
(_, Type::Callable(_)) => self (_, Type::Callable(_)) => self.into_callable(db).is_some_and(|callable| {
.into_callable(db) callable.has_relation_to_impl(db, target, relation, visitor)
.is_some_and(|callable| callable.has_relation_to(db, target, relation)), }),
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => { (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
left.has_relation_to(db, right, relation) left.has_relation_to(db, right, relation)
@ -1476,8 +1488,9 @@ impl<'db> Type<'db> {
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::EnumLiteral(_), | Type::EnumLiteral(_),
_, _,
) => (self.literal_fallback_instance(db)) ) => (self.literal_fallback_instance(db)).is_some_and(|instance| {
.is_some_and(|instance| instance.has_relation_to(db, target, relation)), instance.has_relation_to_impl(db, target, relation, visitor)
}),
// A `FunctionLiteral` type is a single-valued type like the other literals handled above, // A `FunctionLiteral` type is a single-valued type like the other literals handled above,
// so it also, for now, just delegates to its instance fallback. // so it also, for now, just delegates to its instance fallback.
@ -1488,13 +1501,13 @@ impl<'db> Type<'db> {
// The same reasoning applies for these special callable types: // The same reasoning applies for these special callable types:
(Type::BoundMethod(_), _) => KnownClass::MethodType (Type::BoundMethod(_), _) => KnownClass::MethodType
.to_instance(db) .to_instance(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
(Type::MethodWrapper(_), _) => KnownClass::WrapperDescriptorType (Type::MethodWrapper(_), _) => KnownClass::WrapperDescriptorType
.to_instance(db) .to_instance(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
(Type::WrapperDescriptor(_), _) => KnownClass::WrapperDescriptorType (Type::WrapperDescriptor(_), _) => KnownClass::WrapperDescriptorType
.to_instance(db) .to_instance(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
// TODO: Implement subtyping using an equivalent `Callable` type. // TODO: Implement subtyping using an equivalent `Callable` type.
@ -1503,24 +1516,30 @@ impl<'db> Type<'db> {
// `TypeIs` is invariant. // `TypeIs` is invariant.
(Type::TypeIs(left), Type::TypeIs(right)) => { (Type::TypeIs(left), Type::TypeIs(right)) => {
left.return_type(db) left.return_type(db).has_relation_to_impl(
.has_relation_to(db, right.return_type(db), relation) db,
&& right right.return_type(db),
.return_type(db) relation,
.has_relation_to(db, left.return_type(db), relation) visitor,
) && right.return_type(db).has_relation_to_impl(
db,
left.return_type(db),
relation,
visitor,
)
} }
// `TypeIs[T]` is a subtype of `bool`. // `TypeIs[T]` is a subtype of `bool`.
(Type::TypeIs(_), _) => KnownClass::Bool (Type::TypeIs(_), _) => KnownClass::Bool
.to_instance(db) .to_instance(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
// Function-like callables are subtypes of `FunctionType` // Function-like callables are subtypes of `FunctionType`
(Type::Callable(callable), _) (Type::Callable(callable), _)
if callable.is_function_like(db) if callable.is_function_like(db)
&& KnownClass::FunctionType && KnownClass::FunctionType
.to_instance(db) .to_instance(db)
.has_relation_to(db, target, relation) => .has_relation_to_impl(db, target, relation, visitor) =>
{ {
true true
} }
@ -1548,7 +1567,7 @@ impl<'db> Type<'db> {
(Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target), (Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target),
(Type::BoundSuper(_), _) => KnownClass::Super (Type::BoundSuper(_), _) => KnownClass::Super
.to_instance(db) .to_instance(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
// `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`, // `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`,
// since `type[B]` describes all possible runtime subclasses of the class object `B`. // since `type[B]` describes all possible runtime subclasses of the class object `B`.
@ -1577,25 +1596,30 @@ impl<'db> Type<'db> {
// is an instance of its metaclass `abc.ABCMeta`. // is an instance of its metaclass `abc.ABCMeta`.
(Type::ClassLiteral(class), _) => class (Type::ClassLiteral(class), _) => class
.metaclass_instance_type(db) .metaclass_instance_type(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
(Type::GenericAlias(alias), _) => ClassType::from(alias) (Type::GenericAlias(alias), _) => ClassType::from(alias)
.metaclass_instance_type(db) .metaclass_instance_type(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
// `type[Any]` is a subtype of `type[object]`, and is assignable to any `type[...]` // `type[Any]` is a subtype of `type[object]`, and is assignable to any `type[...]`
(Type::SubclassOf(subclass_of_ty), other) if subclass_of_ty.is_dynamic() => { (Type::SubclassOf(subclass_of_ty), other) if subclass_of_ty.is_dynamic() => {
KnownClass::Type KnownClass::Type
.to_instance(db) .to_instance(db)
.has_relation_to(db, other, relation) .has_relation_to_impl(db, other, relation, visitor)
|| (relation.is_assignability() || (relation.is_assignability()
&& other.has_relation_to(db, KnownClass::Type.to_instance(db), relation)) && other.has_relation_to_impl(
db,
KnownClass::Type.to_instance(db),
relation,
visitor,
))
} }
// Any `type[...]` type is assignable to `type[Any]` // Any `type[...]` type is assignable to `type[Any]`
(other, Type::SubclassOf(subclass_of_ty)) (other, Type::SubclassOf(subclass_of_ty))
if subclass_of_ty.is_dynamic() && relation.is_assignability() => if subclass_of_ty.is_dynamic() && relation.is_assignability() =>
{ {
other.has_relation_to(db, KnownClass::Type.to_instance(db), relation) other.has_relation_to_impl(db, KnownClass::Type.to_instance(db), relation, visitor)
} }
// `type[str]` (== `SubclassOf("str")` in ty) describes all possible runtime subclasses // `type[str]` (== `SubclassOf("str")` in ty) describes all possible runtime subclasses
@ -1610,18 +1634,18 @@ impl<'db> Type<'db> {
.into_class() .into_class()
.map(|class| class.metaclass_instance_type(db)) .map(|class| class.metaclass_instance_type(db))
.unwrap_or_else(|| KnownClass::Type.to_instance(db)) .unwrap_or_else(|| KnownClass::Type.to_instance(db))
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
// For example: `Type::SpecialForm(SpecialFormType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, // For example: `Type::SpecialForm(SpecialFormType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`,
// because `Type::SpecialForm(SpecialFormType::Type)` is a set with exactly one runtime value in it // because `Type::SpecialForm(SpecialFormType::Type)` is a set with exactly one runtime value in it
// (the symbol `typing.Type`), and that symbol is known to be an instance of `typing._SpecialForm` at runtime. // (the symbol `typing.Type`), and that symbol is known to be an instance of `typing._SpecialForm` at runtime.
(Type::SpecialForm(left), right) => left (Type::SpecialForm(left), right) => left
.instance_fallback(db) .instance_fallback(db)
.has_relation_to(db, right, relation), .has_relation_to_impl(db, right, relation, visitor),
(Type::KnownInstance(left), right) => left (Type::KnownInstance(left), right) => left
.instance_fallback(db) .instance_fallback(db)
.has_relation_to(db, right, relation), .has_relation_to_impl(db, right, relation, visitor),
// `bool` is a subtype of `int`, because `bool` subclasses `int`, // `bool` is a subtype of `int`, because `bool` subclasses `int`,
// which means that all instances of `bool` are also instances of `int` // which means that all instances of `bool` are also instances of `int`
@ -1631,10 +1655,13 @@ impl<'db> Type<'db> {
(Type::PropertyInstance(_), _) => KnownClass::Property (Type::PropertyInstance(_), _) => KnownClass::Property
.to_instance(db) .to_instance(db)
.has_relation_to(db, target, relation), .has_relation_to_impl(db, target, relation, visitor),
(_, Type::PropertyInstance(_)) => { (_, Type::PropertyInstance(_)) => self.has_relation_to_impl(
self.has_relation_to(db, KnownClass::Property.to_instance(db), relation) db,
} KnownClass::Property.to_instance(db),
relation,
visitor,
),
// Other than the special cases enumerated above, `Instance` types and typevars are // Other than the special cases enumerated above, `Instance` types and typevars are
// never subtypes of any other variants // never subtypes of any other variants
@ -1655,6 +1682,16 @@ impl<'db> Type<'db> {
/// ///
/// [equivalent to]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent /// [equivalent to]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool { pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
let mut visitor = PairVisitor::new(true);
self.is_equivalent_to_impl(db, other, &mut visitor)
}
pub(crate) fn is_equivalent_to_impl(
self,
db: &'db dyn Db,
other: Type<'db>,
visitor: &mut PairVisitor<'db>,
) -> bool {
if self == other { if self == other {
return true; return true;
} }
@ -5441,6 +5478,16 @@ impl<'db> Type<'db> {
self, self,
db: &'db dyn Db, db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>, type_mapping: &TypeMapping<'a, 'db>,
) -> Type<'db> {
let mut visitor = TypeTransformer::default();
self.apply_type_mapping_impl(db, type_mapping, &mut visitor)
}
fn apply_type_mapping_impl<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &mut TypeTransformer<'db>,
) -> Type<'db> { ) -> Type<'db> {
match self { match self {
Type::TypeVar(typevar) => match type_mapping { Type::TypeVar(typevar) => match type_mapping {
@ -5512,21 +5559,21 @@ impl<'db> Type<'db> {
} }
Type::Union(union) => union.map(db, |element| { Type::Union(union) => union.map(db, |element| {
element.apply_type_mapping(db, type_mapping) element.apply_type_mapping_impl(db, type_mapping, visitor)
}), }),
Type::Intersection(intersection) => { Type::Intersection(intersection) => {
let mut builder = IntersectionBuilder::new(db); let mut builder = IntersectionBuilder::new(db);
for positive in intersection.positive(db) { for positive in intersection.positive(db) {
builder = builder =
builder.add_positive(positive.apply_type_mapping(db, type_mapping)); builder.add_positive(positive.apply_type_mapping_impl(db, type_mapping, visitor));
} }
for negative in intersection.negative(db) { for negative in intersection.negative(db) {
builder = builder =
builder.add_negative(negative.apply_type_mapping(db, type_mapping)); builder.add_negative(negative.apply_type_mapping_impl(db, type_mapping, visitor));
} }
builder.build() builder.build()
} }
Type::Tuple(tuple) => Type::tuple(tuple.apply_type_mapping(db, type_mapping)), Type::Tuple(tuple) => Type::tuple(tuple.apply_type_mapping_impl(db, type_mapping, visitor)),
Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)), Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)),

View File

@ -420,15 +420,25 @@ impl<'db> Specialization<'db> {
self, self,
db: &'db dyn Db, db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>, type_mapping: &TypeMapping<'a, 'db>,
) -> Self {
let mut visitor = TypeTransformer::default();
self.apply_type_mapping_impl(db, type_mapping, &mut visitor)
}
pub(crate) fn apply_type_mapping_impl<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &mut TypeTransformer<'db>,
) -> Self { ) -> Self {
let types: Box<[_]> = self let types: Box<[_]> = self
.types(db) .types(db)
.iter() .iter()
.map(|ty| ty.apply_type_mapping(db, type_mapping)) .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor))
.collect(); .collect();
let tuple_inner = self let tuple_inner = self
.tuple_inner(db) .tuple_inner(db)
.and_then(|tuple| tuple.apply_type_mapping(db, type_mapping)); .and_then(|tuple| tuple.apply_type_mapping_impl(db, type_mapping, visitor));
Specialization::new(db, self.generic_context(db), types, tuple_inner) Specialization::new(db, self.generic_context(db), types, tuple_inner)
} }

View File

@ -213,12 +213,17 @@ impl<'db> TupleType<'db> {
TupleType::new(db, self.tuple(db).materialize(db, variance)) TupleType::new(db, self.tuple(db).materialize(db, variance))
} }
pub(crate) fn apply_type_mapping<'a>( pub(crate) fn apply_type_mapping_impl<'a>(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>, type_mapping: &TypeMapping<'a, 'db>,
visitor: &mut TypeTransformer<'db>,
) -> Option<Self> { ) -> Option<Self> {
TupleType::new(db, self.tuple(db).apply_type_mapping(db, type_mapping)) TupleType::new(
db,
self.tuple(db)
.apply_type_mapping_impl(db, type_mapping, visitor),
)
} }
pub(crate) fn find_legacy_typevars( pub(crate) fn find_legacy_typevars(
@ -376,11 +381,16 @@ impl<'db> FixedLengthTuple<Type<'db>> {
Self::from_elements(self.0.iter().map(|ty| ty.materialize(db, variance))) Self::from_elements(self.0.iter().map(|ty| ty.materialize(db, variance)))
} }
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &mut TypeTransformer<'db>,
) -> Self {
Self::from_elements( Self::from_elements(
self.0 self.0
.iter() .iter()
.map(|ty| ty.apply_type_mapping(db, type_mapping)), .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
) )
} }
@ -706,19 +716,26 @@ impl<'db> VariableLengthTuple<Type<'db>> {
) )
} }
fn apply_type_mapping<'a>( fn apply_type_mapping_impl<'a>(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>, type_mapping: &TypeMapping<'a, 'db>,
visitor: &mut TypeTransformer<'db>,
) -> TupleSpec<'db> { ) -> TupleSpec<'db> {
Self::mixed( // TODO maybe use interior mutability in `TypeTransformer` instead of passing around
self.prefix // mutable references to it; then we wouldn't need to collect here.
let prefix: Vec<_> = self
.prefix
.iter() .iter()
.map(|ty| ty.apply_type_mapping(db, type_mapping)), .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor))
self.variable.apply_type_mapping(db, type_mapping), .collect();
Self::mixed(
prefix,
self.variable
.apply_type_mapping_impl(db, type_mapping, visitor),
self.suffix self.suffix
.iter() .iter()
.map(|ty| ty.apply_type_mapping(db, type_mapping)), .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
) )
} }
@ -1058,14 +1075,17 @@ impl<'db> Tuple<Type<'db>> {
} }
} }
pub(crate) fn apply_type_mapping<'a>( pub(crate) fn apply_type_mapping_impl<'a>(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>, type_mapping: &TypeMapping<'a, 'db>,
visitor: &mut TypeTransformer<'db>,
) -> Self { ) -> Self {
match self { match self {
Tuple::Fixed(tuple) => Tuple::Fixed(tuple.apply_type_mapping(db, type_mapping)), Tuple::Fixed(tuple) => {
Tuple::Variable(tuple) => tuple.apply_type_mapping(db, type_mapping), Tuple::Fixed(tuple.apply_type_mapping_impl(db, type_mapping, visitor))
}
Tuple::Variable(tuple) => tuple.apply_type_mapping_impl(db, type_mapping, visitor),
} }
} }