mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-07 12:18:19 +00:00
[syntax-errors] Irrefutable case pattern before final case (#16905)
Summary -- Detects irrefutable `match` cases before the final case using a modified version of the existing `Pattern::is_irrefutable` method from the AST crate. The modified method helps to retrieve a more precise diagnostic range to match what Python 3.13 shows in the REPL. Test Plan -- New inline tests, as well as some updates to existing tests that had irrefutable patterns before the last block.
This commit is contained in:
parent
58350ec93b
commit
5697d21fca
17 changed files with 1420 additions and 594 deletions
|
@ -9,7 +9,7 @@ use std::fmt::Display;
|
|||
use ruff_python_ast::{
|
||||
self as ast,
|
||||
visitor::{walk_expr, Visitor},
|
||||
Expr, PythonVersion, Stmt, StmtExpr, StmtImportFrom,
|
||||
Expr, IrrefutablePatternKind, PythonVersion, Stmt, StmtExpr, StmtImportFrom,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
|
@ -54,27 +54,30 @@ impl SemanticSyntaxChecker {
|
|||
}
|
||||
|
||||
fn check_stmt<Ctx: SemanticSyntaxContext>(&mut self, stmt: &ast::Stmt, ctx: &Ctx) {
|
||||
if let Stmt::ImportFrom(StmtImportFrom { range, module, .. }) = stmt {
|
||||
if self.seen_futures_boundary && matches!(module.as_deref(), Some("__future__")) {
|
||||
Self::add_error(ctx, SemanticSyntaxErrorKind::LateFutureImport, *range);
|
||||
match stmt {
|
||||
Stmt::ImportFrom(StmtImportFrom { range, module, .. }) => {
|
||||
if self.seen_futures_boundary && matches!(module.as_deref(), Some("__future__")) {
|
||||
Self::add_error(ctx, SemanticSyntaxErrorKind::LateFutureImport, *range);
|
||||
}
|
||||
}
|
||||
Stmt::Match(match_stmt) => {
|
||||
Self::irrefutable_match_case(match_stmt, ctx);
|
||||
}
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { type_params, .. })
|
||||
| Stmt::ClassDef(ast::StmtClassDef { type_params, .. })
|
||||
| Stmt::TypeAlias(ast::StmtTypeAlias { type_params, .. }) => {
|
||||
if let Some(type_params) = type_params {
|
||||
Self::duplicate_type_parameter_name(type_params, ctx);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Self::duplicate_type_parameter_name(stmt, ctx);
|
||||
}
|
||||
|
||||
fn duplicate_type_parameter_name<Ctx: SemanticSyntaxContext>(stmt: &ast::Stmt, ctx: &Ctx) {
|
||||
let (Stmt::FunctionDef(ast::StmtFunctionDef { type_params, .. })
|
||||
| Stmt::ClassDef(ast::StmtClassDef { type_params, .. })
|
||||
| Stmt::TypeAlias(ast::StmtTypeAlias { type_params, .. })) = stmt
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(type_params) = type_params else {
|
||||
return;
|
||||
};
|
||||
|
||||
fn duplicate_type_parameter_name<Ctx: SemanticSyntaxContext>(
|
||||
type_params: &ast::TypeParams,
|
||||
ctx: &Ctx,
|
||||
) {
|
||||
if type_params.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
@ -109,6 +112,49 @@ impl SemanticSyntaxChecker {
|
|||
}
|
||||
}
|
||||
|
||||
fn irrefutable_match_case<Ctx: SemanticSyntaxContext>(stmt: &ast::StmtMatch, ctx: &Ctx) {
|
||||
// test_ok irrefutable_case_pattern_at_end
|
||||
// match x:
|
||||
// case 2: ...
|
||||
// case var: ...
|
||||
// match x:
|
||||
// case 2: ...
|
||||
// case _: ...
|
||||
// match x:
|
||||
// case var if True: ... # don't try to refute a guarded pattern
|
||||
// case 2: ...
|
||||
|
||||
// test_err irrefutable_case_pattern
|
||||
// match x:
|
||||
// case var: ... # capture pattern
|
||||
// case 2: ...
|
||||
// match x:
|
||||
// case _: ...
|
||||
// case 2: ... # wildcard pattern
|
||||
// match x:
|
||||
// case var1 as var2: ... # as pattern with irrefutable left-hand side
|
||||
// case 2: ...
|
||||
// match x:
|
||||
// case enum.variant | var: ... # or pattern with irrefutable part
|
||||
// case 2: ...
|
||||
for case in stmt
|
||||
.cases
|
||||
.iter()
|
||||
.rev()
|
||||
.skip(1)
|
||||
.filter_map(|case| match case.guard {
|
||||
Some(_) => None,
|
||||
None => case.pattern.irrefutable_pattern(),
|
||||
})
|
||||
{
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::IrrefutableCasePattern(case.kind),
|
||||
case.range,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visit_stmt<Ctx: SemanticSyntaxContext>(&mut self, stmt: &ast::Stmt, ctx: &Ctx) {
|
||||
// update internal state
|
||||
match stmt {
|
||||
|
@ -209,7 +255,7 @@ pub struct SemanticSyntaxError {
|
|||
|
||||
impl Display for SemanticSyntaxError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
match &self.kind {
|
||||
SemanticSyntaxErrorKind::LateFutureImport => {
|
||||
f.write_str("__future__ imports must be at the top of the file")
|
||||
}
|
||||
|
@ -219,11 +265,23 @@ impl Display for SemanticSyntaxError {
|
|||
SemanticSyntaxErrorKind::DuplicateTypeParameter => {
|
||||
f.write_str("duplicate type parameter")
|
||||
}
|
||||
SemanticSyntaxErrorKind::IrrefutableCasePattern(kind) => match kind {
|
||||
// These error messages are taken from CPython's syntax errors
|
||||
IrrefutablePatternKind::Name(name) => {
|
||||
write!(
|
||||
f,
|
||||
"name capture `{name}` makes remaining patterns unreachable"
|
||||
)
|
||||
}
|
||||
IrrefutablePatternKind::Wildcard => {
|
||||
f.write_str("wildcard makes remaining patterns unreachable")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SemanticSyntaxErrorKind {
|
||||
/// Represents the use of a `__future__` import after the beginning of a file.
|
||||
///
|
||||
|
@ -265,6 +323,26 @@ pub enum SemanticSyntaxErrorKind {
|
|||
/// class C[T, T]: ...
|
||||
/// ```
|
||||
DuplicateTypeParameter,
|
||||
|
||||
/// Represents an irrefutable `case` pattern before the last `case` in a `match` statement.
|
||||
///
|
||||
/// According to the [Python reference], "a match statement may have at most one irrefutable
|
||||
/// case block, and it must be last."
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```python
|
||||
/// match x:
|
||||
/// case value: ... # irrefutable capture pattern
|
||||
/// case other: ...
|
||||
///
|
||||
/// match x:
|
||||
/// case _: ... # irrefutable wildcard pattern
|
||||
/// case other: ...
|
||||
/// ```
|
||||
///
|
||||
/// [Python reference]: https://docs.python.org/3/reference/compound_stmts.html#irrefutable-case-blocks
|
||||
IrrefutableCasePattern(IrrefutablePatternKind),
|
||||
}
|
||||
|
||||
/// Searches for the first named expression (`x := y`) rebinding one of the `iteration_variables` in
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue