[syntax-errors] Limit except* range to * (#16473)

Summary
--
This is a follow-up to #16446 to fix the diagnostic range to point to
the `*` like `pyright` does
(https://github.com/astral-sh/ruff/pull/16446#discussion_r1976900643).

Storing the range in the `ExceptClauseKind::Star` variant feels slightly
awkward, but we don't store the star itself anywhere on the
`ExceptHandler`. And we can't just take `ExceptHandler.start() +
"except".text_len()` because this code appears to be valid:

```python
try: ...
except    *    Error: ...
```

Test Plan
--
Existing tests.
This commit is contained in:
Brent Westbrook 2025-03-04 11:50:09 -05:00 committed by GitHub
parent 1977dda079
commit c8a06a9be8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 94 additions and 16 deletions

View file

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

View file

@ -7,7 +7,7 @@ use ruff_python_ast::name::Name;
use ruff_python_ast::{
self as ast, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, Stmt, WithItem,
};
use ruff_text_size::{Ranged, TextSize};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::parser::expression::{ParsedExpr, EXPR_SET};
use crate::parser::progress::ParserProgress;
@ -1377,6 +1377,9 @@ impl<'src> Parser<'src> {
let mut mixed_except_ranges = Vec::new();
let handlers = self.parse_clauses(Clause::Except, |p| {
let (handler, kind) = p.parse_except_clause();
if let ExceptClauseKind::Star(range) = kind {
p.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::ExceptStar, range);
}
if is_star.is_none() {
is_star = Some(kind.is_star());
} else if is_star != Some(kind.is_star()) {
@ -1466,11 +1469,8 @@ impl<'src> Parser<'src> {
// # 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);
}
// except* KeyError: ...
// except * Error: ...
ast::StmtTry {
body: try_body,
@ -1478,7 +1478,7 @@ impl<'src> Parser<'src> {
orelse,
finalbody,
is_star,
range,
range: self.node_range(try_start),
}
}
@ -1491,8 +1491,9 @@ impl<'src> Parser<'src> {
let start = self.node_start();
self.bump(TokenKind::Except);
let star_token_range = self.current_token_range();
let block_kind = if self.eat(TokenKind::Star) {
ExceptClauseKind::Star
ExceptClauseKind::Star(star_token_range)
} else {
ExceptClauseKind::Normal
};
@ -3650,12 +3651,14 @@ enum ExceptClauseKind {
/// A normal except clause e.g., `except Exception as e: ...`.
Normal,
/// An except clause with a star e.g., `except *: ...`.
Star,
///
/// Contains the star's [`TextRange`] for error reporting.
Star(TextRange),
}
impl ExceptClauseKind {
const fn is_star(self) -> bool {
matches!(self, ExceptClauseKind::Star)
matches!(self, ExceptClauseKind::Star(..))
}
}

View file

@ -7,11 +7,11 @@ input_file: crates/ruff_python_parser/resources/inline/err/except_star_py310.py
```
Module(
ModModule {
range: 0..77,
range: 0..126,
body: [
Try(
StmtTry {
range: 44..76,
range: 44..125,
body: [
Expr(
StmtExpr {
@ -52,6 +52,60 @@ Module(
],
},
),
ExceptHandler(
ExceptHandlerExceptHandler {
range: 77..98,
type_: Some(
Name(
ExprName {
range: 85..93,
id: Name("KeyError"),
ctx: Load,
},
),
),
name: None,
body: [
Expr(
StmtExpr {
range: 95..98,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 95..98,
},
),
},
),
],
},
),
ExceptHandler(
ExceptHandlerExceptHandler {
range: 99..125,
type_: Some(
Name(
ExprName {
range: 115..120,
id: Name("Error"),
ctx: Load,
},
),
),
name: None,
body: [
Expr(
StmtExpr {
range: 122..125,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 122..125,
},
),
},
),
],
},
),
],
orelse: [],
finalbody: [],
@ -65,8 +119,27 @@ Module(
## 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)
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)
4 | except* KeyError: ...
5 | except * Error: ...
|
|
2 | try: ...
3 | except* ValueError: ...
4 | except* KeyError: ...
| ^ Syntax Error: Cannot use `except*` on Python 3.10 (syntax was added in Python 3.11)
5 | except * Error: ...
|
|
3 | except* ValueError: ...
4 | except* KeyError: ...
5 | except * Error: ...
| ^ Syntax Error: Cannot use `except*` on Python 3.10 (syntax was added in Python 3.11)
|