Skip to content

Commit 331aab6

Browse files
jorenhammeta-codesync[bot]
authored andcommitted
Fix overloads with a generic self: dropped when matching a Protocol (#3975)
Summary: `filter_overloads_by_self_type` dropped any overload whose `self:` mentions the overload's own type params, since `is_subset_eq(receiver, self)` checks against a rigid, unsolvable variable. Substitute those params with `Any` first, so the receiver is matched against a gradual `self:`. An overload whose `self:` mentions its own type parameters is matched gradually by substituting those parameters with `Any (self: Arr[S, T] -> Arr[Any, Any])`, which is exact for the realistic single-occurrence case and only over-accepts the presumably niche degenerate repeated-parameter case, `(self: C[Z, Z])`, same as mypy and pyright. Fixes #3974 Pull Request resolved: #3975 Test Plan: Regression tests added Reviewed By: stroxler Differential Revision: D111462642 Pulled By: NathanTempest fbshipit-source-id: 97419e2b60e39fb737efe881909b53d3bda16573
1 parent a45d7f0 commit 331aab6

2 files changed

Lines changed: 74 additions & 3 deletions

File tree

pyrefly/lib/alt/class/class_field.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3185,16 +3185,26 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
31853185
.signatures
31863186
.iter()
31873187
.filter(|sig| {
3188-
let func = match sig {
3189-
OverloadType::Function(f) => f,
3190-
OverloadType::Forall(forall) => &forall.body,
3188+
let (func, tparams) = match sig {
3189+
OverloadType::Function(f) => (f, None),
3190+
OverloadType::Forall(forall) => (&forall.body, Some(&forall.tparams)),
31913191
};
31923192
// Only instance methods have a `self` first parameter; static and
31933193
// class methods' first parameter is a regular argument.
31943194
if func.metadata.flags.is_staticmethod || func.metadata.flags.is_classmethod {
31953195
return true;
31963196
}
31973197
func.signature.get_first_param().is_none_or(|p| {
3198+
// Replace the overload's own type params in `self:` with `Any`
3199+
// (e.g. `self: Array[S, T]` -> `Array[Any, Any]`), matching against a gradual
3200+
// `self:` rather than a rigid, unsolvable variable.
3201+
let p = match tparams {
3202+
Some(tparams) => {
3203+
let any = self.heap.mk_any_implicit();
3204+
p.subst(&tparams.iter().map(|q| (q, &any)).collect())
3205+
}
3206+
None => p,
3207+
};
31983208
// A non-protocol `self:` can't re-enter protocol conformance, so check
31993209
// it directly. A protocol-typed `self:`, however, makes
32003210
// `is_subset_eq(self_type, p)` re-enter this same filtering on the same

pyrefly/lib/test/protocol.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,3 +1795,64 @@ def f(x: HasCallNoExtra[int, str]) -> Lens[int, str]:
17951795
return x # E: not assignable to declared return type
17961796
"#,
17971797
);
1798+
1799+
testcase!(
1800+
test_protocol_overload_typevar_self_kept,
1801+
r#"
1802+
from typing import Protocol, overload
1803+
1804+
class C:
1805+
@overload
1806+
def f[S](self: S, x: int) -> S: ...
1807+
@overload
1808+
def f(self, x: str) -> str: ...
1809+
def f(self, x: int | str) -> object: ...
1810+
1811+
class P(Protocol):
1812+
def f(self, x: int) -> object: ...
1813+
1814+
p: P = C() # the `self: S` overload binds S=C, satisfying P
1815+
"#,
1816+
);
1817+
1818+
testcase!(
1819+
test_protocol_overload_parameterized_self_kept,
1820+
r#"
1821+
from typing import Protocol, overload
1822+
1823+
class Arr[S, T]:
1824+
@overload
1825+
def astype[S2, T2](self: Arr[S2, T2], dtype: int) -> Arr[S2, T2]: ...
1826+
@overload
1827+
def astype(self, dtype: str) -> bytes: ...
1828+
def astype(self, dtype: object) -> object: ...
1829+
1830+
class HasAstype(Protocol):
1831+
def astype(self, dtype: int) -> object: ...
1832+
1833+
def g(a: Arr[int, float]) -> HasAstype:
1834+
return a # the `self: Arr[S2, T2]` overload binds to the receiver, satisfying P
1835+
"#,
1836+
);
1837+
1838+
testcase!(
1839+
test_protocol_overload_partially_concrete_self_filtered,
1840+
r#"
1841+
from typing import Protocol, overload
1842+
1843+
class Arr[S, T]:
1844+
@overload
1845+
def m[S2](self: Arr[S2, int], x: int) -> int: ...
1846+
@overload
1847+
def m(self, x: str) -> str: ...
1848+
def m(self, x: object) -> object: ...
1849+
1850+
class HasM(Protocol):
1851+
def m(self, x: int) -> int: ...
1852+
1853+
def g(a: Arr[float, str]) -> HasM:
1854+
# `self: Arr[S2, int]` pins T=int; receiver has T=str, so that overload does not
1855+
# apply and `Arr[float, str]` does not satisfy `HasM`.
1856+
return a # E: not assignable to declared return type
1857+
"#,
1858+
);

0 commit comments

Comments
 (0)