Skip to content

fix(checker): avoid false overload-cannot-match with ParamSpec args#21194

Open
Bahtya wants to merge 2 commits intopython:masterfrom
Bahtya:fix/paramspec-overload-overlap
Open

fix(checker): avoid false overload-cannot-match with ParamSpec args#21194
Bahtya wants to merge 2 commits intopython:masterfrom
Bahtya:fix/paramspec-overload-overlap

Conversation

@Bahtya
Copy link
Copy Markdown

@Bahtya Bahtya commented Apr 9, 2026

Problem

When an overload uses ParamSpec-flavored *args (P.args) or **kwargs (P.kwargs), mypy incorrectly reports that a second overload with explicit keyword-only parameters "will never be matched":

P = ParamSpec("P")
T = TypeVar("T")

@overload
def bar(f: Callable[P, T], *a: P.args, **k: P.kwargs) -> T: ...
@overload
def bar(f: Callable[..., T], *a: Any, baz: int, **k: Any) -> T: ...
def bar(f, *a, **k): ...

Output:

test.pyi:9: error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader  [overload-cannot-match]

Root Cause

overload_can_never_match erases type variables by expanding them (erase_def_to_union_or_bound). For ParamSpec, this erases to Any, so *a: P.args and **k: P.kwargs become *a: Any and **k: Any. This makes the first overload appear to accept all possible arguments — including the baz keyword argument — causing mypy to conclude (incorrectly) that the second overload can never be reached.

In reality, P.kwargs is constrained to the wrapped function f's actual keyword parameters, so a call with baz=5 would NOT match the first overload if f doesn't accept a baz parameter.

Solution

In overload_can_never_match, check if the signature has ParamSpec-flavored variadic arguments (P.args or P.kwargs). If so, skip the can-never-match check, since we cannot reliably determine overlap after erasing ParamSpec to Any.

Testing

  • Added testOverloadParamSpecNoFalsePositiveCannotMatch test case in check-parameter-specification.test
  • All 463 existing overload-related tests pass (was 462 before — +1 new test)

Fixes #21171

Bahtya and others added 2 commits April 9, 2026 18:51
When an overload uses ParamSpec-flavored *args (P.args) or **kwargs
(P.kwargs), erasing the ParamSpec to Any makes the signature appear to
accept all arguments. This causes a false 'overload will never be
matched' error when combined with a second overload that has explicit
keyword-only parameters.

Skip the can-never-match check when the first overload has
ParamSpec-flavored variadic arguments, since we cannot reliably
determine overlap after erasure.

Fixes python#21171

Signed-off-by: bahtya <bahtyar153@qq.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@Bahtya
Copy link
Copy Markdown
Author

Bahtya commented Apr 9, 2026

Hi mypy maintainers! Friendly ping on this PR.

To summarize for ease of review:

  • All CI checks pass (including mypy_primer, mypyc, self-check across Python 3.10–3.14)
  • mypy_primer result: No effect on the open source corpus — clean ✅
  • The fix is minimal (27 lines added, 0 deleted): it adds an early-exit guard in overload_can_never_match when either signature uses ParamSpec-flavored variadic args (P.args/P.kwargs), since erasing ParamSpec to Any makes overlap analysis unreliable.
  • Fixes Overlapping overloads false positive with ParamSpec shenanigans #21171: false overload-cannot-match when combining a ParamSpec-based overload with an explicit keyword-only parameter overload.

Happy to make any adjustments if needed. Thanks for your time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Overlapping overloads false positive with ParamSpec shenanigans

1 participant