[ty] improve cycle-detection coverage for apply_type_mapping (#20159)
Some checks failed
CI / Determine changes (push) Has been cancelled
CI / cargo fmt (push) Has been cancelled
CI / cargo build (release) (push) Has been cancelled
CI / python package (push) Has been cancelled
CI / pre-commit (push) Has been cancelled
CI / mkdocs (push) Has been cancelled
[ty Playground] Release / publish (push) Has been cancelled
CI / cargo clippy (push) Has been cancelled
CI / cargo test (linux) (push) Has been cancelled
CI / cargo test (linux, release) (push) Has been cancelled
CI / cargo test (windows) (push) Has been cancelled
CI / cargo test (wasm) (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / cargo fuzz build (push) Has been cancelled
CI / fuzz parser (push) Has been cancelled
CI / test scripts (push) Has been cancelled
CI / ecosystem (push) Has been cancelled
CI / Fuzz for new ty panics (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / formatter instabilities and black similarity (push) Has been cancelled
CI / test ruff-lsp (push) Has been cancelled
CI / check playground (push) Has been cancelled
CI / benchmarks-instrumented (push) Has been cancelled
CI / benchmarks-walltime (push) Has been cancelled

## Summary

Thread visitors through the rest of `apply_type_mapping`: callable and
protocol types.

## Test Plan

Added mdtest that previously stack overflowed.
This commit is contained in:
Carl Meyer 2025-08-29 16:20:07 -07:00 committed by GitHub
parent 17dc2e4d80
commit 6f2b874d6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 112 additions and 44 deletions

View file

@ -276,6 +276,17 @@ def h(x: Intersection[A, B]):
reveal_type(x) # revealed: tuple[B] | None
```
### Self-recursive callable type
```py
from typing import Callable
type C = Callable[[], C | None]
def _(x: C):
reveal_type(x) # revealed: () -> C | None
```
### Union inside generic
#### With old-style union

View file

@ -489,13 +489,18 @@ fn walk_property_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
impl get_size2::GetSize for PropertyInstanceType<'_> {}
impl<'db> PropertyInstanceType<'db> {
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
fn apply_type_mapping_impl<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
let getter = self
.getter(db)
.map(|ty| ty.apply_type_mapping(db, type_mapping));
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor));
let setter = self
.setter(db)
.map(|ty| ty.apply_type_mapping(db, type_mapping));
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor));
Self::new(db, getter, setter)
}
@ -6077,18 +6082,18 @@ impl<'db> Type<'db> {
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)) => {
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(
property.apply_type_mapping(db, type_mapping),
property.apply_type_mapping_impl(db, type_mapping, visitor),
))
}
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(property)) => {
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(
property.apply_type_mapping(db, type_mapping),
property.apply_type_mapping_impl(db, type_mapping, visitor),
))
}
Type::Callable(callable) => {
Type::Callable(callable.apply_type_mapping(db, type_mapping))
Type::Callable(callable.apply_type_mapping_impl(db, type_mapping, visitor))
}
Type::GenericAlias(generic) => {
@ -6104,7 +6109,7 @@ impl<'db> Type<'db> {
),
Type::PropertyInstance(property) => {
Type::PropertyInstance(property.apply_type_mapping(db, type_mapping))
Type::PropertyInstance(property.apply_type_mapping_impl(db, type_mapping, visitor))
}
Type::Union(union) => union.map(db, |element| {
@ -8985,10 +8990,16 @@ impl<'db> CallableType<'db> {
)
}
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
fn apply_type_mapping_impl<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
CallableType::new(
db,
self.signatures(db).apply_type_mapping(db, type_mapping),
self.signatures(db)
.apply_type_mapping_impl(db, type_mapping, visitor),
self.is_function_like(db),
)
}

View file

@ -569,7 +569,7 @@ impl<'db> FunctionLiteral<'db> {
if overloads.is_empty() {
return CallableSignature::single(type_mappings.iter().fold(
implementation.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
|sig, mapping| sig.apply_type_mapping(db, mapping),
));
}
}
@ -577,7 +577,7 @@ impl<'db> FunctionLiteral<'db> {
CallableSignature::from_overloads(overloads.iter().map(|overload| {
type_mappings.iter().fold(
overload.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
|sig, mapping| sig.apply_type_mapping(db, mapping),
)
}))
}
@ -602,7 +602,7 @@ impl<'db> FunctionLiteral<'db> {
type_mappings.iter().fold(
self.last_definition(db)
.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
|sig, mapping| sig.apply_type_mapping(db, mapping),
)
}

View file

@ -17,10 +17,10 @@ use crate::{
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
semantic_index::{definition::Definition, use_def_map},
types::{
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsDisjointVisitor, KnownFunction, MaterializationKind,
NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers,
TypeRelation, VarianceInferable,
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral,
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownFunction,
MaterializationKind, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping,
TypeQualifiers, TypeRelation, VarianceInferable,
constraints::{Constraints, IteratorConstraintsExtension},
signatures::{Parameter, Parameters},
},
@ -282,7 +282,12 @@ impl<'db> ProtocolInterface<'db> {
.map(|(name, data)| {
(
name.clone(),
data.apply_type_mapping(db, type_mapping).normalized(db),
data.apply_type_mapping_impl(
db,
type_mapping,
&ApplyTypeMappingVisitor::default(),
)
.normalized(db),
)
})
.collect::<BTreeMap<_, _>>(),
@ -354,9 +359,14 @@ impl<'db> ProtocolMemberData<'db> {
}
}
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
Self {
kind: self.kind.apply_type_mapping(db, type_mapping),
kind: self.kind.apply_type_mapping_impl(db, type_mapping, visitor),
qualifiers: self.qualifiers,
}
}
@ -444,16 +454,21 @@ impl<'db> ProtocolMemberKind<'db> {
}
}
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
match self {
ProtocolMemberKind::Method(callable) => {
ProtocolMemberKind::Method(callable.apply_type_mapping(db, type_mapping))
}
ProtocolMemberKind::Property(property) => {
ProtocolMemberKind::Property(property.apply_type_mapping(db, type_mapping))
}
ProtocolMemberKind::Method(callable) => ProtocolMemberKind::Method(
callable.apply_type_mapping_impl(db, type_mapping, visitor),
),
ProtocolMemberKind::Property(property) => ProtocolMemberKind::Property(
property.apply_type_mapping_impl(db, type_mapping, visitor),
),
ProtocolMemberKind::Other(ty) => {
ProtocolMemberKind::Other(ty.apply_type_mapping(db, type_mapping))
ProtocolMemberKind::Other(ty.apply_type_mapping_impl(db, type_mapping, visitor))
}
}
}

View file

@ -20,9 +20,9 @@ use crate::semantic_index::definition::Definition;
use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension};
use crate::types::generics::{GenericContext, walk_generic_context};
use crate::types::{
BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor, TypeMapping,
TypeRelation, VarianceInferable, todo_type,
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor,
TypeMapping, TypeRelation, VarianceInferable, todo_type,
};
use crate::{Db, FxOrderSet};
use ruff_python_ast::{self as ast, name::Name};
@ -82,15 +82,16 @@ impl<'db> CallableSignature<'db> {
)
}
pub(crate) fn apply_type_mapping<'a>(
pub(crate) fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
Self::from_overloads(
self.overloads
.iter()
.map(|signature| signature.apply_type_mapping(db, type_mapping)),
.map(|signature| signature.apply_type_mapping_impl(db, type_mapping, visitor)),
)
}
@ -458,15 +459,26 @@ impl<'db> Signature<'db> {
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
) -> Self {
self.apply_type_mapping_impl(db, type_mapping, &ApplyTypeMappingVisitor::default())
}
pub(crate) fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
Self {
generic_context: self.generic_context,
inherited_generic_context: self.inherited_generic_context,
definition: self.definition,
parameters: self.parameters.apply_type_mapping(db, type_mapping),
parameters: self
.parameters
.apply_type_mapping_impl(db, type_mapping, visitor),
return_ty: self
.return_ty
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
}
}
@ -504,7 +516,11 @@ impl<'db> Signature<'db> {
let mut parameters = Parameters::new(self.parameters().iter().skip(1).cloned());
let mut return_ty = self.return_ty;
if let Some(self_type) = self_type {
parameters = parameters.apply_type_mapping(db, &TypeMapping::BindSelf(self_type));
parameters = parameters.apply_type_mapping_impl(
db,
&TypeMapping::BindSelf(self_type),
&ApplyTypeMappingVisitor::default(),
);
return_ty =
return_ty.map(|ty| ty.apply_type_mapping(db, &TypeMapping::BindSelf(self_type)));
}
@ -1232,12 +1248,17 @@ impl<'db> Parameters<'db> {
)
}
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
Self {
value: self
.value
.iter()
.map(|param| param.apply_type_mapping(db, type_mapping))
.map(|param| param.apply_type_mapping_impl(db, type_mapping, visitor))
.collect(),
is_gradual: self.is_gradual,
}
@ -1416,12 +1437,17 @@ impl<'db> Parameter<'db> {
}
}
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
Self {
annotated_type: self
.annotated_type
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
kind: self.kind.apply_type_mapping(db, type_mapping),
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
kind: self.kind.apply_type_mapping_impl(db, type_mapping, visitor),
form: self.form,
}
}
@ -1625,24 +1651,29 @@ pub(crate) enum ParameterKind<'db> {
}
impl<'db> ParameterKind<'db> {
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
fn apply_type_mapping_impl<'a>(
&self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
match self {
Self::PositionalOnly { default_type, name } => Self::PositionalOnly {
default_type: default_type
.as_ref()
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
name: name.clone(),
},
Self::PositionalOrKeyword { default_type, name } => Self::PositionalOrKeyword {
default_type: default_type
.as_ref()
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
name: name.clone(),
},
Self::KeywordOnly { default_type, name } => Self::KeywordOnly {
default_type: default_type
.as_ref()
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
name: name.clone(),
},
Self::Variadic { .. } | Self::KeywordVariadic { .. } => self.clone(),