[syntax-errors] except* before Python 3.11 (#16446)

Summary
--

One of the simpler ones, just detect the use of `except*` before 3.11.

Test Plan
--

New inline tests.
This commit is contained in:
Brent Westbrook 2025-03-02 13:20:18 -05:00 committed by GitHub
parent 0d615b8765
commit e924ecbdac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 180 additions and 23 deletions

View file

@ -613,7 +613,7 @@ mod tests {
let settings = Settings {
cache_dir,
linter: LinterSettings {
unresolved_target_version: PythonVersion::PY310,
unresolved_target_version: PythonVersion::latest(),
..Default::default()
},
..Settings::default()

View file

@ -0,0 +1,3 @@
# parse_options: {"target-version": "3.10"}
try: ...
except* ValueError: ...

View file

@ -0,0 +1,3 @@
# parse_options: {"target-version": "3.11"}
try: ...
except* ValueError: ...

View file

@ -438,9 +438,9 @@ pub struct UnsupportedSyntaxError {
/// The target [`PythonVersion`] for which this error was detected.
///
/// This is different from the version reported by the
/// [`minimum_version`](UnsupportedSyntaxError::minimum_version) method, which is the earliest
/// allowed version for this piece of syntax. The `target_version` is primarily used for
/// user-facing error messages.
/// [`minimum_version`](UnsupportedSyntaxErrorKind::minimum_version) method, which is the
/// earliest allowed version for this piece of syntax. The `target_version` is primarily used
/// for user-facing error messages.
pub target_version: PythonVersion,
}
@ -448,6 +448,7 @@ pub struct UnsupportedSyntaxError {
pub enum UnsupportedSyntaxErrorKind {
Match,
Walrus,
ExceptStar,
}
impl Display for UnsupportedSyntaxError {
@ -455,22 +456,24 @@ impl Display for UnsupportedSyntaxError {
let kind = match self.kind {
UnsupportedSyntaxErrorKind::Match => "`match` statement",
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)",
UnsupportedSyntaxErrorKind::ExceptStar => "`except*`",
};
write!(
f,
"Cannot use {kind} on Python {} (syntax was added in Python {})",
self.target_version,
self.minimum_version(),
self.kind.minimum_version(),
)
}
}
impl UnsupportedSyntaxError {
impl UnsupportedSyntaxErrorKind {
/// The earliest allowed version for the syntax associated with this error.
pub const fn minimum_version(&self) -> PythonVersion {
match self.kind {
match self {
UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310,
UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38,
UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311,
}
}
}

View file

@ -7,7 +7,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
use ruff_python_ast::name::Name;
use ruff_python_ast::{
self as ast, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FStringElement, FStringElements,
IpyEscapeKind, Number, Operator, PythonVersion, StringFlags, UnaryOp,
IpyEscapeKind, Number, Operator, StringFlags, UnaryOp,
};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
@ -2171,9 +2171,7 @@ impl<'src> Parser<'src> {
// # parse_options: { "target-version": "3.8" }
// (x := 1)
if self.options.target_version < PythonVersion::PY38 {
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Walrus, range);
}
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Walrus, range);
ast::ExprNamed {
target: Box::new(target),

View file

@ -439,13 +439,15 @@ impl<'src> Parser<'src> {
}
/// Add an [`UnsupportedSyntaxError`] with the given [`UnsupportedSyntaxErrorKind`] and
/// [`TextRange`].
/// [`TextRange`] if its minimum version is less than [`Parser::target_version`].
fn add_unsupported_syntax_error(&mut self, kind: UnsupportedSyntaxErrorKind, range: TextRange) {
self.unsupported_syntax_errors.push(UnsupportedSyntaxError {
kind,
range,
target_version: self.options.target_version,
});
if self.options.target_version < kind.minimum_version() {
self.unsupported_syntax_errors.push(UnsupportedSyntaxError {
kind,
range,
target_version: self.options.target_version,
});
}
}
/// Returns `true` if the current token is of the given kind.

View file

@ -5,8 +5,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
use ruff_python_ast::name::Name;
use ruff_python_ast::{
self as ast, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, PythonVersion, Stmt,
WithItem,
self as ast, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, Stmt, WithItem,
};
use ruff_text_size::{Ranged, TextSize};
@ -1458,13 +1457,28 @@ impl<'src> Parser<'src> {
);
}
// test_ok except_star_py311
// # parse_options: {"target-version": "3.11"}
// try: ...
// except* ValueError: ...
// test_err except_star_py310
// # parse_options: {"target-version": "3.10"}
// try: ...
// except* ValueError: ...
let range = self.node_range(try_start);
if is_star {
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::ExceptStar, range);
}
ast::StmtTry {
body: try_body,
handlers,
orelse,
finalbody,
is_star,
range: self.node_range(try_start),
range,
}
}
@ -2277,9 +2291,7 @@ impl<'src> Parser<'src> {
// case 1:
// pass
if self.options.target_version < PythonVersion::PY310 {
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Match, match_range);
}
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::Match, match_range);
ast::StmtMatch {
subject: Box::new(subject),

View file

@ -0,0 +1,72 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/except_star_py310.py
---
## AST
```
Module(
ModModule {
range: 0..77,
body: [
Try(
StmtTry {
range: 44..76,
body: [
Expr(
StmtExpr {
range: 49..52,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 49..52,
},
),
},
),
],
handlers: [
ExceptHandler(
ExceptHandlerExceptHandler {
range: 53..76,
type_: Some(
Name(
ExprName {
range: 61..71,
id: Name("ValueError"),
ctx: Load,
},
),
),
name: None,
body: [
Expr(
StmtExpr {
range: 73..76,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 73..76,
},
),
},
),
],
},
),
],
orelse: [],
finalbody: [],
is_star: true,
},
),
],
},
)
```
## Unsupported Syntax Errors
|
1 | # parse_options: {"target-version": "3.10"}
2 | / try: ...
3 | | except* ValueError: ...
| |_______________________^ Syntax Error: Cannot use `except*` on Python 3.10 (syntax was added in Python 3.11)
|

View file

@ -0,0 +1,64 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/except_star_py311.py
---
## AST
```
Module(
ModModule {
range: 0..77,
body: [
Try(
StmtTry {
range: 44..76,
body: [
Expr(
StmtExpr {
range: 49..52,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 49..52,
},
),
},
),
],
handlers: [
ExceptHandler(
ExceptHandlerExceptHandler {
range: 53..76,
type_: Some(
Name(
ExprName {
range: 61..71,
id: Name("ValueError"),
ctx: Load,
},
),
),
name: None,
body: [
Expr(
StmtExpr {
range: 73..76,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 73..76,
},
),
},
),
],
},
),
],
orelse: [],
finalbody: [],
is_star: true,
},
),
],
},
)
```