diff --git a/crates/ruff/src/cache.rs b/crates/ruff/src/cache.rs index 2ae5e82850..addc4cac96 100644 --- a/crates/ruff/src/cache.rs +++ b/crates/ruff/src/cache.rs @@ -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() diff --git a/crates/ruff_python_parser/resources/inline/err/except_star_py310.py b/crates/ruff_python_parser/resources/inline/err/except_star_py310.py new file mode 100644 index 0000000000..17c373fb9a --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/except_star_py310.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.10"} +try: ... +except* ValueError: ... diff --git a/crates/ruff_python_parser/resources/inline/ok/except_star_py311.py b/crates/ruff_python_parser/resources/inline/ok/except_star_py311.py new file mode 100644 index 0000000000..2aafd897f3 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/except_star_py311.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.11"} +try: ... +except* ValueError: ... diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 9b21752442..191068c2a1 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -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, } } } diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 2a0db69997..e37fb835d7 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -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), diff --git a/crates/ruff_python_parser/src/parser/mod.rs b/crates/ruff_python_parser/src/parser/mod.rs index 81ac52d973..78bf047d46 100644 --- a/crates/ruff_python_parser/src/parser/mod.rs +++ b/crates/ruff_python_parser/src/parser/mod.rs @@ -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. diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 64bc40fee4..5f7f870a00 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -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), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_star_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_star_py310.py.snap new file mode 100644 index 0000000000..a7c20d6e23 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@except_star_py310.py.snap @@ -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) + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_star_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_star_py311.py.snap new file mode 100644 index 0000000000..f0a6018f84 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@except_star_py311.py.snap @@ -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, + }, + ), + ], + }, +) +```