From 9363eeca264b03ffbf4b54aee0ae2fee6b0e70be Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 28 Aug 2025 09:58:01 -0700 Subject: [PATCH] [ty] add support for cyclic legacy generic protocols (#20125) ## Summary Just add the necessary Salsa cycle handling. ## Test Plan Added mdtest. --- .../resources/mdtest/protocols.md | 22 +++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 20 ++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) 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. ///