From 20b37df8a4777ef4f201afee36fcd7c812237a39 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Thu, 24 Oct 2024 18:48:41 +0200 Subject: [PATCH] Do not prioritize ParamSpec signatures during overload resolution --- mypy/checkexpr.py | 6 ++-- .../unit/check-parameter-specification.test | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 22f4b05b7ad4..d63cf6e782c7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2781,7 +2781,7 @@ def plausible_overload_call_targets( ) -> list[CallableType]: """Returns all overload call targets that having matching argument counts. - If the given args contains a star-arg (*arg or **kwarg argument, including + If the given args contains a star-arg (*arg or **kwarg argument, except for ParamSpec), this method will ensure all star-arg overloads appear at the start of the list, instead of their usual location. @@ -2816,7 +2816,9 @@ def has_shape(typ: Type) -> bool: # ParamSpec can be expanded in a lot of different ways. We may try # to expand it here instead, but picking an impossible overload # is safe: it will be filtered out later. - star_matches.append(typ) + # Unlike other var-args signatures, ParamSpec produces essentially + # a fixed signature, so there's no need to push them to the top. + matches.append(typ) elif self.check_argument_count( typ, arg_types, arg_kinds, arg_names, formal_to_actual, None ): diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 38fb62fe78e0..f499bac45102 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2303,3 +2303,38 @@ reveal_type(capture(fn)) # N: Revealed type is "Union[builtins.str, builtins.in reveal_type(capture(err)) # N: Revealed type is "builtins.int" [builtins fixtures/paramspec.pyi] + +[case testRunParamSpecOverlappingOverloadsOrder] +from typing import Any, Callable, overload +from typing_extensions import ParamSpec + +P = ParamSpec("P") + +class Base: + pass +class Child(Base): + def __call__(self) -> str: ... +class NotChild: + def __call__(self) -> str: ... + +@overload +def handle(func: Base) -> int: ... +@overload +def handle(func: Callable[P, str], *args: P.args, **kwargs: P.kwargs) -> str: ... +def handle(func: Any, *args: Any, **kwargs: Any) -> Any: + return func(*args, **kwargs) + +@overload +def handle_reversed(func: Callable[P, str], *args: P.args, **kwargs: P.kwargs) -> str: ... +@overload +def handle_reversed(func: Base) -> int: ... +def handle_reversed(func: Any, *args: Any, **kwargs: Any) -> Any: + return func(*args, **kwargs) + +reveal_type(handle(Child())) # N: Revealed type is "builtins.int" +reveal_type(handle(NotChild())) # N: Revealed type is "builtins.str" + +reveal_type(handle_reversed(Child())) # N: Revealed type is "builtins.str" +reveal_type(handle_reversed(NotChild())) # N: Revealed type is "builtins.str" + +[builtins fixtures/paramspec.pyi]