[ty] Split `ParamSpec` mdtests to separate legacy and PEP 695 tests (#21687)

## Summary

This is another small refactor for
https://github.com/astral-sh/ruff/pull/21445 that splits the single
`paramspec.md` into `generics/legacy/paramspec.md` and
`generics/pep695/paramspec.md`.

## Test Plan

Make sure that all mdtests pass.
This commit is contained in:
Dhruv Manilawala 2025-11-29 12:19:39 +05:30 committed by GitHub
parent ecab623fb2
commit 8795d9f0cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 57 deletions

View File

@ -1,4 +1,4 @@
# `ParamSpec`
# Legacy `ParamSpec`
## Definition
@ -115,59 +115,3 @@ P = ParamSpec("P", default=[A, B])
class A: ...
class B: ...
```
### PEP 695
```toml
[environment]
python-version = "3.12"
```
#### Valid
```py
def foo1[**P]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
def foo2[**P = ...]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
def foo3[**P = [int, str]]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
def foo4[**P, **Q = P]():
reveal_type(P) # revealed: typing.ParamSpec
reveal_type(Q) # revealed: typing.ParamSpec
```
#### Invalid
ParamSpec, when defined using the new syntax, does not allow defining bounds or constraints.
This results in a lot of syntax errors mainly because the AST doesn't accept them in this position.
The parser could do a better job in recovering from these errors.
<!-- blacken-docs:off -->
```py
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
def foo[**P: int]() -> None:
# error: [invalid-syntax]
# error: [invalid-syntax]
pass
```
<!-- blacken-docs:on -->
#### Invalid default
```py
# error: [invalid-paramspec]
def foo[**P = int]() -> None:
pass
```

View File

@ -0,0 +1,64 @@
# PEP 695 `ParamSpec`
`ParamSpec` was introduced in Python 3.12 while the support for specifying defaults was added in
Python 3.13.
```toml
[environment]
python-version = "3.13"
```
## Definition
```py
def foo1[**P]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
```
## Bounds and constraints
`ParamSpec`, when defined using the new syntax, does not allow defining bounds or constraints.
TODO: This results in a lot of syntax errors mainly because the AST doesn't accept them in this
position. The parser could do a better job in recovering from these errors.
<!-- blacken-docs:off -->
```py
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
def foo[**P: int]() -> None:
# error: [invalid-syntax]
# error: [invalid-syntax]
pass
```
<!-- blacken-docs:on -->
## Default
The default value for a `ParamSpec` can be either a list of types, `...`, or another `ParamSpec`.
```py
def foo2[**P = ...]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
def foo3[**P = [int, str]]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
def foo4[**P, **Q = P]():
reveal_type(P) # revealed: typing.ParamSpec
reveal_type(Q) # revealed: typing.ParamSpec
```
Other values are invalid.
```py
# error: [invalid-paramspec]
def foo[**P = int]() -> None:
pass
```