[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 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 ### Regression test: narrowing with self-referential protocols
This snippet caused us to panic on an early version of the implementation for 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) mypy # too many iterations (self-recursive type alias)
packaging # too many iterations packaging # too many iterations
pandas # slow (9s) pandas # slow (9s)
pandera # stack overflow pandera # too many iterations
pip # vendors packaging, see above pip # vendors packaging, see above
pylint # cycle panics (self-recursive type alias) pylint # cycle panics (self-recursive type alias)
pyodide # too many cycle iterations pyodide # too many cycle iterations
@ -19,5 +19,4 @@ setuptools # vendors packaging, see above
spack # slow, success, but mypy-primer hangs processing the output spack # slow, success, but mypy-primer hangs processing the output
spark # too many iterations spark # too many iterations
steam.py # hangs (single threaded) steam.py # hangs (single threaded)
tornado # bad use-def map (https://github.com/astral-sh/ty/issues/365)
xarray # too many iterations xarray # too many iterations

View file

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

View file

@ -21,6 +21,7 @@ use ruff_text_size::{Ranged, TextRange};
use type_ordering::union_or_intersection_elements_ordering; use type_ordering::union_or_intersection_elements_ordering;
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder}; pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
pub(crate) use self::cyclic::TypeVisitor;
pub use self::diagnostic::TypeCheckDiagnostics; pub use self::diagnostic::TypeCheckDiagnostics;
pub(crate) use self::diagnostic::register_lints; pub(crate) use self::diagnostic::register_lints;
pub(crate) use self::infer::{ pub(crate) use self::infer::{
@ -63,6 +64,7 @@ mod call;
mod class; mod class;
mod class_base; mod class_base;
mod context; mod context;
mod cyclic;
mod diagnostic; mod diagnostic;
mod display; mod display;
mod function; mod function;
@ -384,11 +386,11 @@ impl<'db> PropertyInstanceType<'db> {
Self::new(db, getter, setter) 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( Self::new(
db, db,
self.getter(db).map(|ty| ty.normalized(db)), self.getter(db).map(|ty| ty.normalized_impl(db, visitor)),
self.setter(db).map(|ty| ty.normalized(db)), 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 /// 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. /// is specialized, we will replace this typevar with its specialization.
TypeVar(TypeVarInstance<'db>), TypeVar(TypeVarInstance<'db>),
// A bound super object like `super()` or `super(A, A())` /// 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 /// This type doesn't handle an unbound super object like `super(A)`; for that we just use
// a `Type::NominalInstance` of `builtins.super`. /// a `Type::NominalInstance` of `builtins.super`.
BoundSuper(BoundSuperType<'db>), BoundSuper(BoundSuperType<'db>),
/// A subtype of `bool` that allows narrowing in both positive and negative cases. /// A subtype of `bool` that allows narrowing in both positive and negative cases.
TypeIs(TypeIsType<'db>), 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. /// 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 { pub fn any_over_type(self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool {
if type_fn(self) { if type_fn(self) {
@ -1149,26 +1072,62 @@ impl<'db> Type<'db> {
/// - Converts class-based protocols into synthesized protocols /// - Converts class-based protocols into synthesized protocols
#[must_use] #[must_use]
pub fn normalized(self, db: &'db dyn Db) -> Self { 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 { match self {
Type::Union(union) => Type::Union(union.normalized(db)), Type::Union(union) => {
Type::Intersection(intersection) => Type::Intersection(intersection.normalized(db)), visitor.visit(self, |v| Type::Union(union.normalized_impl(db, v)))
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::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::LiteralString
| Type::AlwaysFalsy | Type::AlwaysFalsy
| Type::AlwaysTruthy | 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 { match self {
TypeMapping::Specialization(specialization) => { TypeMapping::Specialization(specialization) => {
TypeMapping::Specialization(specialization.normalized(db)) TypeMapping::Specialization(specialization.normalized_impl(db, visitor))
} }
TypeMapping::PartialSpecialization(partial) => { TypeMapping::PartialSpecialization(partial) => {
TypeMapping::PartialSpecialization(partial.normalized(db)) TypeMapping::PartialSpecialization(partial.normalized_impl(db, visitor))
} }
TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals, TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals,
} }
@ -5795,12 +5754,18 @@ pub enum KnownInstanceType<'db> {
} }
impl<'db> 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 { match self {
Self::SubscriptedProtocol(context) => Self::SubscriptedProtocol(context.normalized(db)), Self::SubscriptedProtocol(context) => {
Self::SubscriptedGeneric(context) => Self::SubscriptedGeneric(context.normalized(db)), Self::SubscriptedProtocol(context.normalized_impl(db, visitor))
Self::TypeVar(typevar) => Self::TypeVar(typevar.normalized(db)), }
Self::TypeAliasType(type_alias) => Self::TypeAliasType(type_alias.normalized(db)), 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( Self::new(
db, db,
self.name(db), self.name(db),
self.definition(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.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), self.kind(db),
) )
} }
@ -6236,13 +6202,13 @@ pub enum TypeVarBoundOrConstraints<'db> {
} }
impl<'db> 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 { match self {
TypeVarBoundOrConstraints::UpperBound(bound) => { TypeVarBoundOrConstraints::UpperBound(bound) => {
TypeVarBoundOrConstraints::UpperBound(bound.normalized(db)) TypeVarBoundOrConstraints::UpperBound(bound.normalized_impl(db, visitor))
} }
TypeVarBoundOrConstraints::Constraints(constraints) => { 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( Self::new(
db, db,
self.function(db).normalized(db), self.function(db).normalized_impl(db, visitor),
self.self_instance(db).normalized(db), self.self_instance(db).normalized_impl(db, visitor),
) )
} }
@ -7261,10 +7227,10 @@ impl<'db> CallableType<'db> {
/// Return a "normalized" version of this `Callable` type. /// Return a "normalized" version of this `Callable` type.
/// ///
/// See [`Type::normalized`] for more details. /// 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( CallableType::new(
db, db,
self.signatures(db).normalized(db), self.signatures(db).normalized_impl(db, visitor),
self.is_function_like(db), self.is_function_like(db),
) )
} }
@ -7305,15 +7271,6 @@ impl<'db> CallableType<'db> {
.signatures(db) .signatures(db)
.is_equivalent_to(db, other.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` /// 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 { match self {
MethodWrapperKind::FunctionTypeDunderGet(function) => { MethodWrapperKind::FunctionTypeDunderGet(function) => {
MethodWrapperKind::FunctionTypeDunderGet(function.normalized(db)) MethodWrapperKind::FunctionTypeDunderGet(function.normalized_impl(db, visitor))
} }
MethodWrapperKind::FunctionTypeDunderCall(function) => { MethodWrapperKind::FunctionTypeDunderCall(function) => {
MethodWrapperKind::FunctionTypeDunderCall(function.normalized(db)) MethodWrapperKind::FunctionTypeDunderCall(function.normalized_impl(db, visitor))
} }
MethodWrapperKind::PropertyDunderGet(property) => { MethodWrapperKind::PropertyDunderGet(property) => {
MethodWrapperKind::PropertyDunderGet(property.normalized(db)) MethodWrapperKind::PropertyDunderGet(property.normalized_impl(db, visitor))
} }
MethodWrapperKind::PropertyDunderSet(property) => { MethodWrapperKind::PropertyDunderSet(property) => {
MethodWrapperKind::PropertyDunderSet(property.normalized(db)) MethodWrapperKind::PropertyDunderSet(property.normalized_impl(db, visitor))
} }
MethodWrapperKind::StrStartswith(_) => self, MethodWrapperKind::StrStartswith(_) => self,
} }
@ -7530,7 +7487,7 @@ impl<'db> PEP695TypeAliasType<'db> {
definition_expression_type(db, definition, &type_alias_stmt_node.value) 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 self
} }
} }
@ -7551,12 +7508,12 @@ pub struct BareTypeAliasType<'db> {
impl get_size2::GetSize for BareTypeAliasType<'_> {} impl get_size2::GetSize for BareTypeAliasType<'_> {}
impl<'db> BareTypeAliasType<'db> { 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( Self::new(
db, db,
self.name(db), self.name(db),
self.definition(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> { 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 { match self {
TypeAliasType::PEP695(type_alias) => TypeAliasType::PEP695(type_alias.normalized(db)), TypeAliasType::PEP695(type_alias) => {
TypeAliasType::Bare(type_alias) => TypeAliasType::Bare(type_alias.normalized(db)), 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. /// See [`Type::normalized`] for more details.
#[must_use] #[must_use]
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { 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 let mut new_elements: Vec<Type<'db>> = self
.elements(db) .elements(db)
.iter() .iter()
.map(|element| element.normalized(db)) .map(|element| element.normalized_impl(db, visitor))
.collect(); .collect();
new_elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); new_elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
UnionType::new(db, new_elements.into_boxed_slice()) UnionType::new(db, new_elements.into_boxed_slice())
@ -7839,12 +7804,20 @@ impl<'db> IntersectionType<'db> {
/// See [`Type::normalized`] for more details. /// See [`Type::normalized`] for more details.
#[must_use] #[must_use]
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { 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>( fn normalized_set<'db>(
db: &'db dyn Db, db: &'db dyn Db,
elements: &FxOrderSet<Type<'db>>, elements: &FxOrderSet<Type<'db>>,
visitor: &mut TypeVisitor<'db>,
) -> FxOrderSet<Type<'db>> { ) -> FxOrderSet<Type<'db>> {
let mut elements: FxOrderSet<Type<'db>> = let mut elements: FxOrderSet<Type<'db>> = elements
elements.iter().map(|ty| ty.normalized(db)).collect(); .iter()
.map(|ty| ty.normalized_impl(db, visitor))
.collect();
elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
elements elements
@ -7852,8 +7825,8 @@ impl<'db> IntersectionType<'db> {
IntersectionType::new( IntersectionType::new(
db, db,
normalized_set(db, self.positive(db)), normalized_set(db, self.positive(db), visitor),
normalized_set(db, self.negative(db)), normalized_set(db, self.negative(db), visitor),
) )
} }
@ -8092,11 +8065,15 @@ pub enum SuperOwnerKind<'db> {
} }
impl<'db> 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 { match self {
SuperOwnerKind::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic.normalized()), SuperOwnerKind::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic.normalized()),
SuperOwnerKind::Class(class) => SuperOwnerKind::Class(class.normalized(db)), SuperOwnerKind::Class(class) => {
SuperOwnerKind::Instance(instance) => SuperOwnerKind::Instance(instance.normalized(db)), 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( Self::new(
db, db,
self.pivot_class(db).normalized(db), self.pivot_class(db).normalized_impl(db, visitor),
self.owner(db).normalized(db), self.owner(db).normalized_impl(db, visitor),
) )
} }
} }

View file

@ -22,7 +22,7 @@ use crate::types::tuple::TupleType;
use crate::types::{ use crate::types::{
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams, BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
TypeVarInstance, TypeVarKind, infer_definition_types, TypeVarInstance, TypeVarKind, TypeVisitor, infer_definition_types,
}; };
use crate::{ use crate::{
Db, FxOrderSet, KnownModule, Program, Db, FxOrderSet, KnownModule, Program,
@ -182,8 +182,12 @@ pub struct GenericAlias<'db> {
impl get_size2::GetSize for GenericAlias<'_> {} impl get_size2::GetSize for GenericAlias<'_> {}
impl<'db> GenericAlias<'db> { impl<'db> GenericAlias<'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 {
Self::new(db, self.origin(db), self.specialization(db).normalized(db)) 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 { pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
@ -249,10 +253,10 @@ pub enum ClassType<'db> {
#[salsa::tracked] #[salsa::tracked]
impl<'db> ClassType<'db> { 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 { match self {
Self::NonGeneric(_) => 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::generics::Specialization;
use crate::types::{ use crate::types::{
ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, SpecialFormType, 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. /// Enumeration of the possible kinds of types we allow in class bases.
@ -31,10 +31,10 @@ impl<'db> ClassBase<'db> {
Self::Dynamic(DynamicType::Unknown) 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 { match self {
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()), 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, 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::signatures::{CallableSignature, Signature};
use crate::types::{ use crate::types::{
BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation, BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation,
TypeVarInstance, TypeVarInstance, TypeVisitor,
}; };
use crate::{Db, FxOrderSet, ModuleName, resolve_module}; 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 let context = self
.inherited_generic_context(db) .inherited_generic_context(db)
.map(|ctx| ctx.normalized(db)); .map(|ctx| ctx.normalized_impl(db, visitor));
Self::new(db, self.last_definition(db), context) 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 { 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 let mappings: Box<_> = self
.type_mappings(db) .type_mappings(db)
.iter() .iter()
.map(|mapping| mapping.normalized(db)) .map(|mapping| mapping.normalized_impl(db, visitor))
.collect(); .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::tuple::{TupleSpec, TupleType};
use crate::types::{ use crate::types::{
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
TypeVarVariance, UnionType, declaration_type, TypeVarVariance, TypeVisitor, UnionType, declaration_type,
}; };
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
@ -233,11 +233,11 @@ impl<'db> GenericContext<'db> {
Specialization::new(db, self, expanded.into_boxed_slice(), None) 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 let variables: FxOrderSet<_> = self
.variables(db) .variables(db)
.iter() .iter()
.map(|ty| ty.normalized(db)) .map(|ty| ty.normalized_impl(db, visitor))
.collect(); .collect();
Self::new(db, variables) Self::new(db, variables)
} }
@ -376,9 +376,15 @@ impl<'db> Specialization<'db> {
Specialization::new(db, self.generic_context(db), types, None) Specialization::new(db, self.generic_context(db), types, 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 types: Box<[_]> = self.types(db).iter().map(|ty| ty.normalized(db)).collect(); let types: Box<[_]> = self
let tuple_inner = self.tuple_inner(db).and_then(|tuple| tuple.normalized(db)); .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) 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> { pub(crate) fn normalized_impl(
let generic_context = self.generic_context.normalized(db); &self,
let types: Cow<_> = self.types.iter().map(|ty| ty.normalized(db)).collect(); 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 { PartialSpecialization {
generic_context, generic_context,

View file

@ -6,7 +6,7 @@ use super::protocol_class::ProtocolInterface;
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance}; use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
use crate::place::{Place, PlaceAndQualifiers}; use crate::place::{Place, PlaceAndQualifiers};
use crate::types::tuple::TupleType; 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}; use crate::{Db, FxOrderSet};
pub(super) use synthesized_protocol::SynthesizedProtocolType; pub(super) use synthesized_protocol::SynthesizedProtocolType;
@ -41,7 +41,11 @@ impl<'db> Type<'db> {
M: IntoIterator<Item = (&'a str, Type<'db>)>, M: IntoIterator<Item = (&'a str, Type<'db>)>,
{ {
Self::ProtocolInstance(ProtocolInstanceType::synthesized( 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 { pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
Self::from_class(self.class.normalized(db)) Self::from_class(self.class.normalized_impl(db, visitor))
} }
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { 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. /// See [`Type::normalized`] for more details.
pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> { 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); let object = KnownClass::Object.to_instance(db);
if object.satisfies_protocol(db, self, TypeRelation::Subtyping) { if object.satisfies_protocol(db, self, TypeRelation::Subtyping) {
return object; return object;
} }
match self.inner { match self.inner {
Protocol::FromClass(_) => Type::ProtocolInstance(Self::synthesized( 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), 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. /// Return `true` if the types of any of the members match the closure passed in.
pub(super) fn any_over_type( pub(super) fn any_over_type(
self, self,
@ -352,7 +355,7 @@ impl<'db> Protocol<'db> {
mod synthesized_protocol { mod synthesized_protocol {
use crate::types::protocol_class::ProtocolInterface; use crate::types::protocol_class::ProtocolInterface;
use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance}; use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance, TypeVisitor};
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
/// A "synthesized" protocol type that is dissociated from a class definition in source code. /// 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>); pub(in crate::types) struct SynthesizedProtocolType<'db>(ProtocolInterface<'db>);
impl<'db> SynthesizedProtocolType<'db> { impl<'db> SynthesizedProtocolType<'db> {
pub(super) fn new(db: &'db dyn Db, interface: ProtocolInterface<'db>) -> Self { pub(super) fn new(
Self(interface.normalized(db)) 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 { 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 std::{collections::BTreeMap, ops::Deref};
use itertools::{Either, Itertools}; use itertools::Itertools;
use ruff_python_ast::name::Name; use ruff_python_ast::name::Name;
@ -10,7 +10,7 @@ use crate::{
semantic_index::{place_table, use_def_map}, semantic_index::{place_table, use_def_map},
types::{ types::{
CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature, CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature,
Type, TypeMapping, TypeQualifiers, TypeRelation, TypeVarInstance, Type, TypeMapping, TypeQualifiers, TypeRelation, TypeVarInstance, TypeVisitor,
signatures::{Parameter, Parameters}, 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
/// Ordering is based on the protocol interface member's salsa-assigned id and not on its members. /// 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. /// The id may change between runs, or when the protocol instance members was garbage collected and recreated.
#[salsa::interned(debug)] #[salsa::interned(debug)]
#[derive(PartialOrd, Ord)] #[derive(PartialOrd, Ord)]
pub(super) struct ProtocolInterfaceMembers<'db> { pub(super) struct ProtocolInterface<'db> {
#[returns(ref)] #[returns(ref)]
inner: BTreeMap<Name, ProtocolMemberData<'db>>, inner: BTreeMap<Name, ProtocolMemberData<'db>>,
} }
impl get_size2::GetSize for ProtocolInterfaceMembers<'_> {} impl get_size2::GetSize for ProtocolInterface<'_> {}
/// 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<'db> ProtocolInterface<'db> { impl<'db> ProtocolInterface<'db> {
/// Synthesize a new protocol interface with the given members. /// Synthesize a new protocol interface with the given members.
@ -112,11 +105,11 @@ impl<'db> ProtocolInterface<'db> {
) )
}) })
.collect(); .collect();
Self::Members(ProtocolInterfaceMembers::new(db, members)) Self::new(db, members)
} }
fn empty(db: &'db dyn Db) -> Self { fn empty(db: &'db dyn Db) -> Self {
Self::Members(ProtocolInterfaceMembers::new(db, BTreeMap::default())) Self::new(db, BTreeMap::default())
} }
pub(super) fn members<'a>( pub(super) fn members<'a>(
@ -126,16 +119,11 @@ impl<'db> ProtocolInterface<'db> {
where where
'db: 'a, 'db: 'a,
{ {
match self { self.inner(db).iter().map(|(name, data)| ProtocolMember {
Self::Members(members) => { name,
Either::Left(members.inner(db).iter().map(|(name, data)| ProtocolMember { kind: data.kind,
name, qualifiers: data.qualifiers,
kind: data.kind, })
qualifiers: data.qualifiers,
}))
}
Self::SelfReference => Either::Right(std::iter::empty()),
}
} }
pub(super) fn member_by_name<'a>( pub(super) fn member_by_name<'a>(
@ -143,29 +131,20 @@ impl<'db> ProtocolInterface<'db> {
db: &'db dyn Db, db: &'db dyn Db,
name: &'a str, name: &'a str,
) -> Option<ProtocolMember<'a, 'db>> { ) -> Option<ProtocolMember<'a, 'db>> {
match self { self.inner(db).get(name).map(|data| ProtocolMember {
Self::Members(members) => members.inner(db).get(name).map(|data| ProtocolMember { name,
name, kind: data.kind,
kind: data.kind, qualifiers: data.qualifiers,
qualifiers: data.qualifiers, })
}),
Self::SelfReference => None,
}
} }
/// Return `true` if if all members on `self` are also members of `other`. /// 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. /// 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 { pub(super) fn is_sub_interface_of(self, db: &'db dyn Db, other: Self) -> bool {
match (self, other) { self.inner(db)
(Self::Members(self_members), Self::Members(other_members)) => self_members .keys()
.inner(db) .all(|member_name| other.inner(db).contains_key(member_name))
.keys()
.all(|member_name| other_members.inner(db).contains_key(member_name)),
_ => {
unreachable!("Enclosing protocols should never be a self-reference marker")
}
}
} }
/// Return `true` if the types of any of the members match the closure passed in. /// 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)) .any(|member| member.any_over_type(db, type_fn))
} }
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::new(
Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( db,
db, self.inner(db)
members .iter()
.inner(db) .map(|(name, data)| (name.clone(), data.normalized_impl(db, visitor)))
.iter() .collect::<BTreeMap<_, _>>(),
.map(|(name, data)| (name.clone(), data.normalized(db))) )
.collect::<BTreeMap<_, _>>(),
)),
Self::SelfReference => Self::SelfReference,
}
} }
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
match self { Self::new(
Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( db,
db, self.inner(db)
members .iter()
.inner(db) .map(|(name, data)| (name.clone(), data.materialize(db, variance)))
.iter() .collect::<BTreeMap<_, _>>(),
.map(|(name, data)| (name.clone(), data.materialize(db, variance))) )
.collect::<BTreeMap<_, _>>(),
)),
Self::SelfReference => Self::SelfReference,
}
} }
pub(super) fn specialized_and_normalized<'a>( pub(super) fn specialized_and_normalized<'a>(
@ -211,22 +182,18 @@ impl<'db> ProtocolInterface<'db> {
db: &'db dyn Db, db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>, type_mapping: &TypeMapping<'a, 'db>,
) -> Self { ) -> Self {
match self { Self::new(
Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( db,
db, self.inner(db)
members .iter()
.inner(db) .map(|(name, data)| {
.iter() (
.map(|(name, data)| { name.clone(),
( data.apply_type_mapping(db, type_mapping).normalized(db),
name.clone(), )
data.apply_type_mapping(db, type_mapping).normalized(db), })
) .collect::<BTreeMap<_, _>>(),
}) )
.collect::<BTreeMap<_, _>>(),
)),
Self::SelfReference => Self::SelfReference,
}
} }
pub(super) fn find_legacy_typevars( pub(super) fn find_legacy_typevars(
@ -234,13 +201,8 @@ impl<'db> ProtocolInterface<'db> {
db: &'db dyn Db, db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>, typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
) { ) {
match self { for data in self.inner(db).values() {
Self::Members(members) => { data.find_legacy_typevars(db, typevars);
for data in members.inner(db).values() {
data.find_legacy_typevars(db, typevars);
}
}
Self::SelfReference => {}
} }
} }
} }
@ -253,8 +215,12 @@ pub(super) struct ProtocolMemberData<'db> {
impl<'db> ProtocolMemberData<'db> { impl<'db> ProtocolMemberData<'db> {
fn normalized(&self, db: &'db dyn Db) -> Self { 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 { Self {
kind: self.kind.normalized(db), kind: self.kind.normalized_impl(db, visitor),
qualifiers: self.qualifiers, qualifiers: self.qualifiers,
} }
} }
@ -290,15 +256,17 @@ enum ProtocolMemberKind<'db> {
} }
impl<'db> 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 { match self {
ProtocolMemberKind::Method(callable) => { ProtocolMemberKind::Method(callable) => {
ProtocolMemberKind::Method(callable.normalized(db)) ProtocolMemberKind::Method(callable.normalized_impl(db, visitor))
} }
ProtocolMemberKind::Property(property) => { 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) (Type::Callable(callable), BoundOnClass::Yes)
if callable.is_function_like(db) => 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 // TODO: method members that have `FunctionLiteral` types should be upcast
// to `CallableType` so that two protocols with identical method members // to `CallableType` so that two protocols with identical method members
// are recognized as equivalent. // are recognized as equivalent.
(Type::FunctionLiteral(_function), BoundOnClass::Yes) => { (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 }; 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)] #[allow(clippy::trivially_copy_pass_by_ref)]

View file

@ -15,10 +15,10 @@ use std::{collections::HashMap, slice::Iter};
use itertools::EitherOrBoth; use itertools::EitherOrBoth;
use smallvec::{SmallVec, smallvec}; 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::semantic_index::definition::Definition;
use crate::types::generics::GenericContext; 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 crate::{Db, FxOrderSet};
use ruff_python_ast::{self as ast, name::Name}; 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::from_overloads(
self.overloads self.overloads
.iter() .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> { 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 { Self {
generic_context: self.generic_context.map(|ctx| ctx.normalized(db)), generic_context: self
inherited_generic_context: self.inherited_generic_context.map(|ctx| ctx.normalized(db)), .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: self
.parameters .parameters
.iter() .iter()
.map(|param| param.normalized(db)) .map(|param| param.normalized_impl(db, visitor))
.collect(), .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 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)] #[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. /// Normalize nested unions and intersections in the annotated type, if any.
/// ///
/// See [`Type::normalized`] for more details. /// 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 { let Parameter {
annotated_type, annotated_type,
kind, kind,
@ -1311,7 +1284,7 @@ impl<'db> Parameter<'db> {
} = self; } = self;
// Ensure unions and intersections are ordered in the annotated type (if there is one) // 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 parameter names are stripped from positional-only, variadic and keyword-variadic parameters.
// Ensure that we only record whether a parameter *has* a default // Ensure that we only record whether a parameter *has* a default
@ -1444,14 +1417,6 @@ impl<'db> Parameter<'db> {
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => None, 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)] #[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::place::PlaceAndQualifiers;
use crate::types::{ use crate::types::{
ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation, ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation,
TypeVarInstance, TypeVarInstance, TypeVisitor,
}; };
use crate::{Db, FxOrderSet}; 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 { 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 { 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()), 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::class::{ClassType, KnownClass};
use crate::types::{ 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::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
@ -174,8 +175,12 @@ impl<'db> TupleType<'db> {
/// ///
/// See [`Type::normalized`] for more details. /// See [`Type::normalized`] for more details.
#[must_use] #[must_use]
pub(crate) fn normalized(self, db: &'db dyn Db) -> Option<Self> { pub(crate) fn normalized_impl(
TupleType::new(db, self.tuple(db).normalized(db)) 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> { pub(crate) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Option<Self> {
@ -327,8 +332,8 @@ impl<'db> FixedLengthTuple<Type<'db>> {
} }
#[must_use] #[must_use]
fn normalized(&self, db: &'db dyn Db) -> Self { fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
Self::from_elements(self.0.iter().map(|ty| ty.normalized(db))) Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor)))
} }
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
@ -640,14 +645,16 @@ impl<'db> VariableLengthTuple<Type<'db>> {
} }
#[must_use] #[must_use]
fn normalized(&self, db: &'db dyn Db) -> TupleSpec<'db> { fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> TupleSpec<'db> {
Self::mixed( let prefix = self
self.prenormalized_prefix_elements(db, None) .prenormalized_prefix_elements(db, None)
.map(|ty| ty.normalized(db)), .map(|ty| ty.normalized_impl(db, visitor))
self.variable.normalized(db), .collect::<Vec<_>>();
self.prenormalized_suffix_elements(db, None) let suffix = self
.map(|ty| ty.normalized(db)), .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> { 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 { match self {
Tuple::Fixed(tuple) => Tuple::Fixed(tuple.normalized(db)), Tuple::Fixed(tuple) => Tuple::Fixed(tuple.normalized_impl(db, visitor)),
Tuple::Variable(tuple) => tuple.normalized(db), Tuple::Variable(tuple) => tuple.normalized_impl(db, visitor),
} }
} }