From edf9e38f5cb9e7d4c4180377f47b625e4fb20e1e Mon Sep 17 00:00:00 2001 From: Fangyi Zhou Date: Tue, 14 Apr 2026 18:18:01 -0700 Subject: [PATCH] Skip self/cls in overload consistency checks (#3112) Summary: Pull Request resolved: https://github.com/facebook/pyrefly/pull/3112 Skip the self/cls parameter when checking overload input signature consistency. Without this, overloads with narrowed self types (e.g. `self: C[int]`) produce false positive `inconsistent-overload` errors because `C[int] <: Self@C` fails after removing the unsound `ClassType <: SelfType` subtyping rule. This is a deliberate relaxation: stripping self loses type variable constraints that flow through the self parameter, but in practice the self type is validated separately (must be a superclass of the defining class, D97390197), and callers don't pass self explicitly. This is preparatory work for removing the unsound `ClassType <: SelfType` subtyping rule. Reviewed By: grievejia Differential Revision: D99748452 --- conformance/third_party/conformance.exp | 11 --------- pyrefly/lib/alt/function.rs | 10 ++++++++ pyrefly/lib/test/class_overrides.rs | 32 ++++++++++++++++++++++--- pyrefly/lib/test/constructors.rs | 4 ++-- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/conformance/third_party/conformance.exp b/conformance/third_party/conformance.exp index 2e50e4643c..b676ef3516 100644 --- a/conformance/third_party/conformance.exp +++ b/conformance/third_party/conformance.exp @@ -8915,17 +8915,6 @@ "stop_column": 14, "stop_line": 90 }, - { - "code": -2, - "column": 9, - "concise_description": "Implementation signature `(cls: type[Self@C], *args: int | str) -> int | str` does not accept all arguments that overload signature `(self: Self@C, x: str, /) -> str` accepts", - "description": "Implementation signature `(cls: type[Self@C], *args: int | str) -> int | str` does not accept all arguments that overload signature `(self: Self@C, x: str, /) -> str` accepts", - "line": 90, - "name": "inconsistent-overload", - "severity": "error", - "stop_column": 14, - "stop_line": 90 - }, { "code": -2, "column": 9, diff --git a/pyrefly/lib/alt/function.rs b/pyrefly/lib/alt/function.rs index 163aec6d38..c34fce460f 100644 --- a/pyrefly/lib/alt/function.rs +++ b/pyrefly/lib/alt/function.rs @@ -1784,14 +1784,24 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { Some(Arc::new(all_tparams)) } }; + let has_self_param = def.defining_cls().is_some() && !def.metadata().flags.is_staticmethod; let sig_for_input_check = |sig: &Callable| { let mut sig = sig.clone(); // Set the return type to `Any` so that we check just the input signature. sig.ret = self.heap.mk_any_implicit(); + // Skip self/cls to avoid false positive overload errors on narrowed self types. + if has_self_param { + let mut owner = Owner::new(); + if let Some((_, rest)) = sig.split_first_param(&mut owner) { + sig = rest; + } + } sig }; // Collect param name -> default map from implementation so we can check for // inconsistencies between the default and the param type in overloads. + // This uses the original parameter lists instead of `sig_for_input_check`: + // self/cls never has a default, so stripping the receiver is unnecessary here. let mut defaults = match &impl_sig.params { Params::List(params) => params .items() diff --git a/pyrefly/lib/test/class_overrides.rs b/pyrefly/lib/test/class_overrides.rs index 553757626b..971728c76c 100644 --- a/pyrefly/lib/test/class_overrides.rs +++ b/pyrefly/lib/test/class_overrides.rs @@ -1307,16 +1307,16 @@ class MyString(str): ); testcase!( - bug = "We raise 4 inconsistent-overload errors where pyright raises 1. We should investigate this.", + bug = "We raise 2 inconsistent-overload errors where pyright raises 1. We should investigate this.", test_override_all_parent_overloads_inapplicable, r#" from typing import overload, LiteralString class Base(str): @overload - def method(self: LiteralString) -> LiteralString: ... # E: Implementation signature `(self: Self@Base, x: Unknown | None = None) -> Self@Base` does not accept all arguments that overload signature `(self: LiteralString) -> LiteralString` accepts # E: Overload return type `LiteralString` is not assignable to implementation return type `Self@Base` + def method(self: LiteralString) -> LiteralString: ... # E: Overload return type `LiteralString` is not assignable to implementation return type `Self@Base` @overload - def method(self: LiteralString, x: int) -> LiteralString: ... # E: Implementation signature `(self: Self@Base, x: Unknown | None = None) -> Self@Base` does not accept all arguments that overload signature `(self: LiteralString, x: int) -> LiteralString` accepts # E: Overload return type `LiteralString` is not assignable to implementation return type `Self@Base` + def method(self: LiteralString, x: int) -> LiteralString: ... # E: Overload return type `LiteralString` is not assignable to implementation return type `Self@Base` def method(self, x=None): return self @@ -1348,6 +1348,32 @@ class Child(Parent): "#, ); +testcase!( + test_override_generic_overload_with_inapplicable_cls, + r#" +from typing import overload + +class Parent[T]: + @classmethod + @overload + def make(cls: type["Narrow"], x: T) -> T: ... + @classmethod + @overload + def make(cls, x: int) -> int: ... + @classmethod + def make(cls, x): + return x + +class Narrow(Parent[int]): + pass + +class Child(Parent[int]): + @classmethod + def make(cls, x: int) -> int: + return x + "#, +); + testcase!( test_override_init_with_decorator, r#" diff --git a/pyrefly/lib/test/constructors.rs b/pyrefly/lib/test/constructors.rs index 90ac83e6a1..61a09e2722 100644 --- a/pyrefly/lib/test/constructors.rs +++ b/pyrefly/lib/test/constructors.rs @@ -668,9 +668,9 @@ class E(A): class C[T]: @overload - def __init__(self: A, x: Literal[True]) -> None: ... # E: `__init__` method self type `A` is not a superclass of class `C` # E: Implementation signature `(self: Self@C, x: Unknown) -> None` does not accept all arguments that overload signature `(self: A, x: Literal[True]) -> None` accepts + def __init__(self: A, x: Literal[True]) -> None: ... # E: `__init__` method self type `A` is not a superclass of class `C` @overload - def __init__(self: B, x: Literal[False]) -> None: ... # E: `__init__` method self type `B` is not a superclass of class `C` # E: Implementation signature `(self: Self@C, x: Unknown) -> None` does not accept all arguments that overload signature `(self: B, x: Literal[False]) -> None` accepts + def __init__(self: B, x: Literal[False]) -> None: ... # E: `__init__` method self type `B` is not a superclass of class `C` def __init__(self, x): pass