Skip to content

Commit db77d7b

Browse files
authored
[ty] Add a diagnostic if a TypeVar is used to specialize a ParamSpec, or vice versa (#23738)
1 parent db28490 commit db77d7b

7 files changed

+553
-260
lines changed

crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,6 @@ def valid(
189189
def invalid(
190190
# error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context"
191191
a1: P,
192-
# TODO: this should cause us to emit an error because a `ParamSpec` type argument
193-
# cannot be used to specialize a non-`ParamSpec` type parameter
194-
a2: list[P],
195192
# error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context"
196193
a3: Callable[[P], int],
197194
# error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context"
@@ -437,6 +434,41 @@ both mypy and Pyright allow this and there are usages of this in the wild e.g.,
437434
reveal_type(TypeVarAndParamSpec[int, Any]().attr) # revealed: (...) -> int
438435
```
439436

437+
## `ParamSpec` cannot specialize a `TypeVar`, and vice versa
438+
439+
<!-- snapshot-diagnostics -->
440+
441+
A `ParamSpec` is not a valid type argument for a regular `TypeVar`, and vice versa.
442+
443+
```py
444+
from typing import Generic, Callable, TypeVar, ParamSpec
445+
446+
T = TypeVar("T")
447+
P = ParamSpec("P")
448+
449+
class OnlyTypeVar(Generic[T]):
450+
attr: T
451+
452+
def func(c: Callable[P, None]):
453+
# error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`"
454+
a: OnlyTypeVar[P]
455+
456+
class OnlyParamSpec(Generic[P]):
457+
attr: Callable[P, None]
458+
459+
# This is fine due to the special case whereby `OnlyParamSpec[T]` is interpreted the same as
460+
# `OnlyParamSpec[[T]]`, due to the fact that `OnlyParamSpec` is only generic over a single
461+
# `ParamSpec` and no other type variables.
462+
def func2(c: OnlyParamSpec[T], other: T):
463+
reveal_type(c.attr) # revealed: (T@func2, /) -> None
464+
465+
class ParamSpecAndTypeVar(Generic[P, T]):
466+
attr: Callable[P, T]
467+
468+
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`"
469+
def func3(c: ParamSpecAndTypeVar[T, int], other: T): ...
470+
```
471+
440472
## Specialization when defaults are involved
441473

442474
```toml

crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ def valid[**P](
8181
def invalid[**P](
8282
# error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context"
8383
a1: P,
84-
# TODO: this should cause us to emit an error because a `ParamSpec` type argument
85-
# cannot be used to specialize a non-`ParamSpec` type parameter
86-
a2: list[P],
8784
# error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context"
8885
a3: Callable[[P], int],
8986
# error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context"
@@ -379,6 +376,44 @@ both mypy and Pyright allow this and there are usages of this in the wild e.g.,
379376
reveal_type(TypeVarAndParamSpec[int, Any]().attr) # revealed: (...) -> int
380377
```
381378

379+
## `ParamSpec` cannot specialize a `TypeVar`, and vice versa
380+
381+
<!-- snapshot-diagnostics -->
382+
383+
A `ParamSpec` is not a valid type argument for a regular `TypeVar`, and vice versa.
384+
385+
```py
386+
from typing import Callable
387+
388+
class OnlyTypeVar[T]:
389+
attr: T
390+
391+
class TypeVarAndParamSpec[T, **P]:
392+
attr: Callable[P, T]
393+
394+
def f[**P, T]():
395+
# error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`"
396+
a: OnlyTypeVar[P]
397+
398+
# error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`"
399+
b: TypeVarAndParamSpec[P, [int]]
400+
401+
class OnlyParamSpec[**P]:
402+
attr: Callable[P, None]
403+
404+
# This is fine due to the special case whereby `OnlyParamSpec[T]` is interpreted the same as
405+
# `OnlyParamSpec[[T]]`, due to the fact that `OnlyParamSpec` is only generic over a single
406+
# `ParamSpec` and no other type variables.
407+
def func2[T](c: OnlyParamSpec[T], other: T):
408+
reveal_type(c.attr) # revealed: (T@func2, /) -> None
409+
410+
class ParamSpecAndTypeVar[**P, T]:
411+
attr: Callable[P, T]
412+
413+
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`"
414+
def func3[T](c: ParamSpecAndTypeVar[T, int], other: T): ...
415+
```
416+
382417
## Specialization when defaults are involved
383418

384419
```py

0 commit comments

Comments
 (0)