Skip to content

Commit 72605dc

Browse files
authored
Fix crash on ParamSpec unification (#16251)
Fixes #16245 Fixes #16248 Unfortunately I was a bit reckless with parentheses, but in my defense `unify_generic_callable()` is kind of broken for long time, as it can return "solutions" like ```{1: T`1}```. We need a more principled approach there (IIRC there is already an issue about this in the scope of `--new-type-inference`). (The fix is quite trivial so I am not going to wait for review too long to save time, unless there will be some issues in `mypy_primer` etc.)
1 parent 2c1009e commit 72605dc

File tree

3 files changed

+43
-14
lines changed

3 files changed

+43
-14
lines changed

mypy/expandtype.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,15 @@ def visit_param_spec(self, t: ParamSpecType) -> Type:
241241
return repl.copy_modified(
242242
flavor=t.flavor,
243243
prefix=t.prefix.copy_modified(
244-
arg_types=self.expand_types(t.prefix.arg_types + repl.prefix.arg_types),
244+
arg_types=self.expand_types(t.prefix.arg_types) + repl.prefix.arg_types,
245245
arg_kinds=t.prefix.arg_kinds + repl.prefix.arg_kinds,
246246
arg_names=t.prefix.arg_names + repl.prefix.arg_names,
247247
),
248248
)
249249
elif isinstance(repl, Parameters):
250250
assert t.flavor == ParamSpecFlavor.BARE
251251
return Parameters(
252-
self.expand_types(t.prefix.arg_types + repl.arg_types),
252+
self.expand_types(t.prefix.arg_types) + repl.arg_types,
253253
t.prefix.arg_kinds + repl.arg_kinds,
254254
t.prefix.arg_names + repl.arg_names,
255255
variables=[*t.prefix.variables, *repl.variables],
@@ -333,12 +333,14 @@ def visit_callable_type(self, t: CallableType) -> CallableType:
333333
# the replacement is ignored.
334334
if isinstance(repl, Parameters):
335335
# We need to expand both the types in the prefix and the ParamSpec itself
336-
t = t.expand_param_spec(repl)
337336
return t.copy_modified(
338-
arg_types=self.expand_types(t.arg_types),
337+
arg_types=self.expand_types(t.arg_types[:-2]) + repl.arg_types,
338+
arg_kinds=t.arg_kinds[:-2] + repl.arg_kinds,
339+
arg_names=t.arg_names[:-2] + repl.arg_names,
339340
ret_type=t.ret_type.accept(self),
340341
type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None),
341342
imprecise_arg_kinds=(t.imprecise_arg_kinds or repl.imprecise_arg_kinds),
343+
variables=[*repl.variables, *t.variables],
342344
)
343345
elif isinstance(repl, ParamSpecType):
344346
# We're substituting one ParamSpec for another; this can mean that the prefix

mypy/types.py

-10
Original file line numberDiff line numberDiff line change
@@ -2069,16 +2069,6 @@ def param_spec(self) -> ParamSpecType | None:
20692069
prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2])
20702070
return arg_type.copy_modified(flavor=ParamSpecFlavor.BARE, prefix=prefix)
20712071

2072-
def expand_param_spec(self, c: Parameters) -> CallableType:
2073-
variables = c.variables
2074-
return self.copy_modified(
2075-
arg_types=self.arg_types[:-2] + c.arg_types,
2076-
arg_kinds=self.arg_kinds[:-2] + c.arg_kinds,
2077-
arg_names=self.arg_names[:-2] + c.arg_names,
2078-
is_ellipsis_args=c.is_ellipsis_args,
2079-
variables=[*variables, *self.variables],
2080-
)
2081-
20822072
def with_unpacked_kwargs(self) -> NormalizedCallableType:
20832073
if not self.unpack_kwargs:
20842074
return cast(NormalizedCallableType, self)

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

+37
Original file line numberDiff line numberDiff line change
@@ -1976,3 +1976,40 @@ g(cb, y=0, x='a') # OK
19761976
g(cb, y='a', x=0) # E: Argument "y" to "g" has incompatible type "str"; expected "int" \
19771977
# E: Argument "x" to "g" has incompatible type "int"; expected "str"
19781978
[builtins fixtures/paramspec.pyi]
1979+
1980+
[case testParamSpecNoCrashOnUnificationAlias]
1981+
import mod
1982+
[file mod.pyi]
1983+
from typing import Callable, Protocol, TypeVar, overload
1984+
from typing_extensions import ParamSpec
1985+
1986+
P = ParamSpec("P")
1987+
R_co = TypeVar("R_co", covariant=True)
1988+
Handler = Callable[P, R_co]
1989+
1990+
class HandlerDecorator(Protocol):
1991+
def __call__(self, handler: Handler[P, R_co]) -> Handler[P, R_co]: ...
1992+
1993+
@overload
1994+
def event(event_handler: Handler[P, R_co]) -> Handler[P, R_co]: ...
1995+
@overload
1996+
def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ...
1997+
[builtins fixtures/paramspec.pyi]
1998+
1999+
[case testParamSpecNoCrashOnUnificationCallable]
2000+
import mod
2001+
[file mod.pyi]
2002+
from typing import Callable, Protocol, TypeVar, overload
2003+
from typing_extensions import ParamSpec
2004+
2005+
P = ParamSpec("P")
2006+
R_co = TypeVar("R_co", covariant=True)
2007+
2008+
class HandlerDecorator(Protocol):
2009+
def __call__(self, handler: Callable[P, R_co]) -> Callable[P, R_co]: ...
2010+
2011+
@overload
2012+
def event(event_handler: Callable[P, R_co]) -> Callable[P, R_co]: ...
2013+
@overload
2014+
def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ...
2015+
[builtins fixtures/paramspec.pyi]

0 commit comments

Comments
 (0)