[ty] Normalize recursive types using Any (#19003)

## Summary

This just replaces one temporary solution to recursive protocols (the
`SelfReference` mechanism) with another one (track seen types when
recursively descending in `normalize` and replace recursive references
with `Any`). But this temporary solution can handle mutually-recursive
types, not just self-referential ones, and it's sufficient for the
primer ecosystem and some other projects we are testing on to no longer
stack overflow.

The follow-up here will be to properly handle these self-references
instead of replacing them with `Any`.

We will also eventually need cycle detection on more recursive-descent
type transformations and tests.

## Test Plan

Existing tests (including recursive-protocol tests) and primer.

Added mdtest for mutually-recursive protocols that stack-overflowed
before this PR.
This commit is contained in:
Carl Meyer 2025-06-30 12:07:57 -07:00 committed by GitHub
parent 34052a1185
commit 2ae0bd9464
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 356 additions and 368 deletions

View file

@ -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.

View file

@ -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

View file

@ -110,6 +110,7 @@ strawberry
streamlit
svcs
sympy
tornado
trio
twine
typeshed-stats

View file

@ -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<Type<'db>> = 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<Type<'db>>,
visitor: &mut TypeVisitor<'db>,
) -> FxOrderSet<Type<'db>> {
let mut elements: FxOrderSet<Type<'db>> =
elements.iter().map(|ty| ty.normalized(db)).collect();
let mut elements: FxOrderSet<Type<'db>> = 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),
)
}
}

View file

@ -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)),
}
}

View file

@ -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,
}
}

View file

@ -0,0 +1,26 @@
use crate::FxOrderSet;
use crate::types::Type;
#[derive(Debug, Default)]
pub(crate) struct TypeVisitor<'db> {
seen: FxOrderSet<Type<'db>>,
}
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
}
}

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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<Item = (&'a str, Type<'db>)>,
{
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 {

View file

@ -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<Name, ProtocolMemberData<'db>>,
}
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<ProtocolMember<'a, 'db>> {
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::<BTreeMap<_, _>>(),
)),
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::<BTreeMap<_, _>>(),
)
}
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::<BTreeMap<_, _>>(),
)),
Self::SelfReference => Self::SelfReference,
}
Self::new(
db,
self.inner(db)
.iter()
.map(|(name, data)| (name.clone(), data.materialize(db, variance)))
.collect::<BTreeMap<_, _>>(),
)
}
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::<BTreeMap<_, _>>(),
)),
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::<BTreeMap<_, _>>(),
)
}
pub(super) fn find_legacy_typevars(
@ -234,13 +201,8 @@ impl<'db> ProtocolInterface<'db> {
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
) {
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)]

View file

@ -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)]

View file

@ -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()),
}
}

View file

@ -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<Self> {
TupleType::new(db, self.tuple(db).normalized(db))
pub(crate) fn normalized_impl(
self,
db: &'db dyn Db,
visitor: &mut TypeVisitor<'db>,
) -> Option<Self> {
TupleType::new(db, self.tuple(db).normalized_impl(db, visitor))
}
pub(crate) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Option<Self> {
@ -327,8 +332,8 @@ impl<'db> FixedLengthTuple<Type<'db>> {
}
#[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<Type<'db>> {
}
#[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::<Vec<_>>();
let suffix = self
.prenormalized_suffix_elements(db, None)
.map(|ty| ty.normalized_impl(db, visitor))
.collect::<Vec<_>>();
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<Type<'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 {
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),
}
}