diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md index 72aec3936d599..867d77da3bd15 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md @@ -189,9 +189,6 @@ def valid( def invalid( # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" a1: P, - # TODO: this should cause us to emit an error because a `ParamSpec` type argument - # cannot be used to specialize a non-`ParamSpec` type parameter - a2: list[P], # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" a3: Callable[[P], int], # 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., reveal_type(TypeVarAndParamSpec[int, Any]().attr) # revealed: (...) -> int ``` +## `ParamSpec` cannot specialize a `TypeVar`, and vice versa + + + +A `ParamSpec` is not a valid type argument for a regular `TypeVar`, and vice versa. + +```py +from typing import Generic, Callable, TypeVar, ParamSpec + +T = TypeVar("T") +P = ParamSpec("P") + +class OnlyTypeVar(Generic[T]): + attr: T + +def func(c: Callable[P, None]): + # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" + a: OnlyTypeVar[P] + +class OnlyParamSpec(Generic[P]): + attr: Callable[P, None] + +# This is fine due to the special case whereby `OnlyParamSpec[T]` is interpreted the same as +# `OnlyParamSpec[[T]]`, due to the fact that `OnlyParamSpec` is only generic over a single +# `ParamSpec` and no other type variables. +def func2(c: OnlyParamSpec[T], other: T): + reveal_type(c.attr) # revealed: (T@func2, /) -> None + +class ParamSpecAndTypeVar(Generic[P, T]): + attr: Callable[P, T] + +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`" +def func3(c: ParamSpecAndTypeVar[T, int], other: T): ... +``` + ## Specialization when defaults are involved ```toml diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md index 4c6acdb15776a..a97eb8bf3ecd3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md @@ -81,9 +81,6 @@ def valid[**P]( def invalid[**P]( # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" a1: P, - # TODO: this should cause us to emit an error because a `ParamSpec` type argument - # cannot be used to specialize a non-`ParamSpec` type parameter - a2: list[P], # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" a3: Callable[[P], int], # 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., reveal_type(TypeVarAndParamSpec[int, Any]().attr) # revealed: (...) -> int ``` +## `ParamSpec` cannot specialize a `TypeVar`, and vice versa + + + +A `ParamSpec` is not a valid type argument for a regular `TypeVar`, and vice versa. + +```py +from typing import Callable + +class OnlyTypeVar[T]: + attr: T + +class TypeVarAndParamSpec[T, **P]: + attr: Callable[P, T] + +def f[**P, T](): + # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" + a: OnlyTypeVar[P] + + # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" + b: TypeVarAndParamSpec[P, [int]] + +class OnlyParamSpec[**P]: + attr: Callable[P, None] + +# This is fine due to the special case whereby `OnlyParamSpec[T]` is interpreted the same as +# `OnlyParamSpec[[T]]`, due to the fact that `OnlyParamSpec` is only generic over a single +# `ParamSpec` and no other type variables. +def func2[T](c: OnlyParamSpec[T], other: T): + reveal_type(c.attr) # revealed: (T@func2, /) -> None + +class ParamSpecAndTypeVar[**P, T]: + attr: Callable[P, T] + +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`" +def func3[T](c: ParamSpecAndTypeVar[T, int], other: T): ... +``` + ## Specialization when defaults are involved ```py diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_Legacy_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(648be2a43987ffd8).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_Legacy_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(648be2a43987ffd8).snap" index 0a7cda0a747b5..3ff6516f4a927 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_Legacy_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(648be2a43987ffd8).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_Legacy_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(648be2a43987ffd8).snap" @@ -44,52 +44,49 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspe 21 | def invalid( 22 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" 23 | a1: P, -24 | # TODO: this should cause us to emit an error because a `ParamSpec` type argument -25 | # cannot be used to specialize a non-`ParamSpec` type parameter -26 | a2: list[P], -27 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -28 | a3: Callable[[P], int], -29 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -30 | a4: Callable[..., P], -31 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -32 | a5: Callable[Concatenate[P, ...], int], -33 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -34 | a6: P | int, -35 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -36 | a7: Union[P, int], -37 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -38 | a8: Optional[P], -39 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -40 | a9: Annotated[P, "metadata"], -41 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" -42 | a10: Callable["[int, str]", str], -43 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" -44 | a11: Callable["...", int], -45 | ) -> None: ... -46 | -47 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -48 | def invalid_return() -> P: -49 | raise NotImplementedError -50 | -51 | def invalid_variable_annotation(y: Any) -> None: -52 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -53 | x: P = y -54 | -55 | def invalid_with_qualifier(y: Any) -> None: -56 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -57 | x: Final[P] = y -58 | -59 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -60 | def invalid_stringified_return() -> "P": -61 | raise NotImplementedError -62 | -63 | def invalid_stringified_annotation( -64 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -65 | a: "P", -66 | ) -> None: ... -67 | def invalid_stringified_variable_annotation(y: Any) -> None: -68 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -69 | x: "P" = y +24 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +25 | a3: Callable[[P], int], +26 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +27 | a4: Callable[..., P], +28 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +29 | a5: Callable[Concatenate[P, ...], int], +30 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +31 | a6: P | int, +32 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +33 | a7: Union[P, int], +34 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +35 | a8: Optional[P], +36 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +37 | a9: Annotated[P, "metadata"], +38 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" +39 | a10: Callable["[int, str]", str], +40 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" +41 | a11: Callable["...", int], +42 | ) -> None: ... +43 | +44 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +45 | def invalid_return() -> P: +46 | raise NotImplementedError +47 | +48 | def invalid_variable_annotation(y: Any) -> None: +49 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +50 | x: P = y +51 | +52 | def invalid_with_qualifier(y: Any) -> None: +53 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +54 | x: Final[P] = y +55 | +56 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +57 | def invalid_stringified_return() -> "P": +58 | raise NotImplementedError +59 | +60 | def invalid_stringified_annotation( +61 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +62 | a: "P", +63 | ) -> None: ... +64 | def invalid_stringified_variable_annotation(y: Any) -> None: +65 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +66 | x: "P" = y ``` # Diagnostics @@ -102,8 +99,8 @@ error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a t 22 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" 23 | a1: P, | ^ -24 | # TODO: this should cause us to emit an error because a `ParamSpec` type argument -25 | # cannot be used to specialize a non-`ParamSpec` type parameter +24 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +25 | a3: Callable[[P], int], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -117,14 +114,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:28:19 + --> src/main.py:25:19 | -26 | a2: list[P], -27 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -28 | a3: Callable[[P], int], +23 | a1: P, +24 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +25 | a3: Callable[[P], int], | ^ -29 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -30 | a4: Callable[..., P], +26 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +27 | a4: Callable[..., P], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -138,14 +135,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:30:23 + --> src/main.py:27:23 | -28 | a3: Callable[[P], int], -29 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -30 | a4: Callable[..., P], +25 | a3: Callable[[P], int], +26 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +27 | a4: Callable[..., P], | ^ -31 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -32 | a5: Callable[Concatenate[P, ...], int], +28 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +29 | a5: Callable[Concatenate[P, ...], int], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -159,14 +156,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:32:30 + --> src/main.py:29:30 | -30 | a4: Callable[..., P], -31 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -32 | a5: Callable[Concatenate[P, ...], int], +27 | a4: Callable[..., P], +28 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +29 | a5: Callable[Concatenate[P, ...], int], | ^ -33 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -34 | a6: P | int, +30 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +31 | a6: P | int, | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -180,14 +177,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:34:9 + --> src/main.py:31:9 | -32 | a5: Callable[Concatenate[P, ...], int], -33 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -34 | a6: P | int, +29 | a5: Callable[Concatenate[P, ...], int], +30 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +31 | a6: P | int, | ^ -35 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -36 | a7: Union[P, int], +32 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +33 | a7: Union[P, int], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -201,14 +198,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:36:15 + --> src/main.py:33:15 | -34 | a6: P | int, -35 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -36 | a7: Union[P, int], +31 | a6: P | int, +32 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +33 | a7: Union[P, int], | ^ -37 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -38 | a8: Optional[P], +34 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +35 | a8: Optional[P], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -222,14 +219,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:38:18 + --> src/main.py:35:18 | -36 | a7: Union[P, int], -37 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -38 | a8: Optional[P], +33 | a7: Union[P, int], +34 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +35 | a8: Optional[P], | ^ -39 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -40 | a9: Annotated[P, "metadata"], +36 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +37 | a9: Annotated[P, "metadata"], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -243,14 +240,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:40:19 + --> src/main.py:37:19 | -38 | a8: Optional[P], -39 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -40 | a9: Annotated[P, "metadata"], +35 | a8: Optional[P], +36 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +37 | a9: Annotated[P, "metadata"], | ^ -41 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" -42 | a10: Callable["[int, str]", str], +38 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" +39 | a10: Callable["[int, str]", str], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -264,14 +261,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...` - --> src/main.py:42:19 + --> src/main.py:39:19 | -40 | a9: Annotated[P, "metadata"], -41 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" -42 | a10: Callable["[int, str]", str], +37 | a9: Annotated[P, "metadata"], +38 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" +39 | a10: Callable["[int, str]", str], | ^^^^^^^^^^^^ -43 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" -44 | a11: Callable["...", int], +40 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" +41 | a11: Callable["...", int], | info: See the following page for a reference on valid type expressions: info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions @@ -281,13 +278,13 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...` - --> src/main.py:44:19 + --> src/main.py:41:19 | -42 | a10: Callable["[int, str]", str], -43 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" -44 | a11: Callable["...", int], +39 | a10: Callable["[int, str]", str], +40 | # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" +41 | a11: Callable["...", int], | ^^^^^ -45 | ) -> None: ... +42 | ) -> None: ... | info: See the following page for a reference on valid type expressions: info: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions @@ -297,12 +294,12 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:48:25 + --> src/main.py:45:25 | -47 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -48 | def invalid_return() -> P: +44 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +45 | def invalid_return() -> P: | ^ -49 | raise NotImplementedError +46 | raise NotImplementedError | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -316,14 +313,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:53:8 + --> src/main.py:50:8 | -51 | def invalid_variable_annotation(y: Any) -> None: -52 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -53 | x: P = y +48 | def invalid_variable_annotation(y: Any) -> None: +49 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +50 | x: P = y | ^ -54 | -55 | def invalid_with_qualifier(y: Any) -> None: +51 | +52 | def invalid_with_qualifier(y: Any) -> None: | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -337,14 +334,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:57:14 + --> src/main.py:54:14 | -55 | def invalid_with_qualifier(y: Any) -> None: -56 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -57 | x: Final[P] = y +52 | def invalid_with_qualifier(y: Any) -> None: +53 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +54 | x: Final[P] = y | ^ -58 | -59 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +55 | +56 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -358,12 +355,12 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:60:38 + --> src/main.py:57:38 | -59 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -60 | def invalid_stringified_return() -> "P": +56 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +57 | def invalid_stringified_return() -> "P": | ^ -61 | raise NotImplementedError +58 | raise NotImplementedError | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -377,14 +374,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:65:9 + --> src/main.py:62:9 | -63 | def invalid_stringified_annotation( -64 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -65 | a: "P", +60 | def invalid_stringified_annotation( +61 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +62 | a: "P", | ^ -66 | ) -> None: ... -67 | def invalid_stringified_variable_annotation(y: Any) -> None: +63 | ) -> None: ... +64 | def invalid_stringified_variable_annotation(y: Any) -> None: | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -398,11 +395,11 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/main.py:69:9 + --> src/main.py:66:9 | -67 | def invalid_stringified_variable_annotation(y: Any) -> None: -68 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -69 | x: "P" = y +64 | def invalid_stringified_variable_annotation(y: Any) -> None: +65 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +66 | x: "P" = y | ^ | info: A bare ParamSpec is only valid: diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_Legacy_`ParamSpec`_-_`ParamSpec`_cannot_s\342\200\246_(c9dbdc7b13b704a4).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_Legacy_`ParamSpec`_-_`ParamSpec`_cannot_s\342\200\246_(c9dbdc7b13b704a4).snap" new file mode 100644 index 0000000000000..81ce304ed4947 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_Legacy_`ParamSpec`_-_`ParamSpec`_cannot_s\342\200\246_(c9dbdc7b13b704a4).snap" @@ -0,0 +1,82 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- + +--- +mdtest name: paramspec.md - Legacy `ParamSpec` - `ParamSpec` cannot specialize a `TypeVar`, and vice versa +mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import Generic, Callable, TypeVar, ParamSpec + 2 | + 3 | T = TypeVar("T") + 4 | P = ParamSpec("P") + 5 | + 6 | class OnlyTypeVar(Generic[T]): + 7 | attr: T + 8 | + 9 | def func(c: Callable[P, None]): +10 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" +11 | a: OnlyTypeVar[P] +12 | +13 | class OnlyParamSpec(Generic[P]): +14 | attr: Callable[P, None] +15 | +16 | # This is fine due to the special case whereby `OnlyParamSpec[T]` is interpreted the same as +17 | # `OnlyParamSpec[[T]]`, due to the fact that `OnlyParamSpec` is only generic over a single +18 | # `ParamSpec` and no other type variables. +19 | def func2(c: OnlyParamSpec[T], other: T): +20 | reveal_type(c.attr) # revealed: (T@func2, /) -> None +21 | +22 | class ParamSpecAndTypeVar(Generic[P, T]): +23 | attr: Callable[P, T] +24 | +25 | # error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`" +26 | def func3(c: ParamSpecAndTypeVar[T, int], other: T): ... +``` + +# Diagnostics + +``` +error[invalid-type-arguments]: ParamSpec `P` cannot be used to specialize type variable `T` + --> src/mdtest_snippet.py:11:20 + | + 9 | def func(c: Callable[P, None]): +10 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" +11 | a: OnlyTypeVar[P] + | ^ +12 | +13 | class OnlyParamSpec(Generic[P]): + | + ::: src/mdtest_snippet.py:3:1 + | + 1 | from typing import Generic, Callable, TypeVar, ParamSpec + 2 | + 3 | T = TypeVar("T") + | - Type variable `T` defined here + 4 | P = ParamSpec("P") + | - ParamSpec `P` defined here + 5 | + 6 | class OnlyTypeVar(Generic[T]): + | +info: rule `invalid-type-arguments` is enabled by default + +``` + +``` +error[invalid-type-arguments]: Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...` + --> src/mdtest_snippet.py:26:34 + | +25 | # error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`" +26 | def func3(c: ParamSpecAndTypeVar[T, int], other: T): ... + | ^ + | +info: rule `invalid-type-arguments` is enabled by default + +``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_PEP_695_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(327594c6dacd8ad).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_PEP_695_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(327594c6dacd8ad).snap" index c5aa764b966ba..69a918e09fcde 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_PEP_695_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(327594c6dacd8ad).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_PEP_695_`ParamSpec`_-_Validating_`ParamSpe\342\200\246_(327594c6dacd8ad).snap" @@ -24,51 +24,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspe 9 | def invalid[**P]( 10 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" 11 | a1: P, -12 | # TODO: this should cause us to emit an error because a `ParamSpec` type argument -13 | # cannot be used to specialize a non-`ParamSpec` type parameter -14 | a2: list[P], -15 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -16 | a3: Callable[[P], int], -17 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -18 | a4: Callable[..., P], -19 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -20 | a5: Callable[Concatenate[P, ...], int], -21 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -22 | a6: P | int, -23 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -24 | a7: Union[P, int], -25 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -26 | a8: Optional[P], -27 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -28 | a9: Annotated[P, "metadata"], -29 | ) -> None: ... -30 | -31 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -32 | def invalid_return[**P]() -> P: -33 | raise NotImplementedError +12 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +13 | a3: Callable[[P], int], +14 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +15 | a4: Callable[..., P], +16 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +17 | a5: Callable[Concatenate[P, ...], int], +18 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +19 | a6: P | int, +20 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +21 | a7: Union[P, int], +22 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +23 | a8: Optional[P], +24 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +25 | a9: Annotated[P, "metadata"], +26 | ) -> None: ... +27 | +28 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +29 | def invalid_return[**P]() -> P: +30 | raise NotImplementedError +31 | +32 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +33 | type Alias[**P] = P 34 | -35 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -36 | type Alias[**P] = P -37 | -38 | def invalid_variable_annotation[**P](y: Any) -> None: -39 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -40 | x: P = y -41 | -42 | def invalid_with_qualifier[**P](y: Any) -> None: -43 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -44 | x: Final[P] = y -45 | -46 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -47 | def invalid_stringified_return[**P]() -> "P": -48 | raise NotImplementedError -49 | -50 | def invalid_stringified_annotation[**P]( -51 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -52 | a: "P", -53 | ) -> None: ... -54 | def invalid_stringified_variable_annotation[**P](y: Any) -> None: -55 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -56 | x: "P" = y +35 | def invalid_variable_annotation[**P](y: Any) -> None: +36 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +37 | x: P = y +38 | +39 | def invalid_with_qualifier[**P](y: Any) -> None: +40 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +41 | x: Final[P] = y +42 | +43 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +44 | def invalid_stringified_return[**P]() -> "P": +45 | raise NotImplementedError +46 | +47 | def invalid_stringified_annotation[**P]( +48 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +49 | a: "P", +50 | ) -> None: ... +51 | def invalid_stringified_variable_annotation[**P](y: Any) -> None: +52 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +53 | x: "P" = y ``` # Diagnostics @@ -81,8 +78,8 @@ error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a t 10 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" 11 | a1: P, | ^ -12 | # TODO: this should cause us to emit an error because a `ParamSpec` type argument -13 | # cannot be used to specialize a non-`ParamSpec` type parameter +12 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +13 | a3: Callable[[P], int], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -96,14 +93,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:16:19 + --> src/mdtest_snippet.py:13:19 | -14 | a2: list[P], -15 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -16 | a3: Callable[[P], int], +11 | a1: P, +12 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +13 | a3: Callable[[P], int], | ^ -17 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -18 | a4: Callable[..., P], +14 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +15 | a4: Callable[..., P], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -117,14 +114,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:18:23 + --> src/mdtest_snippet.py:15:23 | -16 | a3: Callable[[P], int], -17 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -18 | a4: Callable[..., P], +13 | a3: Callable[[P], int], +14 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +15 | a4: Callable[..., P], | ^ -19 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -20 | a5: Callable[Concatenate[P, ...], int], +16 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +17 | a5: Callable[Concatenate[P, ...], int], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -138,14 +135,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:20:30 + --> src/mdtest_snippet.py:17:30 | -18 | a4: Callable[..., P], -19 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -20 | a5: Callable[Concatenate[P, ...], int], +15 | a4: Callable[..., P], +16 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +17 | a5: Callable[Concatenate[P, ...], int], | ^ -21 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -22 | a6: P | int, +18 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +19 | a6: P | int, | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -159,14 +156,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:22:9 + --> src/mdtest_snippet.py:19:9 | -20 | a5: Callable[Concatenate[P, ...], int], -21 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -22 | a6: P | int, +17 | a5: Callable[Concatenate[P, ...], int], +18 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +19 | a6: P | int, | ^ -23 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -24 | a7: Union[P, int], +20 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +21 | a7: Union[P, int], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -180,14 +177,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:24:15 + --> src/mdtest_snippet.py:21:15 | -22 | a6: P | int, -23 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -24 | a7: Union[P, int], +19 | a6: P | int, +20 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +21 | a7: Union[P, int], | ^ -25 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -26 | a8: Optional[P], +22 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +23 | a8: Optional[P], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -201,14 +198,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:26:18 + --> src/mdtest_snippet.py:23:18 | -24 | a7: Union[P, int], -25 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -26 | a8: Optional[P], +21 | a7: Union[P, int], +22 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +23 | a8: Optional[P], | ^ -27 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -28 | a9: Annotated[P, "metadata"], +24 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +25 | a9: Annotated[P, "metadata"], | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -222,13 +219,13 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:28:19 + --> src/mdtest_snippet.py:25:19 | -26 | a8: Optional[P], -27 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -28 | a9: Annotated[P, "metadata"], +23 | a8: Optional[P], +24 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +25 | a9: Annotated[P, "metadata"], | ^ -29 | ) -> None: ... +26 | ) -> None: ... | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -242,12 +239,12 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:32:30 + --> src/mdtest_snippet.py:29:30 | -31 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -32 | def invalid_return[**P]() -> P: +28 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +29 | def invalid_return[**P]() -> P: | ^ -33 | raise NotImplementedError +30 | raise NotImplementedError | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -261,13 +258,13 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:36:19 + --> src/mdtest_snippet.py:33:19 | -35 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -36 | type Alias[**P] = P +32 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +33 | type Alias[**P] = P | ^ -37 | -38 | def invalid_variable_annotation[**P](y: Any) -> None: +34 | +35 | def invalid_variable_annotation[**P](y: Any) -> None: | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -281,14 +278,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:40:8 + --> src/mdtest_snippet.py:37:8 | -38 | def invalid_variable_annotation[**P](y: Any) -> None: -39 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -40 | x: P = y +35 | def invalid_variable_annotation[**P](y: Any) -> None: +36 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +37 | x: P = y | ^ -41 | -42 | def invalid_with_qualifier[**P](y: Any) -> None: +38 | +39 | def invalid_with_qualifier[**P](y: Any) -> None: | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -302,14 +299,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:44:14 + --> src/mdtest_snippet.py:41:14 | -42 | def invalid_with_qualifier[**P](y: Any) -> None: -43 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -44 | x: Final[P] = y +39 | def invalid_with_qualifier[**P](y: Any) -> None: +40 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +41 | x: Final[P] = y | ^ -45 | -46 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +42 | +43 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -323,12 +320,12 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:47:43 + --> src/mdtest_snippet.py:44:43 | -46 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -47 | def invalid_stringified_return[**P]() -> "P": +43 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +44 | def invalid_stringified_return[**P]() -> "P": | ^ -48 | raise NotImplementedError +45 | raise NotImplementedError | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -342,14 +339,14 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:52:9 + --> src/mdtest_snippet.py:49:9 | -50 | def invalid_stringified_annotation[**P]( -51 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -52 | a: "P", +47 | def invalid_stringified_annotation[**P]( +48 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +49 | a: "P", | ^ -53 | ) -> None: ... -54 | def invalid_stringified_variable_annotation[**P](y: Any) -> None: +50 | ) -> None: ... +51 | def invalid_stringified_variable_annotation[**P](y: Any) -> None: | info: A bare ParamSpec is only valid: info: - as the first argument to `Callable` @@ -363,11 +360,11 @@ info: rule `invalid-type-form` is enabled by default ``` error[invalid-type-form]: Bare ParamSpec `P` is not valid in this context in a type expression - --> src/mdtest_snippet.py:56:9 + --> src/mdtest_snippet.py:53:9 | -54 | def invalid_stringified_variable_annotation[**P](y: Any) -> None: -55 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" -56 | x: "P" = y +51 | def invalid_stringified_variable_annotation[**P](y: Any) -> None: +52 | # error: [invalid-type-form] "Bare ParamSpec `P` is not valid in this context" +53 | x: "P" = y | ^ | info: A bare ParamSpec is only valid: diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_PEP_695_`ParamSpec`_-_`ParamSpec`_cannot_s\342\200\246_(8243f67799c93e3c).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_PEP_695_`ParamSpec`_-_`ParamSpec`_cannot_s\342\200\246_(8243f67799c93e3c).snap" new file mode 100644 index 0000000000000..0a8dbe9ef34be --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/paramspec.md_-_PEP_695_`ParamSpec`_-_`ParamSpec`_cannot_s\342\200\246_(8243f67799c93e3c).snap" @@ -0,0 +1,112 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- + +--- +mdtest name: paramspec.md - PEP 695 `ParamSpec` - `ParamSpec` cannot specialize a `TypeVar`, and vice versa +mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import Callable + 2 | + 3 | class OnlyTypeVar[T]: + 4 | attr: T + 5 | + 6 | class TypeVarAndParamSpec[T, **P]: + 7 | attr: Callable[P, T] + 8 | + 9 | def f[**P, T](): +10 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" +11 | a: OnlyTypeVar[P] +12 | +13 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" +14 | b: TypeVarAndParamSpec[P, [int]] +15 | +16 | class OnlyParamSpec[**P]: +17 | attr: Callable[P, None] +18 | +19 | # This is fine due to the special case whereby `OnlyParamSpec[T]` is interpreted the same as +20 | # `OnlyParamSpec[[T]]`, due to the fact that `OnlyParamSpec` is only generic over a single +21 | # `ParamSpec` and no other type variables. +22 | def func2[T](c: OnlyParamSpec[T], other: T): +23 | reveal_type(c.attr) # revealed: (T@func2, /) -> None +24 | +25 | class ParamSpecAndTypeVar[**P, T]: +26 | attr: Callable[P, T] +27 | +28 | # error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`" +29 | def func3[T](c: ParamSpecAndTypeVar[T, int], other: T): ... +``` + +# Diagnostics + +``` +error[invalid-type-arguments]: ParamSpec `P` cannot be used to specialize type variable `T` + --> src/mdtest_snippet.py:9:9 + | + 7 | attr: Callable[P, T] + 8 | + 9 | def f[**P, T](): + | - ParamSpec `P` defined here +10 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" +11 | a: OnlyTypeVar[P] + | ^ +12 | +13 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" + | + ::: src/mdtest_snippet.py:3:19 + | + 1 | from typing import Callable + 2 | + 3 | class OnlyTypeVar[T]: + | - Type variable `T` defined here + 4 | attr: T + | +info: rule `invalid-type-arguments` is enabled by default + +``` + +``` +error[invalid-type-arguments]: ParamSpec `P` cannot be used to specialize type variable `T` + --> src/mdtest_snippet.py:14:28 + | +13 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" +14 | b: TypeVarAndParamSpec[P, [int]] + | ^ +15 | +16 | class OnlyParamSpec[**P]: + | + ::: src/mdtest_snippet.py:6:27 + | + 4 | attr: T + 5 | + 6 | class TypeVarAndParamSpec[T, **P]: + | - Type variable `T` defined here + 7 | attr: Callable[P, T] + 8 | + 9 | def f[**P, T](): + | - ParamSpec `P` defined here +10 | # error: [invalid-type-arguments] "ParamSpec `P` cannot be used to specialize type variable `T`" +11 | a: OnlyTypeVar[P] + | +info: rule `invalid-type-arguments` is enabled by default + +``` + +``` +error[invalid-type-arguments]: Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...` + --> src/mdtest_snippet.py:29:37 + | +28 | # error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`" +29 | def func3[T](c: ParamSpecAndTypeVar[T, int], other: T): ... + | ^ + | +info: rule `invalid-type-arguments` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs index 7afa6b335f86a..d4ba2ac0084a7 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs @@ -494,6 +494,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ) -> Type<'db> { enum ExplicitSpecializationError { InvalidParamSpec, + ParamSpecForTypeVar, UnsatisfiedBound, UnsatisfiedConstraints, /// These two errors override the errors above, causing all specializations to be `Unknown`. @@ -580,6 +581,42 @@ impl<'db> TypeInferenceBuilder<'db, '_> { inferred_type_arguments.push(provided_type); + // A ParamSpec cannot be used to specialize a regular TypeVar. + if !typevar.is_paramspec(db) + && let Type::TypeVar(tv) = provided_type + && tv.is_paramspec(db) + { + let node = get_node(index); + if let Some(builder) = + self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node) + { + let mut diagnostic = builder.into_diagnostic(format_args!( + "ParamSpec `{}` cannot be used to specialize \ + type variable `{}`", + tv.typevar(db).name(db), + typevar.name(db), + )); + for (kind, var) in [("ParamSpec", tv), ("Type variable", typevar)] { + let Some(definition) = var.typevar(db).definition(db) else { + continue; + }; + let file = definition.file(db); + let module = parsed_module(db, file).load(db); + let range = definition.focus_range(db, &module).range(); + diagnostic.annotate( + Annotation::secondary(Span::from(file).with_range(range)) + .message(format_args!( + "{kind} `{}` defined here", + var.name(db) + )), + ); + } + } + error = Some(ExplicitSpecializationError::ParamSpecForTypeVar); + specialization_types.push(Some(Type::unknown())); + continue; + } + // TODO consider just accepting the given specialization without checking // against bounds/constraints, but recording the expression for deferred // checking at end of scope. This would avoid a lot of cycles caused by eagerly @@ -779,7 +816,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Some( ExplicitSpecializationError::UnsatisfiedBound | ExplicitSpecializationError::UnsatisfiedConstraints - | ExplicitSpecializationError::InvalidParamSpec, + | ExplicitSpecializationError::InvalidParamSpec + | ExplicitSpecializationError::ParamSpecForTypeVar, ) | None => specialize(&specialization_types), }