From d2ba168a9c2c6197de06393b6658e13257422a8b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 11 Oct 2023 23:17:25 +0100 Subject: [PATCH 1/2] Fix crash on ParamSpec unification --- mypy/expandtype.py | 4 ++-- .../unit/check-parameter-specification.test | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index b233561e19c2..69ef37febc30 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -241,7 +241,7 @@ def visit_param_spec(self, t: ParamSpecType) -> Type: return repl.copy_modified( flavor=t.flavor, prefix=t.prefix.copy_modified( - arg_types=self.expand_types(t.prefix.arg_types + repl.prefix.arg_types), + arg_types=self.expand_types(t.prefix.arg_types) + repl.prefix.arg_types, arg_kinds=t.prefix.arg_kinds + repl.prefix.arg_kinds, arg_names=t.prefix.arg_names + repl.prefix.arg_names, ), @@ -249,7 +249,7 @@ def visit_param_spec(self, t: ParamSpecType) -> Type: elif isinstance(repl, Parameters): assert t.flavor == ParamSpecFlavor.BARE return Parameters( - self.expand_types(t.prefix.arg_types + repl.arg_types), + self.expand_types(t.prefix.arg_types) + repl.arg_types, t.prefix.arg_kinds + repl.arg_kinds, t.prefix.arg_names + repl.arg_names, variables=[*t.prefix.variables, *repl.variables], diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index da831d29dd43..6cccb9911ecb 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1976,3 +1976,22 @@ g(cb, y=0, x='a') # OK g(cb, y='a', x=0) # E: Argument "y" to "g" has incompatible type "str"; expected "int" \ # E: Argument "x" to "g" has incompatible type "int"; expected "str" [builtins fixtures/paramspec.pyi] + +[case testParamSpecNoCrashOnUnification] +import mod +[file mod.pyi] +from typing import Callable, Protocol, TypeVar, overload +from typing_extensions import ParamSpec + +P = ParamSpec("P") +R_co = TypeVar("R_co", covariant=True) +Handler = Callable[P, R_co] + +class HandlerDecorator(Protocol): + def __call__(self, handler: Handler[P, R_co]) -> Handler[P, R_co]: ... + +@overload +def event(event_handler: Handler[P, R_co]) -> Handler[P, R_co]: ... +@overload +def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ... +[builtins fixtures/paramspec.pyi] From ec0098469d9f2cc1a4514e700cdbfb43f2a9e908 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 12 Oct 2023 10:06:33 +0100 Subject: [PATCH 2/2] Fix plain Callable as well --- mypy/expandtype.py | 6 ++++-- mypy/types.py | 10 ---------- .../unit/check-parameter-specification.test | 20 ++++++++++++++++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 69ef37febc30..4acb51e22268 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -333,12 +333,14 @@ def visit_callable_type(self, t: CallableType) -> CallableType: # the replacement is ignored. if isinstance(repl, Parameters): # We need to expand both the types in the prefix and the ParamSpec itself - t = t.expand_param_spec(repl) return t.copy_modified( - arg_types=self.expand_types(t.arg_types), + arg_types=self.expand_types(t.arg_types[:-2]) + repl.arg_types, + arg_kinds=t.arg_kinds[:-2] + repl.arg_kinds, + arg_names=t.arg_names[:-2] + repl.arg_names, ret_type=t.ret_type.accept(self), type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None), imprecise_arg_kinds=(t.imprecise_arg_kinds or repl.imprecise_arg_kinds), + variables=[*repl.variables, *t.variables], ) elif isinstance(repl, ParamSpecType): # We're substituting one ParamSpec for another; this can mean that the prefix diff --git a/mypy/types.py b/mypy/types.py index 34ea96be25ee..09ba68aae88a 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2069,16 +2069,6 @@ def param_spec(self) -> ParamSpecType | None: prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2]) return arg_type.copy_modified(flavor=ParamSpecFlavor.BARE, prefix=prefix) - def expand_param_spec(self, c: Parameters) -> CallableType: - variables = c.variables - return self.copy_modified( - arg_types=self.arg_types[:-2] + c.arg_types, - arg_kinds=self.arg_kinds[:-2] + c.arg_kinds, - arg_names=self.arg_names[:-2] + c.arg_names, - is_ellipsis_args=c.is_ellipsis_args, - variables=[*variables, *self.variables], - ) - def with_unpacked_kwargs(self) -> NormalizedCallableType: if not self.unpack_kwargs: return cast(NormalizedCallableType, self) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 6cccb9911ecb..bb7859070f00 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1977,7 +1977,7 @@ g(cb, y='a', x=0) # E: Argument "y" to "g" has incompatible type "str"; expecte # E: Argument "x" to "g" has incompatible type "int"; expected "str" [builtins fixtures/paramspec.pyi] -[case testParamSpecNoCrashOnUnification] +[case testParamSpecNoCrashOnUnificationAlias] import mod [file mod.pyi] from typing import Callable, Protocol, TypeVar, overload @@ -1995,3 +1995,21 @@ def event(event_handler: Handler[P, R_co]) -> Handler[P, R_co]: ... @overload def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ... [builtins fixtures/paramspec.pyi] + +[case testParamSpecNoCrashOnUnificationCallable] +import mod +[file mod.pyi] +from typing import Callable, Protocol, TypeVar, overload +from typing_extensions import ParamSpec + +P = ParamSpec("P") +R_co = TypeVar("R_co", covariant=True) + +class HandlerDecorator(Protocol): + def __call__(self, handler: Callable[P, R_co]) -> Callable[P, R_co]: ... + +@overload +def event(event_handler: Callable[P, R_co]) -> Callable[P, R_co]: ... +@overload +def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ... +[builtins fixtures/paramspec.pyi]