Skip to content

Commit 7f009ab

Browse files
committed
Hack: attempt to fix regression
1 parent f99f89e commit 7f009ab

4 files changed

Lines changed: 91 additions & 36 deletions

File tree

crates/ty_python_semantic/resources/mdtest/overloads.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ python-version = "3.12"
159159
```
160160

161161
```py
162-
from typing import overload
162+
from typing import Generic, TypeVar, overload
163163

164164
class Box[T]:
165165
# Use the class type parameter so specializations remain meaningful.
@@ -174,6 +174,19 @@ class Box[T]:
174174

175175
reveal_type(Box[int]().specialized) # revealed: bound method Box[int].specialized(x: int) -> int
176176
reveal_type(Box[str]().specialized) # revealed: Overload[(x: str) -> str, (x: int) -> int]
177+
178+
T_co = TypeVar("T_co", covariant=True)
179+
180+
class Reader(Generic[T_co]):
181+
@overload
182+
def get(self: "Reader[int]", default: int) -> int: ...
183+
@overload
184+
def get(self: "Reader[str]", default: str) -> str: ...
185+
def get(self, default: object) -> object:
186+
return default
187+
188+
def union_receiver(reader: Reader[int | str]):
189+
reveal_type(reader.get) # revealed: Overload[(default: int) -> int, (default: str) -> str]
177190
```
178191

179192
## Constructor

crates/ty_python_semantic/resources/mdtest/regression/2799_constraint_correlation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ S2 = TypeVar("S2")
2626

2727
class ElementOpsMixin(Generic[S2]):
2828
@overload
29-
def _proto_mul(self: "ElementOpsMixin[bool]", other: bool) -> "ElementOpsMixin[bool]": ...
29+
def _proto_mul(self, other: bool) -> "ElementOpsMixin[bool]": ...
3030
@overload
31-
def _proto_mul(self: "ElementOpsMixin[str]", other: str) -> "ElementOpsMixin[str]": ...
31+
def _proto_mul(self, other: str) -> "ElementOpsMixin[str]": ...
3232
def _proto_mul(self, other):
3333
raise NotImplementedError
3434

crates/ty_python_semantic/src/types/method.rs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,32 +76,45 @@ impl<'db> BoundMethodType<'db> {
7676
)
7777
}
7878

79+
#[salsa::tracked(cycle_initial=|_, _, _| CallableSignature::bottom(), heap_size=ruff_memory_usage::heap_size)]
7980
pub(crate) fn bound_signatures(self, db: &'db dyn Db) -> CallableSignature<'db> {
80-
let self_instance = self.self_instance(db);
81+
let function_signature = self.function(db).signature(db);
8182
let typing_self_type = self.typing_self_type(db);
8283

83-
let mut applicable_overloads = self
84-
.function(db)
85-
.signature(db)
86-
.overloads
87-
.iter()
88-
.filter(|signature| signature.can_bind_self_to(db, self_instance))
89-
.map(|signature| signature.bind_self(db, Some(typing_self_type)))
90-
.peekable();
91-
92-
// If no overload accepts the bound receiver, keep the full overload set so a later call
93-
// still reports the existing invalid-`self` diagnostic instead of becoming non-callable.
94-
if applicable_overloads.peek().is_none() {
95-
let function_signature = self.function(db).signature(db);
96-
CallableSignature::from_overloads(
97-
function_signature
98-
.overloads
99-
.iter()
100-
.map(|signature| signature.bind_self(db, Some(typing_self_type))),
101-
)
102-
} else {
103-
CallableSignature::from_overloads(applicable_overloads)
104-
}
84+
let [signature] = function_signature.overloads.as_slice() else {
85+
let self_instance = self.self_instance(db);
86+
let bind_all_overloads = || {
87+
CallableSignature::from_overloads(
88+
function_signature
89+
.overloads
90+
.iter()
91+
.map(|signature| signature.bind_self(db, Some(typing_self_type))),
92+
)
93+
};
94+
95+
// A gradual receiver can satisfy any receiver-specific overload, so filtering cannot
96+
// safely discard candidates.
97+
if self_instance.has_dynamic(db) {
98+
return bind_all_overloads();
99+
}
100+
101+
let mut applicable_overloads = function_signature
102+
.overloads
103+
.iter()
104+
.filter(|signature| signature.can_bind_self_to(db, self_instance))
105+
.map(|signature| signature.bind_self(db, Some(typing_self_type)))
106+
.peekable();
107+
108+
// If no overload accepts the bound receiver, keep the full overload set so a later call
109+
// still reports the existing invalid-`self` diagnostic instead of becoming non-callable.
110+
return if applicable_overloads.peek().is_none() {
111+
bind_all_overloads()
112+
} else {
113+
CallableSignature::from_overloads(applicable_overloads)
114+
};
115+
};
116+
117+
CallableSignature::single(signature.bind_self(db, Some(typing_self_type)))
105118
}
106119

107120
pub(super) fn recursive_type_normalized_impl(

crates/ty_python_semantic/src/types/signatures.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::types::infer::infer_deferred_types;
2626
use crate::types::relation::{
2727
HasRelationToVisitor, IsDisjointVisitor, TypeRelation, TypeRelationChecker,
2828
};
29+
use crate::types::visitor::any_over_type;
2930
use crate::types::{
3031
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableType, ErrorContext,
3132
FindLegacyTypeVarsVisitor, KnownClass, MaterializationKind, ParamSpecAttrKind,
@@ -744,30 +745,58 @@ impl<'db> Signature<'db> {
744745
return true;
745746
};
746747

748+
// If there is no positional receiver, this signature cannot be pruned based on `self`.
747749
if !first_parameter.is_positional() {
748750
return true;
749751
}
750752

751-
let expected_self_ty = first_parameter
752-
.annotated_type()
753-
.bind_self_typevars(db, self_type)
754-
.apply_optional_specialization(db, self_type.class_specialization(db));
753+
let mut expected_self_ty = first_parameter.annotated_type();
755754

755+
// Avoid the more expensive normalization below for receiver annotations that already
756+
// accept all values, or already exactly match the bound receiver.
756757
if expected_self_ty.is_dynamic()
757758
|| expected_self_ty.is_object()
758759
|| expected_self_ty == self_type
759760
{
760761
return true;
761762
}
762763

764+
// `Self`-like typevars in the receiver annotation are bound using the concrete receiver.
765+
expected_self_ty = expected_self_ty.bind_self_typevars(db, self_type);
766+
767+
if expected_self_ty.is_dynamic()
768+
|| expected_self_ty.is_object()
769+
|| expected_self_ty == self_type
770+
{
771+
return true;
772+
}
773+
774+
// A specialized receiver can make generic receiver annotations concrete enough to compare.
775+
if let Some(self_specialization) = self_type.class_specialization(db) {
776+
expected_self_ty =
777+
expected_self_ty.apply_optional_specialization(db, Some(self_specialization));
778+
779+
if expected_self_ty.is_dynamic()
780+
|| expected_self_ty.is_object()
781+
|| expected_self_ty == self_type
782+
{
783+
return true;
784+
}
785+
}
786+
787+
let inferable = self.inferable_typevars(db);
763788
let constraints = ConstraintSetBuilder::new();
789+
if any_over_type(db, self_type, false, Type::is_union) {
790+
// A union receiver can bind an overload that accepts any possible receiver
791+
// alternative; whole-type assignability would only keep overloads that accept all of
792+
// them.
793+
return !self_type
794+
.when_disjoint_from(db, expected_self_ty, &constraints, inferable)
795+
.is_always_satisfied(db);
796+
}
797+
764798
self_type
765-
.when_assignable_to(
766-
db,
767-
expected_self_ty,
768-
&constraints,
769-
self.inferable_typevars(db),
770-
)
799+
.when_assignable_to(db, expected_self_ty, &constraints, inferable)
771800
.is_always_satisfied(db)
772801
}
773802

0 commit comments

Comments
 (0)