From 9777d221857307e515cce811b3dc4819a4cd5659 Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Thu, 15 Oct 2026 23:27:49 +0900 Subject: [PATCH] Improve diagnostic for positional-only related overload error --- mypy/checker.py | 24 +++++++++++++++++++++++- mypy/messages.py | 7 ++++++- test-data/unit/check-overloading.test | 19 +++++++++++++++++++ test-data/unit/check-python38.test | 3 ++- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2c49ae386f8d2..64852cb724f43 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -238,6 +238,7 @@ def __init__(self) -> None: from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type, make_optional_type from mypy.typeops import ( bind_self, + callable_corresponding_argument, can_have_shared_disjoint_base, coerce_to_literal, custom_special_method, @@ -1144,7 +1145,28 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: if not is_callable_compatible( impl, sig1, is_compat=is_subtype, is_proper_subtype=False, ignore_return=True ): - self.msg.overloaded_signatures_param_specific(i + 1, defn.impl) + positional_only_args: list[str] = [] + if is_callable_compatible( + impl, + sig1, + is_compat=is_subtype, + is_proper_subtype=False, + ignore_return=True, + ignore_pos_arg_names=True, + ): + normalized_impl = impl.with_unpacked_kwargs().with_normalized_var_args() + normalized_sig = sig1.with_unpacked_kwargs().with_normalized_var_args() + for arg in normalized_sig.formal_arguments(): + impl_arg = callable_corresponding_argument(normalized_impl, arg) + if ( + arg.name is not None + and impl_arg is not None + and arg.name != impl_arg.name + ): + positional_only_args.append(arg.name) + self.msg.overloaded_signatures_param_specific( + i + 1, defn.impl, positional_only_args + ) # Is the overload alternative's return type a subtype of the implementation's? if not ( diff --git a/mypy/messages.py b/mypy/messages.py index b58c9e7ac4b6c..0f3a5be368a4b 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1755,7 +1755,9 @@ def overloaded_signatures_typevar_specific(self, index: int, context: Context) - context, ) - def overloaded_signatures_param_specific(self, index: int, context: Context) -> None: + def overloaded_signatures_param_specific( + self, index: int, context: Context, positional_only_args: list[str] + ) -> None: self.fail( ( f"Overloaded function implementation does not accept all possible parameters " @@ -1763,6 +1765,9 @@ def overloaded_signatures_param_specific(self, index: int, context: Context) -> ), context, ) + if positional_only_args: + names = ", ".join(f'"{name}"' for name in positional_only_args) + self.note(f"Consider marking {names} positional-only in signature {index}", context) def overloaded_signatures_ret_specific(self, index: int, context: Context) -> None: self.fail( diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 8716995e4fcfb..cd5b924ed6b49 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -233,6 +233,25 @@ reveal_type(f(A())) # N: Revealed type is "__main__.B" reveal_type(f(B())) # N: Revealed type is "__main__.A" [builtins fixtures/isinstance.pyi] +[case testOverloadImplementationArgumentNames] +from typing import overload + +@overload +def f(x: int, y: int) -> None: ... +@overload +def f(x: str, y: int, /) -> None: ... +def f(a: int | str, /, b: int) -> None: # E: Overloaded function implementation does not accept all possible parameters of signature 1 \ + # N: Consider marking "x", "y" positional-only in signature 1 + pass + +@overload +def h(x: int = 0, y: int = 0) -> None: ... +@overload +def h(x: str, /, y: int = 0) -> None: ... +def h(a: int | str = 0, b: int = 0, /, *, y: int = 0) -> None: # E: Overloaded function implementation does not accept all possible parameters of signature 1 \ + # N: Consider marking "x" positional-only in signature 1 + pass + [case testTypeCheckOverloadImplOverlapVarArgsAndKwargs] from __future__ import annotations from typing import overload diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 50daac19f81ae..628c40096d94f 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -566,7 +566,8 @@ class Foo: @overload def f(self, *, b: bool = False) -> None: ... - def f(self, a: Optional[str] = None, /, *, b: bool = False) -> None: # E: Overloaded function implementation does not accept all possible parameters of signature 2 + def f(self, a: Optional[str] = None, /, *, b: bool = False) -> None: # E: Overloaded function implementation does not accept all possible parameters of signature 2 \ + # N: Consider marking "self" positional-only in signature 2 ... class Bar: