Skip to content

Commit d37f5ab

Browse files
committed
fix(checker): avoid false overload-cannot-match with ParamSpec args
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 #21171 Signed-off-by: bahtya <bahtyar153@qq.com>
1 parent 6cbd2cc commit d37f5ab

File tree

2 files changed

+27
-0
lines changed

2 files changed

+27
-0
lines changed

mypy/checker.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@
228228
TypeVarId,
229229
TypeVarLikeType,
230230
TypeVarTupleType,
231+
ParamSpecType,
231232
TypeVarType,
232233
UnboundType,
233234
UninhabitedType,
@@ -8954,6 +8955,16 @@ def overload_can_never_match(signature: CallableType, other: CallableType) -> bo
89548955
89558956
Assumes that both signatures have overlapping argument counts.
89568957
"""
8958+
# If the signature uses ParamSpec-flavored *args or **kwargs, we cannot
8959+
# reliably determine overlap. Erasing a ParamSpec to Any makes
8960+
# P.args/P.kwargs look like *Any/**Any, which appears to accept all
8961+
# arguments — but in reality the ParamSpec is constrained to the
8962+
# wrapped function's parameters. This leads to false positives where
8963+
# we incorrectly conclude that the other overload can never match.
8964+
for arg_type in signature.arg_types:
8965+
if isinstance(arg_type, ParamSpecType) and arg_type.flavor != 0: # BARE = 0
8966+
return False
8967+
89578968
# The extra erasure is needed to prevent spurious errors
89588969
# in situations where an `Any` overload is used as a fallback
89598970
# for an overload with type variables. The spurious error appears

test-data/unit/check-parameter-specification.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,3 +2756,19 @@ reveal_type(Sneaky(f8, 1, y='').kwargs) # N: Revealed type is "builtins.dict[bu
27562756
reveal_type(Sneaky(f9, 1, y=0).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})"
27572757
reveal_type(Sneaky(f9, 1, y=0, z='').kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})"
27582758
[builtins fixtures/paramspec.pyi]
2759+
2760+
[case testOverloadParamSpecNoFalsePositiveCannotMatch]
2761+
# Test that overloads using P.args/P.kwargs don't trigger false
2762+
# "overload will never be matched" errors when combined with
2763+
# overloads using explicit keyword-only parameters.
2764+
from typing import Any, overload, ParamSpec, TypeVar, Callable
2765+
2766+
P = ParamSpec("P")
2767+
T = TypeVar("T")
2768+
2769+
@overload
2770+
def bar(f: Callable[P, T], *a: P.args, **k: P.kwargs) -> T: ...
2771+
@overload
2772+
def bar(f: Callable[..., T], *a: Any, baz: int, **k: Any) -> T: ...
2773+
def bar(f, *a, **k): ...
2774+
[builtins fixtures/paramspec.pyi]

0 commit comments

Comments
 (0)