From a0f64bd0ae3ae3f5a2224c8030c4e7825a2c823e Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 2 Dec 2025 21:41:55 -0500 Subject: [PATCH] even more hack --- ...pr…_-_Introduction_(cff2724f4c9d28c4).snap | 8 +++--- crates/ty_python_semantic/src/types.rs | 18 +++++++++++-- .../ty_python_semantic/src/types/function.rs | 25 +------------------ 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/deprecated.md_-_Tests_for_the_`@depr…_-_Introduction_(cff2724f4c9d28c4).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/deprecated.md_-_Tests_for_the_`@depr…_-_Introduction_(cff2724f4c9d28c4).snap index 89eb99e534..4e4d8f0ae7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/deprecated.md_-_Tests_for_the_`@depr…_-_Introduction_(cff2724f4c9d28c4).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/deprecated.md_-_Tests_for_the_`@depr…_-_Introduction_(cff2724f4c9d28c4).snap @@ -15,9 +15,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md 1 | from typing_extensions import deprecated 2 | 3 | @deprecated("use OtherClass") - 4 | def myfunc(): ... + 4 | def myfunc(x: int): ... 5 | - 6 | myfunc() # error: [deprecated] "use OtherClass" + 6 | myfunc(1) # error: [deprecated] "use OtherClass" 7 | from typing_extensions import deprecated 8 | 9 | @deprecated("use BetterClass") @@ -42,9 +42,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md warning[deprecated]: The function `myfunc` is deprecated --> src/mdtest_snippet.py:6:1 | -4 | def myfunc(): ... +4 | def myfunc(x: int): ... 5 | -6 | myfunc() # error: [deprecated] "use OtherClass" +6 | myfunc(1) # error: [deprecated] "use OtherClass" | ^^^^^^ use OtherClass 7 | from typing_extensions import deprecated | diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index bdb69ee341..eb145b291f 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -907,8 +907,22 @@ impl<'db> Type<'db> { previous: Self, cycle: &salsa::Cycle, ) -> Self { - // Note: other parts of this crate assume that this union will be ordered with the types - // from later cycle iterations appearing first. + // When we encounter a salsa cycle, we want to avoid oscillating between two or more types + // without converging on a fixed-point result. Most of the time, we union together the + // types from each cycle iteration to ensure that our result is monotonic, even if we + // encounter oscillation. + // + // However, there are several parts of our type inference machinery that assume that we + // infer a single Type::FunctionLiteral type for each overload of each function definition. + // So we avoid the union behavior for those cases, and instead return the inferred type of + // the last cycle iteration. + // + // TODO: If this reintroduces "too many cycle iterations" panics, then we will need to + // consider a different union-like behavior for combining function signatures to ensure + // monotonicity. + if self.is_function_literal() && previous.is_function_literal() { + return self; + } UnionType::from_elements_cycle_recovery(db, [self, previous]) .recursive_type_normalized(db, cycle) } diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 297b00427d..339689169c 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -372,35 +372,12 @@ impl<'db> OverloadLiteral<'db> { .name .scoped_use_id(db, scope); - let Place::Defined(previous_type, _, Definedness::AlwaysDefined) = + let Place::Defined(Type::FunctionLiteral(previous_type), _, Definedness::AlwaysDefined) = place_from_bindings(db, use_def.bindings_at_use(use_id)) else { return None; }; - // TODO: When we encounter a salsa cycle during type inference, we currently union together - // the inferred types from each cycle iteration. That means that in certain cases - // (especially involving decorators), we can end up with a union of FunctionLiterals for - // each overload, instead of a single bare FunctionLiteral. If we do see a union - // (containing _only_ function literals), pull out the first function literal and use it as - // the type of the overload. Note that this depends on how Type::cycle_normalized orders - // things so that later cycle iterations appear first in the union. - let previous_type = match previous_type { - Type::FunctionLiteral(function) => function, - Type::Union(union_type) - if union_type - .elements(db) - .iter() - .all(Type::is_function_literal) => - { - // SAFETY: We just checked this - union_type.elements(db)[0] - .as_function_literal() - .expect("type should be a function literal") - } - _ => return None, - }; - let previous_literal = previous_type.literal(db); let previous_overload = previous_literal.last_definition(db); if !previous_overload.is_overload(db) {