Skip to content

Commit 81bcdce

Browse files
authored
[syntax-errors] Type parameter lists before Python 3.12 (#16479)
Summary -- Another simple one, just detect type parameter lists in functions and classes. Like pyright, we don't emit a second diagnostic for `type` alias statements, which were also introduced in 3.12. Test Plan -- Inline tests.
1 parent d94a78a commit 81bcdce

10 files changed

+557
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# parse_options: {"target-version": "3.11"}
2+
class Foo[S: (str, bytes), T: float, *Ts, **P]: ...
3+
class Foo[]: ...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# parse_options: {"target-version": "3.11"}
2+
def foo[T](): ...
3+
def foo[](): ...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# parse_options: {"target-version": "3.12"}
2+
class Foo[S: (str, bytes), T: float, *Ts, **P]: ...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# parse_options: {"target-version": "3.12"}
2+
def foo[T](): ...

crates/ruff_python_parser/src/error.rs

+30
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,34 @@ pub enum UnsupportedSyntaxErrorKind {
449449
Match,
450450
Walrus,
451451
ExceptStar,
452+
/// Represents the use of a [type parameter list] before Python 3.12.
453+
///
454+
/// ## Examples
455+
///
456+
/// Before Python 3.12, generic parameters had to be declared separately using a class like
457+
/// [`typing.TypeVar`], which could then be used in a function or class definition:
458+
///
459+
/// ```python
460+
/// from typing import Generic, TypeVar
461+
///
462+
/// T = TypeVar("T")
463+
///
464+
/// def f(t: T): ...
465+
/// class C(Generic[T]): ...
466+
/// ```
467+
///
468+
/// [PEP 695], included in Python 3.12, introduced the new type parameter syntax, which allows
469+
/// these to be written more compactly and without a separate type variable:
470+
///
471+
/// ```python
472+
/// def f[T](t: T): ...
473+
/// class C[T]: ...
474+
/// ```
475+
///
476+
/// [type parameter list]: https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists
477+
/// [PEP 695]: https://peps.python.org/pep-0695/
478+
/// [`typing.TypeVar`]: https://docs.python.org/3/library/typing.html#typevar
479+
TypeParameterList,
452480
TypeAliasStatement,
453481
TypeParamDefault,
454482
}
@@ -459,6 +487,7 @@ impl Display for UnsupportedSyntaxError {
459487
UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement",
460488
UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
461489
UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`",
490+
UnsupportedSyntaxErrorKind::TypeParameterList => "Cannot use type parameter lists",
462491
UnsupportedSyntaxErrorKind::TypeAliasStatement => "Cannot use `type` alias statement",
463492
UnsupportedSyntaxErrorKind::TypeParamDefault => {
464493
"Cannot set default type for a type parameter"
@@ -480,6 +509,7 @@ impl UnsupportedSyntaxErrorKind {
480509
UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310,
481510
UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38,
482511
UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311,
512+
UnsupportedSyntaxErrorKind::TypeParameterList => PythonVersion::PY312,
483513
UnsupportedSyntaxErrorKind::TypeAliasStatement => PythonVersion::PY312,
484514
UnsupportedSyntaxErrorKind::TypeParamDefault => PythonVersion::PY313,
485515
}

crates/ruff_python_parser/src/parser/statement.rs

+30
Original file line numberDiff line numberDiff line change
@@ -1786,6 +1786,21 @@ impl<'src> Parser<'src> {
17861786
// x = 10
17871787
let type_params = self.try_parse_type_params();
17881788

1789+
// test_ok function_type_params_py312
1790+
// # parse_options: {"target-version": "3.12"}
1791+
// def foo[T](): ...
1792+
1793+
// test_err function_type_params_py311
1794+
// # parse_options: {"target-version": "3.11"}
1795+
// def foo[T](): ...
1796+
// def foo[](): ...
1797+
if let Some(ast::TypeParams { range, .. }) = &type_params {
1798+
self.add_unsupported_syntax_error(
1799+
UnsupportedSyntaxErrorKind::TypeParameterList,
1800+
*range,
1801+
);
1802+
}
1803+
17891804
// test_ok function_def_parameter_range
17901805
// def foo(
17911806
// first: int,
@@ -1900,6 +1915,21 @@ impl<'src> Parser<'src> {
19001915
// x = 10
19011916
let type_params = self.try_parse_type_params();
19021917

1918+
// test_ok class_type_params_py312
1919+
// # parse_options: {"target-version": "3.12"}
1920+
// class Foo[S: (str, bytes), T: float, *Ts, **P]: ...
1921+
1922+
// test_err class_type_params_py311
1923+
// # parse_options: {"target-version": "3.11"}
1924+
// class Foo[S: (str, bytes), T: float, *Ts, **P]: ...
1925+
// class Foo[]: ...
1926+
if let Some(ast::TypeParams { range, .. }) = &type_params {
1927+
self.add_unsupported_syntax_error(
1928+
UnsupportedSyntaxErrorKind::TypeParameterList,
1929+
*range,
1930+
);
1931+
}
1932+
19031933
// test_ok class_def_arguments
19041934
// class Foo: ...
19051935
// class Foo(): ...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..113,
11+
body: [
12+
ClassDef(
13+
StmtClassDef {
14+
range: 44..95,
15+
decorator_list: [],
16+
name: Identifier {
17+
id: Name("Foo"),
18+
range: 50..53,
19+
},
20+
type_params: Some(
21+
TypeParams {
22+
range: 53..90,
23+
type_params: [
24+
TypeVar(
25+
TypeParamTypeVar {
26+
range: 54..69,
27+
name: Identifier {
28+
id: Name("S"),
29+
range: 54..55,
30+
},
31+
bound: Some(
32+
Tuple(
33+
ExprTuple {
34+
range: 57..69,
35+
elts: [
36+
Name(
37+
ExprName {
38+
range: 58..61,
39+
id: Name("str"),
40+
ctx: Load,
41+
},
42+
),
43+
Name(
44+
ExprName {
45+
range: 63..68,
46+
id: Name("bytes"),
47+
ctx: Load,
48+
},
49+
),
50+
],
51+
ctx: Load,
52+
parenthesized: true,
53+
},
54+
),
55+
),
56+
default: None,
57+
},
58+
),
59+
TypeVar(
60+
TypeParamTypeVar {
61+
range: 71..79,
62+
name: Identifier {
63+
id: Name("T"),
64+
range: 71..72,
65+
},
66+
bound: Some(
67+
Name(
68+
ExprName {
69+
range: 74..79,
70+
id: Name("float"),
71+
ctx: Load,
72+
},
73+
),
74+
),
75+
default: None,
76+
},
77+
),
78+
TypeVarTuple(
79+
TypeParamTypeVarTuple {
80+
range: 81..84,
81+
name: Identifier {
82+
id: Name("Ts"),
83+
range: 82..84,
84+
},
85+
default: None,
86+
},
87+
),
88+
ParamSpec(
89+
TypeParamParamSpec {
90+
range: 86..89,
91+
name: Identifier {
92+
id: Name("P"),
93+
range: 88..89,
94+
},
95+
default: None,
96+
},
97+
),
98+
],
99+
},
100+
),
101+
arguments: None,
102+
body: [
103+
Expr(
104+
StmtExpr {
105+
range: 92..95,
106+
value: EllipsisLiteral(
107+
ExprEllipsisLiteral {
108+
range: 92..95,
109+
},
110+
),
111+
},
112+
),
113+
],
114+
},
115+
),
116+
ClassDef(
117+
StmtClassDef {
118+
range: 96..112,
119+
decorator_list: [],
120+
name: Identifier {
121+
id: Name("Foo"),
122+
range: 102..105,
123+
},
124+
type_params: Some(
125+
TypeParams {
126+
range: 105..107,
127+
type_params: [],
128+
},
129+
),
130+
arguments: None,
131+
body: [
132+
Expr(
133+
StmtExpr {
134+
range: 109..112,
135+
value: EllipsisLiteral(
136+
ExprEllipsisLiteral {
137+
range: 109..112,
138+
},
139+
),
140+
},
141+
),
142+
],
143+
},
144+
),
145+
],
146+
},
147+
)
148+
```
149+
## Errors
150+
151+
|
152+
1 | # parse_options: {"target-version": "3.11"}
153+
2 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ...
154+
3 | class Foo[]: ...
155+
| ^ Syntax Error: Type parameter list cannot be empty
156+
|
157+
158+
159+
## Unsupported Syntax Errors
160+
161+
|
162+
1 | # parse_options: {"target-version": "3.11"}
163+
2 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ...
164+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12)
165+
3 | class Foo[]: ...
166+
|
167+
168+
169+
|
170+
1 | # parse_options: {"target-version": "3.11"}
171+
2 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ...
172+
3 | class Foo[]: ...
173+
| ^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12)
174+
|

0 commit comments

Comments
 (0)