Skip to content

Commit e7b93f9

Browse files
authored
[syntax-errors] Type parameter defaults before Python 3.13 (#16447)
Summary -- Detects the presence of a [PEP 696] type parameter default before Python 3.13. Test Plan -- New inline parser tests for type aliases, generic functions and generic classes. [PEP 696]: https://peps.python.org/pep-0696/#grammar-changes
1 parent c8a06a9 commit e7b93f9

File tree

6 files changed

+514
-4
lines changed

6 files changed

+514
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# parse_options: {"target-version": "3.12"}
2+
type X[T = int] = int
3+
def f[T = int](): ...
4+
class C[T = int](): ...
5+
class D[S, T = int, U = uint](): ...
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# parse_options: {"target-version": "3.13"}
2+
type X[T = int] = int
3+
def f[T = int](): ...
4+
class C[T = int](): ...

crates/ruff_python_parser/src/error.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,18 +449,22 @@ pub enum UnsupportedSyntaxErrorKind {
449449
Match,
450450
Walrus,
451451
ExceptStar,
452+
TypeParamDefault,
452453
}
453454

454455
impl Display for UnsupportedSyntaxError {
455456
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456457
let kind = match self.kind {
457-
UnsupportedSyntaxErrorKind::Match => "`match` statement",
458-
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)",
459-
UnsupportedSyntaxErrorKind::ExceptStar => "`except*`",
458+
UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement",
459+
UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
460+
UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`",
461+
UnsupportedSyntaxErrorKind::TypeParamDefault => {
462+
"Cannot set default type for a type parameter"
463+
}
460464
};
461465
write!(
462466
f,
463-
"Cannot use {kind} on Python {} (syntax was added in Python {})",
467+
"{kind} on Python {} (syntax was added in Python {})",
464468
self.target_version,
465469
self.kind.minimum_version(),
466470
)
@@ -474,6 +478,7 @@ impl UnsupportedSyntaxErrorKind {
474478
UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310,
475479
UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38,
476480
UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311,
481+
UnsupportedSyntaxErrorKind::TypeParamDefault => PythonVersion::PY313,
477482
}
478483
}
479484
}

crates/ruff_python_parser/src/parser/statement.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3243,6 +3243,7 @@ impl<'src> Parser<'src> {
32433243
None
32443244
};
32453245

3246+
let equal_token_start = self.node_start();
32463247
let default = if self.eat(TokenKind::Equal) {
32473248
if self.at_expr() {
32483249
// test_err type_param_type_var_invalid_default_expr
@@ -3268,6 +3269,26 @@ impl<'src> Parser<'src> {
32683269
None
32693270
};
32703271

3272+
// test_ok type_param_default_py313
3273+
// # parse_options: {"target-version": "3.13"}
3274+
// type X[T = int] = int
3275+
// def f[T = int](): ...
3276+
// class C[T = int](): ...
3277+
3278+
// test_err type_param_default_py312
3279+
// # parse_options: {"target-version": "3.12"}
3280+
// type X[T = int] = int
3281+
// def f[T = int](): ...
3282+
// class C[T = int](): ...
3283+
// class D[S, T = int, U = uint](): ...
3284+
3285+
if default.is_some() {
3286+
self.add_unsupported_syntax_error(
3287+
UnsupportedSyntaxErrorKind::TypeParamDefault,
3288+
self.node_range(equal_token_start),
3289+
);
3290+
}
3291+
32713292
ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
32723293
range: self.node_range(start),
32733294
name,
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/err/type_param_default_py312.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..149,
11+
body: [
12+
TypeAlias(
13+
StmtTypeAlias {
14+
range: 44..65,
15+
name: Name(
16+
ExprName {
17+
range: 49..50,
18+
id: Name("X"),
19+
ctx: Store,
20+
},
21+
),
22+
type_params: Some(
23+
TypeParams {
24+
range: 50..59,
25+
type_params: [
26+
TypeVar(
27+
TypeParamTypeVar {
28+
range: 51..58,
29+
name: Identifier {
30+
id: Name("T"),
31+
range: 51..52,
32+
},
33+
bound: None,
34+
default: Some(
35+
Name(
36+
ExprName {
37+
range: 55..58,
38+
id: Name("int"),
39+
ctx: Load,
40+
},
41+
),
42+
),
43+
},
44+
),
45+
],
46+
},
47+
),
48+
value: Name(
49+
ExprName {
50+
range: 62..65,
51+
id: Name("int"),
52+
ctx: Load,
53+
},
54+
),
55+
},
56+
),
57+
FunctionDef(
58+
StmtFunctionDef {
59+
range: 66..87,
60+
is_async: false,
61+
decorator_list: [],
62+
name: Identifier {
63+
id: Name("f"),
64+
range: 70..71,
65+
},
66+
type_params: Some(
67+
TypeParams {
68+
range: 71..80,
69+
type_params: [
70+
TypeVar(
71+
TypeParamTypeVar {
72+
range: 72..79,
73+
name: Identifier {
74+
id: Name("T"),
75+
range: 72..73,
76+
},
77+
bound: None,
78+
default: Some(
79+
Name(
80+
ExprName {
81+
range: 76..79,
82+
id: Name("int"),
83+
ctx: Load,
84+
},
85+
),
86+
),
87+
},
88+
),
89+
],
90+
},
91+
),
92+
parameters: Parameters {
93+
range: 80..82,
94+
posonlyargs: [],
95+
args: [],
96+
vararg: None,
97+
kwonlyargs: [],
98+
kwarg: None,
99+
},
100+
returns: None,
101+
body: [
102+
Expr(
103+
StmtExpr {
104+
range: 84..87,
105+
value: EllipsisLiteral(
106+
ExprEllipsisLiteral {
107+
range: 84..87,
108+
},
109+
),
110+
},
111+
),
112+
],
113+
},
114+
),
115+
ClassDef(
116+
StmtClassDef {
117+
range: 88..111,
118+
decorator_list: [],
119+
name: Identifier {
120+
id: Name("C"),
121+
range: 94..95,
122+
},
123+
type_params: Some(
124+
TypeParams {
125+
range: 95..104,
126+
type_params: [
127+
TypeVar(
128+
TypeParamTypeVar {
129+
range: 96..103,
130+
name: Identifier {
131+
id: Name("T"),
132+
range: 96..97,
133+
},
134+
bound: None,
135+
default: Some(
136+
Name(
137+
ExprName {
138+
range: 100..103,
139+
id: Name("int"),
140+
ctx: Load,
141+
},
142+
),
143+
),
144+
},
145+
),
146+
],
147+
},
148+
),
149+
arguments: Some(
150+
Arguments {
151+
range: 104..106,
152+
args: [],
153+
keywords: [],
154+
},
155+
),
156+
body: [
157+
Expr(
158+
StmtExpr {
159+
range: 108..111,
160+
value: EllipsisLiteral(
161+
ExprEllipsisLiteral {
162+
range: 108..111,
163+
},
164+
),
165+
},
166+
),
167+
],
168+
},
169+
),
170+
ClassDef(
171+
StmtClassDef {
172+
range: 112..148,
173+
decorator_list: [],
174+
name: Identifier {
175+
id: Name("D"),
176+
range: 118..119,
177+
},
178+
type_params: Some(
179+
TypeParams {
180+
range: 119..141,
181+
type_params: [
182+
TypeVar(
183+
TypeParamTypeVar {
184+
range: 120..121,
185+
name: Identifier {
186+
id: Name("S"),
187+
range: 120..121,
188+
},
189+
bound: None,
190+
default: None,
191+
},
192+
),
193+
TypeVar(
194+
TypeParamTypeVar {
195+
range: 123..130,
196+
name: Identifier {
197+
id: Name("T"),
198+
range: 123..124,
199+
},
200+
bound: None,
201+
default: Some(
202+
Name(
203+
ExprName {
204+
range: 127..130,
205+
id: Name("int"),
206+
ctx: Load,
207+
},
208+
),
209+
),
210+
},
211+
),
212+
TypeVar(
213+
TypeParamTypeVar {
214+
range: 132..140,
215+
name: Identifier {
216+
id: Name("U"),
217+
range: 132..133,
218+
},
219+
bound: None,
220+
default: Some(
221+
Name(
222+
ExprName {
223+
range: 136..140,
224+
id: Name("uint"),
225+
ctx: Load,
226+
},
227+
),
228+
),
229+
},
230+
),
231+
],
232+
},
233+
),
234+
arguments: Some(
235+
Arguments {
236+
range: 141..143,
237+
args: [],
238+
keywords: [],
239+
},
240+
),
241+
body: [
242+
Expr(
243+
StmtExpr {
244+
range: 145..148,
245+
value: EllipsisLiteral(
246+
ExprEllipsisLiteral {
247+
range: 145..148,
248+
},
249+
),
250+
},
251+
),
252+
],
253+
},
254+
),
255+
],
256+
},
257+
)
258+
```
259+
## Unsupported Syntax Errors
260+
261+
|
262+
1 | # parse_options: {"target-version": "3.12"}
263+
2 | type X[T = int] = int
264+
| ^^^^^ Syntax Error: Cannot set default type for a type parameter on Python 3.12 (syntax was added in Python 3.13)
265+
3 | def f[T = int](): ...
266+
4 | class C[T = int](): ...
267+
|
268+
269+
270+
|
271+
1 | # parse_options: {"target-version": "3.12"}
272+
2 | type X[T = int] = int
273+
3 | def f[T = int](): ...
274+
| ^^^^^ Syntax Error: Cannot set default type for a type parameter on Python 3.12 (syntax was added in Python 3.13)
275+
4 | class C[T = int](): ...
276+
5 | class D[S, T = int, U = uint](): ...
277+
|
278+
279+
280+
|
281+
2 | type X[T = int] = int
282+
3 | def f[T = int](): ...
283+
4 | class C[T = int](): ...
284+
| ^^^^^ Syntax Error: Cannot set default type for a type parameter on Python 3.12 (syntax was added in Python 3.13)
285+
5 | class D[S, T = int, U = uint](): ...
286+
|
287+
288+
289+
|
290+
3 | def f[T = int](): ...
291+
4 | class C[T = int](): ...
292+
5 | class D[S, T = int, U = uint](): ...
293+
| ^^^^^ Syntax Error: Cannot set default type for a type parameter on Python 3.12 (syntax was added in Python 3.13)
294+
|
295+
296+
297+
|
298+
3 | def f[T = int](): ...
299+
4 | class C[T = int](): ...
300+
5 | class D[S, T = int, U = uint](): ...
301+
| ^^^^^^ Syntax Error: Cannot set default type for a type parameter on Python 3.12 (syntax was added in Python 3.13)
302+
|

0 commit comments

Comments
 (0)