Skip to content

Commit e924ecb

Browse files
authored
[syntax-errors] except* before Python 3.11 (astral-sh#16446)
Summary -- One of the simpler ones, just detect the use of `except*` before 3.11. Test Plan -- New inline tests.
1 parent 0d615b8 commit e924ecb

File tree

9 files changed

+180
-23
lines changed

9 files changed

+180
-23
lines changed

crates/ruff/src/cache.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ mod tests {
613613
let settings = Settings {
614614
cache_dir,
615615
linter: LinterSettings {
616-
unresolved_target_version: PythonVersion::PY310,
616+
unresolved_target_version: PythonVersion::latest(),
617617
..Default::default()
618618
},
619619
..Settings::default()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# parse_options: {"target-version": "3.10"}
2+
try: ...
3+
except* ValueError: ...
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# parse_options: {"target-version": "3.11"}
2+
try: ...
3+
except* ValueError: ...

crates/ruff_python_parser/src/error.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,39 +438,42 @@ pub struct UnsupportedSyntaxError {
438438
/// The target [`PythonVersion`] for which this error was detected.
439439
///
440440
/// This is different from the version reported by the
441-
/// [`minimum_version`](UnsupportedSyntaxError::minimum_version) method, which is the earliest
442-
/// allowed version for this piece of syntax. The `target_version` is primarily used for
443-
/// user-facing error messages.
441+
/// [`minimum_version`](UnsupportedSyntaxErrorKind::minimum_version) method, which is the
442+
/// earliest allowed version for this piece of syntax. The `target_version` is primarily used
443+
/// for user-facing error messages.
444444
pub target_version: PythonVersion,
445445
}
446446

447447
#[derive(Debug, PartialEq, Clone, Copy)]
448448
pub enum UnsupportedSyntaxErrorKind {
449449
Match,
450450
Walrus,
451+
ExceptStar,
451452
}
452453

453454
impl Display for UnsupportedSyntaxError {
454455
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455456
let kind = match self.kind {
456457
UnsupportedSyntaxErrorKind::Match => "`match` statement",
457458
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)",
459+
UnsupportedSyntaxErrorKind::ExceptStar => "`except*`",
458460
};
459461
write!(
460462
f,
461463
"Cannot use {kind} on Python {} (syntax was added in Python {})",
462464
self.target_version,
463-
self.minimum_version(),
465+
self.kind.minimum_version(),
464466
)
465467
}
466468
}
467469

468-
impl UnsupportedSyntaxError {
470+
impl UnsupportedSyntaxErrorKind {
469471
/// The earliest allowed version for the syntax associated with this error.
470472
pub const fn minimum_version(&self) -> PythonVersion {
471-
match self.kind {
473+
match self {
472474
UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310,
473475
UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38,
476+
UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311,
474477
}
475478
}
476479
}

crates/ruff_python_parser/src/parser/expression.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
77
use ruff_python_ast::name::Name;
88
use ruff_python_ast::{
99
self as ast, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FStringElement, FStringElements,
10-
IpyEscapeKind, Number, Operator, PythonVersion, StringFlags, UnaryOp,
10+
IpyEscapeKind, Number, Operator, StringFlags, UnaryOp,
1111
};
1212
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
1313

@@ -2171,9 +2171,7 @@ impl<'src> Parser<'src> {
21712171
// # parse_options: { "target-version": "3.8" }
21722172
// (x := 1)
21732173

2174-
if self.options.target_version < PythonVersion::PY38 {
2175-
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Walrus, range);
2176-
}
2174+
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Walrus, range);
21772175

21782176
ast::ExprNamed {
21792177
target: Box::new(target),

crates/ruff_python_parser/src/parser/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -439,13 +439,15 @@ impl<'src> Parser<'src> {
439439
}
440440

441441
/// Add an [`UnsupportedSyntaxError`] with the given [`UnsupportedSyntaxErrorKind`] and
442-
/// [`TextRange`].
442+
/// [`TextRange`] if its minimum version is less than [`Parser::target_version`].
443443
fn add_unsupported_syntax_error(&mut self, kind: UnsupportedSyntaxErrorKind, range: TextRange) {
444-
self.unsupported_syntax_errors.push(UnsupportedSyntaxError {
445-
kind,
446-
range,
447-
target_version: self.options.target_version,
448-
});
444+
if self.options.target_version < kind.minimum_version() {
445+
self.unsupported_syntax_errors.push(UnsupportedSyntaxError {
446+
kind,
447+
range,
448+
target_version: self.options.target_version,
449+
});
450+
}
449451
}
450452

451453
/// Returns `true` if the current token is of the given kind.

crates/ruff_python_parser/src/parser/statement.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
55

66
use ruff_python_ast::name::Name;
77
use ruff_python_ast::{
8-
self as ast, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, PythonVersion, Stmt,
9-
WithItem,
8+
self as ast, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, Stmt, WithItem,
109
};
1110
use ruff_text_size::{Ranged, TextSize};
1211

@@ -1458,13 +1457,28 @@ impl<'src> Parser<'src> {
14581457
);
14591458
}
14601459

1460+
// test_ok except_star_py311
1461+
// # parse_options: {"target-version": "3.11"}
1462+
// try: ...
1463+
// except* ValueError: ...
1464+
1465+
// test_err except_star_py310
1466+
// # parse_options: {"target-version": "3.10"}
1467+
// try: ...
1468+
// except* ValueError: ...
1469+
1470+
let range = self.node_range(try_start);
1471+
if is_star {
1472+
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::ExceptStar, range);
1473+
}
1474+
14611475
ast::StmtTry {
14621476
body: try_body,
14631477
handlers,
14641478
orelse,
14651479
finalbody,
14661480
is_star,
1467-
range: self.node_range(try_start),
1481+
range,
14681482
}
14691483
}
14701484

@@ -2277,9 +2291,7 @@ impl<'src> Parser<'src> {
22772291
// case 1:
22782292
// pass
22792293

2280-
if self.options.target_version < PythonVersion::PY310 {
2281-
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Match, match_range);
2282-
}
2294+
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Match, match_range);
22832295

22842296
ast::StmtMatch {
22852297
subject: Box::new(subject),
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/err/except_star_py310.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..77,
11+
body: [
12+
Try(
13+
StmtTry {
14+
range: 44..76,
15+
body: [
16+
Expr(
17+
StmtExpr {
18+
range: 49..52,
19+
value: EllipsisLiteral(
20+
ExprEllipsisLiteral {
21+
range: 49..52,
22+
},
23+
),
24+
},
25+
),
26+
],
27+
handlers: [
28+
ExceptHandler(
29+
ExceptHandlerExceptHandler {
30+
range: 53..76,
31+
type_: Some(
32+
Name(
33+
ExprName {
34+
range: 61..71,
35+
id: Name("ValueError"),
36+
ctx: Load,
37+
},
38+
),
39+
),
40+
name: None,
41+
body: [
42+
Expr(
43+
StmtExpr {
44+
range: 73..76,
45+
value: EllipsisLiteral(
46+
ExprEllipsisLiteral {
47+
range: 73..76,
48+
},
49+
),
50+
},
51+
),
52+
],
53+
},
54+
),
55+
],
56+
orelse: [],
57+
finalbody: [],
58+
is_star: true,
59+
},
60+
),
61+
],
62+
},
63+
)
64+
```
65+
## Unsupported Syntax Errors
66+
67+
|
68+
1 | # parse_options: {"target-version": "3.10"}
69+
2 | / try: ...
70+
3 | | except* ValueError: ...
71+
| |_______________________^ Syntax Error: Cannot use `except*` on Python 3.10 (syntax was added in Python 3.11)
72+
|
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/ok/except_star_py311.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..77,
11+
body: [
12+
Try(
13+
StmtTry {
14+
range: 44..76,
15+
body: [
16+
Expr(
17+
StmtExpr {
18+
range: 49..52,
19+
value: EllipsisLiteral(
20+
ExprEllipsisLiteral {
21+
range: 49..52,
22+
},
23+
),
24+
},
25+
),
26+
],
27+
handlers: [
28+
ExceptHandler(
29+
ExceptHandlerExceptHandler {
30+
range: 53..76,
31+
type_: Some(
32+
Name(
33+
ExprName {
34+
range: 61..71,
35+
id: Name("ValueError"),
36+
ctx: Load,
37+
},
38+
),
39+
),
40+
name: None,
41+
body: [
42+
Expr(
43+
StmtExpr {
44+
range: 73..76,
45+
value: EllipsisLiteral(
46+
ExprEllipsisLiteral {
47+
range: 73..76,
48+
},
49+
),
50+
},
51+
),
52+
],
53+
},
54+
),
55+
],
56+
orelse: [],
57+
finalbody: [],
58+
is_star: true,
59+
},
60+
),
61+
],
62+
},
63+
)
64+
```

0 commit comments

Comments
 (0)