Skip to content

Commit

Permalink
Fix crash when passing too many type arguments to generic base class …
Browse files Browse the repository at this point in the history
…accepting single ParamSpec (python#17770)

Fixes python#17765

The offender for this crash appears to be this snippet:


https://github.com/python/mypy/blob/72c413d2352da5ce1433ef241faca8f40fa1fe27/mypy/semanal.py#L5905-L5910

This branch triggers when applying type args to a type that is generic
with respect to a single `ParamSpec`. It allows double brackets to be
omitted when providing a parameter specification by wrapping all of the
provided type arguments into a single parameter specification argument
(i.e. equating `Foo[int, int]` to `Foo[[int, int]]`). This wrapping
occurs *unless*:
* there is only a single type argument, and it resolves to `Any` (e.g.
`Foo[Any]`)
* **there is only a single type argument**, and it's a bracketed
parameter specification or a `ParamSpec` (e.g. `Foo[[int, int]]`)

The problem occurs when multiple type arguments provided and at least
one of them is a bracketed parameter specification, as in `Foo[[int,
int], str]`.

Per the rules above, since there is more than 1 type argument, mypy
attempts to wrap the arguments into a single parameter specification.
This results in the attempted creation of a `Parameters` instance that
contains another `Parameters` instance, which triggers this assert
inside `Parameters.__init__`:


https://github.com/python/mypy/blob/72c413d2352da5ce1433ef241faca8f40fa1fe27/mypy/types.py#L1634

I think a reasonable solution is to forgo wrapping the type arguments
into a single `Parameters` if **any** of the provided type arguments are
a `Parameters`/`ParamSpecType`. That is, don't transform `Foo[A1, A2,
...]` to `Foo[[A1, A2, ...]]` if any of `A1, A2, ...` are a parameter
specification.

This change brings the crash case inline with mypy's current behavior
for a similar case:
```python
# Current behavior
P = ParamSpec("P")
class C(Generic[P]): ...
c: C[int, [int, str], str]  # E: Nested parameter specifications are not allowed
```

Before this change:
```python
P = ParamSpec("P")
class C(Generic[P]): ...
class D(C[int, [int, str], str]): ... # !!! CRASH !!!
```
After this change:
```python
P = ParamSpec("P")
class C(Generic[P]): ...
class D(C[int, [int, str], str]): ... # E: Nested parameter specifications are not allowed
````
  • Loading branch information
brianschubert authored Sep 21, 2024
1 parent 94109aa commit 9ffb9dd
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 3 deletions.
5 changes: 2 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5926,9 +5926,8 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None:

if has_param_spec and num_args == 1 and types:
first_arg = get_proper_type(types[0])
if not (
len(types) == 1 and isinstance(first_arg, (Parameters, ParamSpecType, AnyType))
):
single_any = len(types) == 1 and isinstance(first_arg, AnyType)
if not (single_any or any(isinstance(t, (Parameters, ParamSpecType)) for t in types)):
types = [Parameters(types, [ARG_POS] * len(types), [None] * len(types))]

return types
Expand Down
9 changes: 9 additions & 0 deletions test-data/unit/check-parameter-specification.test
Original file line number Diff line number Diff line change
Expand Up @@ -1836,6 +1836,15 @@ c: C[int, [int, str], str] # E: Nested parameter specifications are not allowed
reveal_type(c) # N: Revealed type is "__main__.C[Any]"
[builtins fixtures/paramspec.pyi]

[case testParamSpecInheritNoCrashOnNested]
from typing import Generic
from typing_extensions import ParamSpec

P = ParamSpec("P")
class C(Generic[P]): ...
class D(C[int, [int, str], str]): ... # E: Nested parameter specifications are not allowed
[builtins fixtures/paramspec.pyi]

[case testParamSpecConcatenateSelfType]
from typing import Callable
from typing_extensions import ParamSpec, Concatenate
Expand Down

0 comments on commit 9ffb9dd

Please sign in to comment.