Skip to content

Commit eb81e63

Browse files
ilevkivskyihauntsaninja
authored andcommittedOct 17, 2023
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 45f7a12 commit eb81e63

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
@@ -236,15 +236,15 @@ def visit_param_spec(self, t: ParamSpecType) -> Type:
236236
return repl.copy_modified(
237237
flavor=t.flavor,
238238
prefix=t.prefix.copy_modified(
239-
arg_types=self.expand_types(t.prefix.arg_types + repl.prefix.arg_types),
239+
arg_types=self.expand_types(t.prefix.arg_types) + repl.prefix.arg_types,
240240
arg_kinds=t.prefix.arg_kinds + repl.prefix.arg_kinds,
241241
arg_names=t.prefix.arg_names + repl.prefix.arg_names,
242242
),
243243
)
244244
elif isinstance(repl, Parameters):
245245
assert t.flavor == ParamSpecFlavor.BARE
246246
return Parameters(
247-
self.expand_types(t.prefix.arg_types + repl.arg_types),
247+
self.expand_types(t.prefix.arg_types) + repl.arg_types,
248248
t.prefix.arg_kinds + repl.arg_kinds,
249249
t.prefix.arg_names + repl.arg_names,
250250
variables=[*t.prefix.variables, *repl.variables],
@@ -327,12 +327,14 @@ def visit_callable_type(self, t: CallableType) -> CallableType:
327327
# the replacement is ignored.
328328
if isinstance(repl, Parameters):
329329
# We need to expand both the types in the prefix and the ParamSpec itself
330-
t = t.expand_param_spec(repl)
331330
return t.copy_modified(
332-
arg_types=self.expand_types(t.arg_types),
331+
arg_types=self.expand_types(t.arg_types[:-2]) + repl.arg_types,
332+
arg_kinds=t.arg_kinds[:-2] + repl.arg_kinds,
333+
arg_names=t.arg_names[:-2] + repl.arg_names,
333334
ret_type=t.ret_type.accept(self),
334335
type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None),
335336
imprecise_arg_kinds=(t.imprecise_arg_kinds or repl.imprecise_arg_kinds),
337+
variables=[*repl.variables, *t.variables],
336338
)
337339
elif isinstance(repl, ParamSpecType):
338340
# 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
@@ -2068,16 +2068,6 @@ def param_spec(self) -> ParamSpecType | None:
20682068
prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2])
20692069
return arg_type.copy_modified(flavor=ParamSpecFlavor.BARE, prefix=prefix)
20702070

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

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

+37
Original file line numberDiff line numberDiff line change
@@ -1839,3 +1839,40 @@ g(cb, y=0, x='a') # OK
18391839
g(cb, y='a', x=0) # E: Argument "y" to "g" has incompatible type "str"; expected "int" \
18401840
# E: Argument "x" to "g" has incompatible type "int"; expected "str"
18411841
[builtins fixtures/paramspec.pyi]
1842+
1843+
[case testParamSpecNoCrashOnUnificationAlias]
1844+
import mod
1845+
[file mod.pyi]
1846+
from typing import Callable, Protocol, TypeVar, overload
1847+
from typing_extensions import ParamSpec
1848+
1849+
P = ParamSpec("P")
1850+
R_co = TypeVar("R_co", covariant=True)
1851+
Handler = Callable[P, R_co]
1852+
1853+
class HandlerDecorator(Protocol):
1854+
def __call__(self, handler: Handler[P, R_co]) -> Handler[P, R_co]: ...
1855+
1856+
@overload
1857+
def event(event_handler: Handler[P, R_co]) -> Handler[P, R_co]: ...
1858+
@overload
1859+
def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ...
1860+
[builtins fixtures/paramspec.pyi]
1861+
1862+
[case testParamSpecNoCrashOnUnificationCallable]
1863+
import mod
1864+
[file mod.pyi]
1865+
from typing import Callable, Protocol, TypeVar, overload
1866+
from typing_extensions import ParamSpec
1867+
1868+
P = ParamSpec("P")
1869+
R_co = TypeVar("R_co", covariant=True)
1870+
1871+
class HandlerDecorator(Protocol):
1872+
def __call__(self, handler: Callable[P, R_co]) -> Callable[P, R_co]: ...
1873+
1874+
@overload
1875+
def event(event_handler: Callable[P, R_co]) -> Callable[P, R_co]: ...
1876+
@overload
1877+
def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ...
1878+
[builtins fixtures/paramspec.pyi]

0 commit comments

Comments
 (0)
Please sign in to comment.