diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index d0b72df047..427868e855 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -2339,6 +2339,28 @@ class Bar(Protocol[S]): z: S | Bar[S] ``` +### Recursive legacy generic protocol + +```py +from typing import Generic, TypeVar, Protocol + +T = TypeVar("T") + +class P(Protocol[T]): + attr: "P[T] | T" + +class A(Generic[T]): + attr: T + +class B(A[P[int]]): + pass + +def f(b: B): + reveal_type(b) # revealed: B + reveal_type(b.attr) # revealed: P[int] + reveal_type(b.attr.attr) # revealed: P[int] | int +``` + ### Recursive generic protocols with property members An early version of caused stack overflows on this diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 771160099a..beaf5e1ea5 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -5964,7 +5964,7 @@ impl<'db> Type<'db> { /// Note that this does not specialize generic classes, functions, or type aliases! That is a /// different operation that is performed explicitly (via a subscript operation), or implicitly /// via a call to the generic object. - #[salsa::tracked(heap_size=ruff_memory_usage::heap_size)] + #[salsa::tracked(heap_size=ruff_memory_usage::heap_size, cycle_fn=apply_specialization_cycle_recover, cycle_initial=apply_specialization_cycle_initial)] pub(crate) fn apply_specialization( self, db: &'db dyn Db, @@ -6610,6 +6610,24 @@ impl<'db> VarianceInferable<'db> for Type<'db> { } } +fn apply_specialization_cycle_recover<'db>( + _db: &'db dyn Db, + _value: &Type<'db>, + _count: u32, + _self: Type<'db>, + _specialization: Specialization<'db>, +) -> salsa::CycleRecoveryAction> { + salsa::CycleRecoveryAction::Iterate +} + +fn apply_specialization_cycle_initial<'db>( + _db: &'db dyn Db, + _self: Type<'db>, + _specialization: Specialization<'db>, +) -> Type<'db> { + Type::Never +} + /// A mapping that can be applied to a type, producing another type. This is applied inductively to /// the components of complex types. ///