diff --git a/crates/ruff_python_parser/resources/inline/err/try_stmt_mixed_except_kind.py b/crates/ruff_python_parser/resources/inline/err/try_stmt_mixed_except_kind.py new file mode 100644 index 0000000000..e9be12b340 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/try_stmt_mixed_except_kind.py @@ -0,0 +1,22 @@ +try: + pass +except: + pass +except* ExceptionGroup: + pass +try: + pass +except* ExceptionGroup: + pass +except: + pass +try: + pass +except: + pass +except: + pass +except* ExceptionGroup: + pass +except* ExceptionGroup: + pass diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 3b7c37636c..fc1bb4bde4 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -1332,14 +1332,12 @@ impl<'src> Parser<'src> { self.bump(TokenKind::Try); self.expect(TokenKind::Colon); - let mut is_star = false; + let mut is_star: Option = None; let try_body = self.parse_body(Clause::Try); let has_except = self.at(TokenKind::Except); - // TODO(dhruvmanila): Raise syntax error if there are both 'except' and 'except*' - // on the same 'try' // test_err try_stmt_mixed_except_kind // try: // pass @@ -1353,11 +1351,36 @@ impl<'src> Parser<'src> { // pass // except: // pass + // try: + // pass + // except: + // pass + // except: + // pass + // except* ExceptionGroup: + // pass + // except* ExceptionGroup: + // pass + let mut mixed_except_ranges = Vec::new(); let handlers = self.parse_clauses(Clause::Except, |p| { let (handler, kind) = p.parse_except_clause(); - is_star |= kind.is_star(); + if is_star.is_none() { + is_star = Some(kind.is_star()); + } else if is_star != Some(kind.is_star()) { + mixed_except_ranges.push(handler.range()); + } handler }); + // Empty handler has `is_star` false. + let is_star = is_star.unwrap_or_default(); + for handler_err_range in mixed_except_ranges { + self.add_error( + ParseErrorType::OtherError( + "Cannot have both 'except' and 'except*' on the same 'try'".to_string(), + ), + handler_err_range, + ); + } // test_err try_stmt_misspelled_except // try: diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_mixed_except_kind.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_mixed_except_kind.py.snap new file mode 100644 index 0000000000..0117d56216 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@try_stmt_mixed_except_kind.py.snap @@ -0,0 +1,252 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/try_stmt_mixed_except_kind.py +--- +## AST + +``` +Module( + ModModule { + range: 0..242, + body: [ + Try( + StmtTry { + range: 0..63, + body: [ + Pass( + StmtPass { + range: 9..13, + }, + ), + ], + handlers: [ + ExceptHandler( + ExceptHandlerExceptHandler { + range: 14..30, + type_: None, + name: None, + body: [ + Pass( + StmtPass { + range: 26..30, + }, + ), + ], + }, + ), + ExceptHandler( + ExceptHandlerExceptHandler { + range: 31..63, + type_: Some( + Name( + ExprName { + range: 39..53, + id: Name("ExceptionGroup"), + ctx: Load, + }, + ), + ), + name: None, + body: [ + Pass( + StmtPass { + range: 59..63, + }, + ), + ], + }, + ), + ], + orelse: [], + finalbody: [], + is_star: false, + }, + ), + Try( + StmtTry { + range: 64..127, + body: [ + Pass( + StmtPass { + range: 73..77, + }, + ), + ], + handlers: [ + ExceptHandler( + ExceptHandlerExceptHandler { + range: 78..110, + type_: Some( + Name( + ExprName { + range: 86..100, + id: Name("ExceptionGroup"), + ctx: Load, + }, + ), + ), + name: None, + body: [ + Pass( + StmtPass { + range: 106..110, + }, + ), + ], + }, + ), + ExceptHandler( + ExceptHandlerExceptHandler { + range: 111..127, + type_: None, + name: None, + body: [ + Pass( + StmtPass { + range: 123..127, + }, + ), + ], + }, + ), + ], + orelse: [], + finalbody: [], + is_star: true, + }, + ), + Try( + StmtTry { + range: 128..241, + body: [ + Pass( + StmtPass { + range: 137..141, + }, + ), + ], + handlers: [ + ExceptHandler( + ExceptHandlerExceptHandler { + range: 142..158, + type_: None, + name: None, + body: [ + Pass( + StmtPass { + range: 154..158, + }, + ), + ], + }, + ), + ExceptHandler( + ExceptHandlerExceptHandler { + range: 159..175, + type_: None, + name: None, + body: [ + Pass( + StmtPass { + range: 171..175, + }, + ), + ], + }, + ), + ExceptHandler( + ExceptHandlerExceptHandler { + range: 176..208, + type_: Some( + Name( + ExprName { + range: 184..198, + id: Name("ExceptionGroup"), + ctx: Load, + }, + ), + ), + name: None, + body: [ + Pass( + StmtPass { + range: 204..208, + }, + ), + ], + }, + ), + ExceptHandler( + ExceptHandlerExceptHandler { + range: 209..241, + type_: Some( + Name( + ExprName { + range: 217..231, + id: Name("ExceptionGroup"), + ctx: Load, + }, + ), + ), + name: None, + body: [ + Pass( + StmtPass { + range: 237..241, + }, + ), + ], + }, + ), + ], + orelse: [], + finalbody: [], + is_star: false, + }, + ), + ], + }, +) +``` +## Errors + + | +3 | except: +4 | pass +5 | / except* ExceptionGroup: +6 | | pass + | |________^ Syntax Error: Cannot have both 'except' and 'except*' on the same 'try' +7 | try: +8 | pass + | + + + | + 9 | except* ExceptionGroup: +10 | pass +11 | / except: +12 | | pass + | |________^ Syntax Error: Cannot have both 'except' and 'except*' on the same 'try' +13 | try: +14 | pass + | + + + | +17 | except: +18 | pass +19 | / except* ExceptionGroup: +20 | | pass + | |________^ Syntax Error: Cannot have both 'except' and 'except*' on the same 'try' +21 | except* ExceptionGroup: +22 | pass + | + + + | +19 | except* ExceptionGroup: +20 | pass +21 | / except* ExceptionGroup: +22 | | pass + | |________^ Syntax Error: Cannot have both 'except' and 'except*' on the same 'try' + |