From d9cc1529532234eaa68f1d3c1b4ae01732caa1f6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 12 Nov 2023 14:45:41 +0000 Subject: [PATCH 1/4] Ignore position if imprecise arguments are matched by name --- mypy/subtypes.py | 11 +++- .../unit/check-parameter-specification.test | 63 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 7e37751b1c15e..77facd7a3fb33 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1651,7 +1651,12 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N continue return False if not are_args_compatible( - left_arg, right_arg, ignore_pos_arg_names, allow_partial_overlap, is_compat + left_arg, + right_arg, + ignore_pos_arg_names, + allow_partial_overlap, + is_compat, + right.imprecise_arg_kinds, ): return False @@ -1735,6 +1740,7 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N and right_by_name != right_by_pos and (right_by_pos.required or right_by_name.required) and strict_concatenate_check + and not right.imprecise_arg_kinds ): return False @@ -1752,6 +1758,7 @@ def are_args_compatible( ignore_pos_arg_names: bool, allow_partial_overlap: bool, is_compat: Callable[[Type, Type], bool], + allow_imprecise_kinds: bool = False, ) -> bool: if left.required and right.required: # If both arguments are required allow_partial_overlap has no effect. @@ -1779,7 +1786,7 @@ def is_different(left_item: object | None, right_item: object | None) -> bool: return False # If right is at a specific position, left must have the same: - if is_different(left.pos, right.pos): + if is_different(left.pos, right.pos) and not allow_imprecise_kinds: return False # If right's argument is optional, left's must also be diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index db8c76fd21e9a..246b51b1d48cf 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1687,9 +1687,18 @@ P = ParamSpec("P") T = TypeVar("T") def apply(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None: ... + def test(x: int) -> int: ... apply(apply, test, x=42) # OK apply(apply, test, 42) # Also OK (but requires some special casing) +apply(apply, test, "bad") # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int], int], str], None]" + +def test2(x: int, y: str) -> None: ... +apply(apply, test2, 42, "yes") +apply(apply, test2, "no", 42) # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int, str], None], str, int], None]" +apply(apply, test2, x=42, y="yes") +apply(apply, test2, y="yes", x=42) +apply(apply, test2, y=42, x="no") # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int, str], None], int, str], None]" [builtins fixtures/paramspec.pyi] [case testParamSpecApplyPosVsNamedOptional] @@ -2086,3 +2095,57 @@ reveal_type(d(b, f1)) # E: Cannot infer type argument 1 of "d" \ # N: Revealed type is "def (*Any, **Any)" reveal_type(d(b, f2)) # N: Revealed type is "def (builtins.int)" [builtins fixtures/paramspec.pyi] + +[case testParamSpecGenericWithNamedArg1] +from typing import Callable, TypeVar +from typing_extensions import ParamSpec + +T_Retval = TypeVar("T_Retval") +P = ParamSpec("P") + +def run(func: Callable[[], T_Retval], *args: object, backend: str = "asyncio") -> T_Retval: + ... +def run_portal(): ... + +def submit( + func: Callable[P, T_Retval], + /, + *args: P.args, + **kwargs: P.kwargs, +) -> T_Retval: ... + +submit( + run, + run_portal, + backend="asyncio", +) +submit( + run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], T_Retval], VarArg(object), DefaultNamedArg(str, 'backend')], T_Retval]"; expected "Callable[[Callable[[], Any], int], Any]" + run_portal, + backend=int(), +) +[builtins fixtures/paramspec.pyi] + +[case testParamSpecGenericWithNamedArg2] +from typing import Callable, TypeVar, Type +from typing_extensions import ParamSpec + +ParamsT = ParamSpec("ParamsT") +T = TypeVar("T") + +def smoke_testable( + *args: ParamsT.args, **kwargs: ParamsT.kwargs +) -> Callable[[Callable[ParamsT, T]], Type[T]]: + ... + +@smoke_testable(name="bob", size=512, flt=0.5) +class SomeClass: + def __init__(self, size: int, name: str, flt: float) -> None: + pass + +# Error message is confusing, but this is a known issue, see #4530. +@smoke_testable(name=42, size="bad", flt=0.5) # E: Argument 1 has incompatible type "Type[OtherClass]"; expected "Callable[[int, str, float], OtherClass]" +class OtherClass: + def __init__(self, size: int, name: str, flt: float) -> None: + pass +[builtins fixtures/paramspec.pyi] From 61c72a45417513b40fadae1cb375a9d7140d25ab Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 12 Nov 2023 14:57:23 +0000 Subject: [PATCH 2/4] Add a reveal_type in test --- test-data/unit/check-parameter-specification.test | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 246b51b1d48cf..615dce2b80da3 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2105,7 +2105,9 @@ P = ParamSpec("P") def run(func: Callable[[], T_Retval], *args: object, backend: str = "asyncio") -> T_Retval: ... -def run_portal(): ... + +class Result: ... +def run_portal() -> Result: ... def submit( func: Callable[P, T_Retval], @@ -2114,13 +2116,13 @@ def submit( **kwargs: P.kwargs, ) -> T_Retval: ... -submit( +reveal_type(submit( # N: Revealed type is "__main__.Result" run, run_portal, backend="asyncio", -) +)) submit( - run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], T_Retval], VarArg(object), DefaultNamedArg(str, 'backend')], T_Retval]"; expected "Callable[[Callable[[], Any], int], Any]" + run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], T_Retval], VarArg(object), DefaultNamedArg(str, 'backend')], T_Retval]"; expected "Callable[[Callable[[], Result], int], Result]" run_portal, backend=int(), ) From 3b2e30b311337d4cc26e127bb5e997cba45abf9d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 12 Nov 2023 20:33:19 +0000 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Alex Waygood --- .../unit/check-parameter-specification.test | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 615dce2b80da3..a8796428ac56c 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2100,21 +2100,13 @@ reveal_type(d(b, f2)) # N: Revealed type is "def (builtins.int)" from typing import Callable, TypeVar from typing_extensions import ParamSpec -T_Retval = TypeVar("T_Retval") +R = TypeVar("R") P = ParamSpec("P") -def run(func: Callable[[], T_Retval], *args: object, backend: str = "asyncio") -> T_Retval: - ... - +def run(func: Callable[[], R], *args: object, backend: str = "asyncio") -> R: ... class Result: ... def run_portal() -> Result: ... - -def submit( - func: Callable[P, T_Retval], - /, - *args: P.args, - **kwargs: P.kwargs, -) -> T_Retval: ... +def submit(func: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R: ... reveal_type(submit( # N: Revealed type is "__main__.Result" run, @@ -2132,12 +2124,10 @@ submit( from typing import Callable, TypeVar, Type from typing_extensions import ParamSpec -ParamsT = ParamSpec("ParamsT") +P= ParamSpec("P") T = TypeVar("T") -def smoke_testable( - *args: ParamsT.args, **kwargs: ParamsT.kwargs -) -> Callable[[Callable[ParamsT, T]], Type[T]]: +def smoke_testable(*args: P.args, **kwargs: P.kwargs) -> Callable[[Callable[P, T]], Type[T]]: ... @smoke_testable(name="bob", size=512, flt=0.5) From 45953309487880ef4cb7bef438131466c947602c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 12 Nov 2023 20:41:16 +0000 Subject: [PATCH 4/4] Small refactoring --- mypy/subtypes.py | 19 ++++++++++++------- .../unit/check-parameter-specification.test | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 77facd7a3fb33..4fd3f8ff98ca8 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1653,10 +1653,10 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N if not are_args_compatible( left_arg, right_arg, - ignore_pos_arg_names, - allow_partial_overlap, is_compat, - right.imprecise_arg_kinds, + ignore_pos_arg_names=ignore_pos_arg_names, + allow_partial_overlap=allow_partial_overlap, + allow_imprecise_kinds=right.imprecise_arg_kinds, ): return False @@ -1681,9 +1681,9 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N if not are_args_compatible( left_by_position, right_by_position, - ignore_pos_arg_names, - allow_partial_overlap, is_compat, + ignore_pos_arg_names=ignore_pos_arg_names, + allow_partial_overlap=allow_partial_overlap, ): return False i += 1 @@ -1716,7 +1716,11 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N continue if not are_args_compatible( - left_by_name, right_by_name, ignore_pos_arg_names, allow_partial_overlap, is_compat + left_by_name, + right_by_name, + is_compat, + ignore_pos_arg_names=ignore_pos_arg_names, + allow_partial_overlap=allow_partial_overlap, ): return False @@ -1755,9 +1759,10 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N def are_args_compatible( left: FormalArgument, right: FormalArgument, + is_compat: Callable[[Type, Type], bool], + *, ignore_pos_arg_names: bool, allow_partial_overlap: bool, - is_compat: Callable[[Type, Type], bool], allow_imprecise_kinds: bool = False, ) -> bool: if left.required and right.required: diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index a8796428ac56c..97105603dd510 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2114,7 +2114,7 @@ reveal_type(submit( # N: Revealed type is "__main__.Result" backend="asyncio", )) submit( - run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], T_Retval], VarArg(object), DefaultNamedArg(str, 'backend')], T_Retval]"; expected "Callable[[Callable[[], Result], int], Result]" + run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], R], VarArg(object), DefaultNamedArg(str, 'backend')], R]"; expected "Callable[[Callable[[], Result], int], Result]" run_portal, backend=int(), )