From 7bbf83932590bafe05666115569fd3a33dbf6523 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 2 Dec 2025 18:24:15 -0500 Subject: [PATCH] hackity hack --- .../resources/mdtest/deprecated.md | 10 +------- crates/ty_python_semantic/src/types.rs | 2 ++ .../ty_python_semantic/src/types/function.rs | 25 ++++++++++++++++++- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/deprecated.md b/crates/ty_python_semantic/resources/mdtest/deprecated.md index 2231dac39f..80d5108508 100644 --- a/crates/ty_python_semantic/resources/mdtest/deprecated.md +++ b/crates/ty_python_semantic/resources/mdtest/deprecated.md @@ -329,19 +329,11 @@ from typing_extensions import overload def f(x: str): ... @overload def f(x: int): ... - -# TODO: no error -# (This is caused by a salsa cycle and how we union type inference results to avoid oscillations -# during cycle handling.) -# error: [invalid-overload] "Overloaded function `f` requires at least two overloads" def f(x): print(x) f(1) -# TODO: no invalid-argument-type error -# TODO: error: [deprecated] "strings are no longer supported" -# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["hello"]`" -f("hello") +f("hello") # TODO: error: [deprecated] "strings are no longer supported" ``` If the actual impl is deprecated, the deprecation always fires. diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 8d8092171f..233b7a5fc7 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -907,6 +907,8 @@ 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. 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 339689169c..a2a22697c4 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -372,12 +372,35 @@ impl<'db> OverloadLiteral<'db> { .name .scoped_use_id(db, scope); - let Place::Defined(Type::FunctionLiteral(previous_type), _, Definedness::AlwaysDefined) = + let Place::Defined(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(|element| element.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) {