From 82796df9b544dc4f893a933879639e9c20a9e9ee Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 12 Sep 2025 13:39:38 -0700 Subject: [PATCH] [ty] add cycle handling to BoundMethodType::into_callable_type() (#20369) ## Summary This looks like it should fix the errors that we've been seeing in sympy in recent mypy-primer runs. ## Test Plan I wasn't able to reproduce the sympy failures locally; it looks like there is probably a dependency on the order in which files are checked. So I don't have a minimal reproducible example, and wasn't able to add a test :/ Obviously I would be happier if we could commit a regression test here, but since the change is straightforward and clearly desirable, I'm not sure how many hours it's worth trying to track it down. Mypy-primer is still failing in CI on this PR, because it fails on the "old" ty commit already (i.e. on main). But it passes [on a no-op PR stacked on top of this](https://github.com/astral-sh/ruff/pull/20370), which strongly suggests this PR fixes the problem. --- crates/ty_python_semantic/src/types.rs | 24 +++++++++++++++---- .../src/types/property_tests.rs | 2 +- .../src/types/signatures.rs | 4 ++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 2c6de29635..e5256ec9c3 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8938,6 +8938,23 @@ fn walk_bound_method_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( visitor.visit_type(db, method.self_instance(db)); } +#[allow(clippy::trivially_copy_pass_by_ref)] +fn into_callable_type_cycle_recover<'db>( + _db: &'db dyn Db, + _value: &CallableType<'db>, + _count: u32, + _self: BoundMethodType<'db>, +) -> salsa::CycleRecoveryAction> { + salsa::CycleRecoveryAction::Iterate +} + +fn into_callable_type_cycle_initial<'db>( + db: &'db dyn Db, + _self: BoundMethodType<'db>, +) -> CallableType<'db> { + CallableType::bottom(db) +} + #[salsa::tracked] impl<'db> BoundMethodType<'db> { /// Returns the type that replaces any `typing.Self` annotations in the bound method signature. @@ -8951,7 +8968,7 @@ impl<'db> BoundMethodType<'db> { self_instance } - #[salsa::tracked(heap_size=ruff_memory_usage::heap_size)] + #[salsa::tracked(cycle_fn=into_callable_type_cycle_recover, cycle_initial=into_callable_type_cycle_initial, heap_size=ruff_memory_usage::heap_size)] pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> CallableType<'db> { let function = self.function(db); let self_instance = self.typing_self_type(db); @@ -9089,9 +9106,8 @@ impl<'db> CallableType<'db> { /// /// Specifically, this represents a callable type with a single signature: /// `(*args: object, **kwargs: object) -> Never`. - #[cfg(test)] - pub(crate) fn bottom(db: &'db dyn Db) -> Type<'db> { - Self::single(db, Signature::bottom()) + pub(crate) fn bottom(db: &'db dyn Db) -> CallableType<'db> { + Self::new(db, CallableSignature::bottom(), false) } /// Return a "normalized" version of this `Callable` type. diff --git a/crates/ty_python_semantic/src/types/property_tests.rs b/crates/ty_python_semantic/src/types/property_tests.rs index 07db77e628..3953b2ef9e 100644 --- a/crates/ty_python_semantic/src/types/property_tests.rs +++ b/crates/ty_python_semantic/src/types/property_tests.rs @@ -158,7 +158,7 @@ mod stable { type_property_test!( bottom_callable_is_subtype_of_all_callable, db, forall types t. t.is_callable_type() - => CallableType::bottom(db).is_subtype_of(db, t) + => Type::Callable(CallableType::bottom(db)).is_subtype_of(db, t) ); // `T` can be assigned to itself. diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 115b6b46de..f25681fdba 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -43,6 +43,10 @@ impl<'db> CallableSignature<'db> { } } + pub(crate) fn bottom() -> Self { + Self::single(Signature::bottom()) + } + /// Creates a new `CallableSignature` from an iterator of [`Signature`]s. Returns a /// non-callable signature if the iterator is empty. pub(crate) fn from_overloads(overloads: I) -> Self