diff --git a/mypy/checker.py b/mypy/checker.py index 8775f1ddef29..7b91a8a3b018 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -215,6 +215,7 @@ LiteralType, NoneType, Overloaded, + ParamSpecType, PartialType, ProperType, TupleType, @@ -8954,6 +8955,16 @@ def overload_can_never_match(signature: CallableType, other: CallableType) -> bo Assumes that both signatures have overlapping argument counts. """ + # If the signature uses ParamSpec-flavored *args or **kwargs, we cannot + # reliably determine overlap. Erasing a ParamSpec to Any makes + # P.args/P.kwargs look like *Any/**Any, which appears to accept all + # arguments — but in reality the ParamSpec is constrained to the + # wrapped function's parameters. This leads to false positives where + # we incorrectly conclude that the other overload can never match. + for arg_type in signature.arg_types: + if isinstance(arg_type, ParamSpecType) and arg_type.flavor != 0: # BARE = 0 + return False + # The extra erasure is needed to prevent spurious errors # in situations where an `Any` overload is used as a fallback # for an overload with type variables. The spurious error appears diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index b0808105a385..d105832f1fa9 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2756,3 +2756,19 @@ reveal_type(Sneaky(f8, 1, y='').kwargs) # N: Revealed type is "builtins.dict[bu reveal_type(Sneaky(f9, 1, y=0).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})" 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})" [builtins fixtures/paramspec.pyi] + +[case testOverloadParamSpecNoFalsePositiveCannotMatch] +# Test that overloads using P.args/P.kwargs don't trigger false +# "overload will never be matched" errors when combined with +# overloads using explicit keyword-only parameters. +from typing import Any, overload, ParamSpec, TypeVar, Callable + +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): ... +[builtins fixtures/paramspec.pyi]