mirror of https://github.com/astral-sh/ruff
Add support for structural pattern matching (#3047)
This commit is contained in:
parent
cdc4e86158
commit
d5c65b5f1b
|
|
@ -2146,7 +2146,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ddf497623ae56d21aa4166ff1c0725a7db67e955#ddf497623ae56d21aa4166ff1c0725a7db67e955"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
|
|
@ -2155,7 +2155,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ddf497623ae56d21aa4166ff1c0725a7db67e955#ddf497623ae56d21aa4166ff1c0725a7db67e955"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"bitflags",
|
||||
|
|
@ -2180,7 +2180,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ddf497623ae56d21aa4166ff1c0725a7db67e955#ddf497623ae56d21aa4166ff1c0725a7db67e955"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
|
|
@ -2197,7 +2197,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=ddf497623ae56d21aa4166ff1c0725a7db67e955#ddf497623ae56d21aa4166ff1c0725a7db67e955"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a87
|
|||
once_cell = { version = "1.16.0" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "ef873b4b606f0a58e3640b6186416631fdeead26" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "ef873b4b606f0a58e3640b6186416631fdeead26" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "ddf497623ae56d21aa4166ff1c0725a7db67e955" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "ddf497623ae56d21aa4166ff1c0725a7db67e955" }
|
||||
schemars = { version = "0.8.11" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
|
|
|
|||
|
|
@ -105,3 +105,25 @@ while True:
|
|||
pass
|
||||
finally:
|
||||
break # warning
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
match *0, 1, *2:
|
||||
case 0,:
|
||||
y = 0
|
||||
case 0, *x:
|
||||
break # warning
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
match *0, 1, *2:
|
||||
case 0,:
|
||||
y = 0
|
||||
case 0, *x:
|
||||
pass # no warning
|
||||
|
|
|
|||
|
|
@ -62,3 +62,11 @@ except Exception as e:
|
|||
raise RuntimeError("boom!")
|
||||
else:
|
||||
raise RuntimeError("bang!")
|
||||
|
||||
|
||||
try:
|
||||
...
|
||||
except Exception as e:
|
||||
match 0:
|
||||
case 0:
|
||||
raise RuntimeError("boom!")
|
||||
|
|
|
|||
|
|
@ -266,3 +266,12 @@ def while_true():
|
|||
if y > 0:
|
||||
return 1
|
||||
y += 1
|
||||
|
||||
|
||||
# match
|
||||
def x(y):
|
||||
match y:
|
||||
case 0:
|
||||
return 1
|
||||
case 1:
|
||||
print() # error
|
||||
|
|
|
|||
|
|
@ -54,3 +54,6 @@ def f(): ...
|
|||
class C: ...; x = 1
|
||||
#: E701:1:8 E702:1:13
|
||||
class C: ...; ...
|
||||
#: E701:2:12
|
||||
match *0, 1, *2:
|
||||
case 0,: y = 0
|
||||
|
|
|
|||
|
|
@ -85,3 +85,10 @@ else:
|
|||
|
||||
|
||||
CustomInt: TypeAlias = "np.int8 | np.int16"
|
||||
|
||||
|
||||
# Test: match statements.
|
||||
match *0, 1, *2:
|
||||
case 0,:
|
||||
import x
|
||||
import y
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Test that shadowing a global with a class attribute does not produce a
|
||||
warning.
|
||||
"""
|
||||
Test that shadowing a global with a class attribute does not produce a
|
||||
warning.
|
||||
"""
|
||||
|
||||
import fu
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ def f():
|
|||
def connect():
|
||||
return None, None
|
||||
|
||||
with (connect() as (connection, cursor)):
|
||||
with connect() as (connection, cursor):
|
||||
cursor.execute("SELECT * FROM users")
|
||||
|
||||
|
||||
|
|
@ -94,3 +94,28 @@ def f():
|
|||
(exponential := (exponential * base_multiplier) % 3): i + 1 for i in range(2)
|
||||
}
|
||||
return hash_map
|
||||
|
||||
|
||||
def f(x: int):
|
||||
msg1 = "Hello, world!"
|
||||
msg2 = "Hello, world!"
|
||||
msg3 = "Hello, world!"
|
||||
match x:
|
||||
case 1:
|
||||
print(msg1)
|
||||
case 2:
|
||||
print(msg2)
|
||||
|
||||
|
||||
def f(x: int):
|
||||
import enum
|
||||
|
||||
Foo = enum.Enum("Foo", "A B")
|
||||
Bar = enum.Enum("Bar", "A B")
|
||||
Baz = enum.Enum("Baz", "A B")
|
||||
|
||||
match x:
|
||||
case (Foo.A):
|
||||
print("A")
|
||||
case [Bar.A, *_]:
|
||||
print("A")
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
use num_bigint::BigInt;
|
||||
use rustpython_parser::ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
|
||||
ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, Operator, Stmt, StmtKind, Unaryop,
|
||||
Withitem,
|
||||
ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, MatchCase, Operator, Pattern,
|
||||
PatternKind, Stmt, StmtKind, Unaryop, Withitem,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
|
@ -157,6 +157,110 @@ impl<'a> From<&'a Withitem> for ComparableWithitem<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparablePattern<'a> {
|
||||
MatchValue {
|
||||
value: ComparableExpr<'a>,
|
||||
},
|
||||
MatchSingleton {
|
||||
value: ComparableConstant<'a>,
|
||||
},
|
||||
MatchSequence {
|
||||
patterns: Vec<ComparablePattern<'a>>,
|
||||
},
|
||||
MatchMapping {
|
||||
keys: Vec<ComparableExpr<'a>>,
|
||||
patterns: Vec<ComparablePattern<'a>>,
|
||||
rest: Option<&'a str>,
|
||||
},
|
||||
MatchClass {
|
||||
cls: ComparableExpr<'a>,
|
||||
patterns: Vec<ComparablePattern<'a>>,
|
||||
kwd_attrs: Vec<&'a str>,
|
||||
kwd_patterns: Vec<ComparablePattern<'a>>,
|
||||
},
|
||||
MatchStar {
|
||||
name: Option<&'a str>,
|
||||
},
|
||||
MatchAs {
|
||||
pattern: Option<Box<ComparablePattern<'a>>>,
|
||||
name: Option<&'a str>,
|
||||
},
|
||||
MatchOr {
|
||||
patterns: Vec<ComparablePattern<'a>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Pattern> for ComparablePattern<'a> {
|
||||
fn from(pattern: &'a Pattern) -> Self {
|
||||
match &pattern.node {
|
||||
PatternKind::MatchValue { value } => Self::MatchValue {
|
||||
value: value.into(),
|
||||
},
|
||||
PatternKind::MatchSingleton { value } => Self::MatchSingleton {
|
||||
value: value.into(),
|
||||
},
|
||||
PatternKind::MatchSequence { patterns } => Self::MatchSequence {
|
||||
patterns: patterns.iter().map(Into::into).collect(),
|
||||
},
|
||||
PatternKind::MatchMapping {
|
||||
keys,
|
||||
patterns,
|
||||
rest,
|
||||
} => Self::MatchMapping {
|
||||
keys: keys.iter().map(Into::into).collect(),
|
||||
patterns: patterns.iter().map(Into::into).collect(),
|
||||
rest: rest.as_deref(),
|
||||
},
|
||||
PatternKind::MatchClass {
|
||||
cls,
|
||||
patterns,
|
||||
kwd_attrs,
|
||||
kwd_patterns,
|
||||
} => Self::MatchClass {
|
||||
cls: cls.into(),
|
||||
patterns: patterns.iter().map(Into::into).collect(),
|
||||
kwd_attrs: kwd_attrs.iter().map(String::as_str).collect(),
|
||||
kwd_patterns: kwd_patterns.iter().map(Into::into).collect(),
|
||||
},
|
||||
PatternKind::MatchStar { name } => Self::MatchStar {
|
||||
name: name.as_deref(),
|
||||
},
|
||||
PatternKind::MatchAs { pattern, name } => Self::MatchAs {
|
||||
pattern: pattern.as_ref().map(Into::into),
|
||||
name: name.as_deref(),
|
||||
},
|
||||
PatternKind::MatchOr { patterns } => Self::MatchOr {
|
||||
patterns: patterns.iter().map(Into::into).collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<Pattern>> for Box<ComparablePattern<'a>> {
|
||||
fn from(pattern: &'a Box<Pattern>) -> Self {
|
||||
Box::new((&**pattern).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableMatchCase<'a> {
|
||||
pub pattern: ComparablePattern<'a>,
|
||||
pub guard: Option<ComparableExpr<'a>>,
|
||||
pub body: Vec<ComparableStmt<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MatchCase> for ComparableMatchCase<'a> {
|
||||
fn from(match_case: &'a MatchCase) -> Self {
|
||||
Self {
|
||||
pattern: (&match_case.pattern).into(),
|
||||
guard: match_case.guard.as_ref().map(Into::into),
|
||||
body: match_case.body.iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableConstant<'a> {
|
||||
None,
|
||||
|
|
@ -644,6 +748,10 @@ pub enum ComparableStmt<'a> {
|
|||
body: Vec<ComparableStmt<'a>>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
Match {
|
||||
subject: ComparableExpr<'a>,
|
||||
cases: Vec<ComparableMatchCase<'a>>,
|
||||
},
|
||||
Raise {
|
||||
exc: Option<ComparableExpr<'a>>,
|
||||
cause: Option<ComparableExpr<'a>>,
|
||||
|
|
@ -817,7 +925,10 @@ impl<'a> From<&'a Stmt> for ComparableStmt<'a> {
|
|||
body: body.iter().map(Into::into).collect(),
|
||||
type_comment: type_comment.as_ref().map(String::as_str),
|
||||
},
|
||||
StmtKind::Match { .. } => unreachable!("StmtKind::Match is not supported"),
|
||||
StmtKind::Match { subject, cases } => Self::Match {
|
||||
subject: subject.into(),
|
||||
cases: cases.iter().map(Into::into).collect(),
|
||||
},
|
||||
StmtKind::Raise { exc, cause } => Self::Raise {
|
||||
exc: exc.as_ref().map(Into::into),
|
||||
cause: cause.as_ref().map(Into::into),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use regex::Regex;
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_parser::ast::{
|
||||
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData,
|
||||
Located, Location, Stmt, StmtKind,
|
||||
Located, Location, MatchCase, Pattern, PatternKind, Stmt, StmtKind,
|
||||
};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::Tok;
|
||||
|
|
@ -249,6 +249,46 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn any_over_pattern<F>(pattern: &Pattern, func: &F) -> bool
|
||||
where
|
||||
F: Fn(&Expr) -> bool,
|
||||
{
|
||||
match &pattern.node {
|
||||
PatternKind::MatchValue { value } => any_over_expr(value, func),
|
||||
PatternKind::MatchSingleton { .. } => false,
|
||||
PatternKind::MatchSequence { patterns } => patterns
|
||||
.iter()
|
||||
.any(|pattern| any_over_pattern(pattern, func)),
|
||||
PatternKind::MatchMapping { keys, patterns, .. } => {
|
||||
keys.iter().any(|key| any_over_expr(key, func))
|
||||
|| patterns
|
||||
.iter()
|
||||
.any(|pattern| any_over_pattern(pattern, func))
|
||||
}
|
||||
PatternKind::MatchClass {
|
||||
cls,
|
||||
patterns,
|
||||
kwd_patterns,
|
||||
..
|
||||
} => {
|
||||
any_over_expr(cls, func)
|
||||
|| patterns
|
||||
.iter()
|
||||
.any(|pattern| any_over_pattern(pattern, func))
|
||||
|| kwd_patterns
|
||||
.iter()
|
||||
.any(|pattern| any_over_pattern(pattern, func))
|
||||
}
|
||||
PatternKind::MatchStar { .. } => false,
|
||||
PatternKind::MatchAs { pattern, .. } => pattern
|
||||
.as_ref()
|
||||
.map_or(false, |pattern| any_over_pattern(pattern, func)),
|
||||
PatternKind::MatchOr { patterns } => patterns
|
||||
.iter()
|
||||
.any(|pattern| any_over_pattern(pattern, func)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn any_over_stmt<F>(stmt: &Stmt, func: &F) -> bool
|
||||
where
|
||||
F: Fn(&Expr) -> bool,
|
||||
|
|
@ -415,8 +455,21 @@ where
|
|||
.as_ref()
|
||||
.map_or(false, |value| any_over_expr(value, func))
|
||||
}
|
||||
// TODO(charlie): Handle match statements.
|
||||
StmtKind::Match { .. } => false,
|
||||
StmtKind::Match { subject, cases } => {
|
||||
any_over_expr(subject, func)
|
||||
|| cases.iter().any(|case| {
|
||||
let MatchCase {
|
||||
pattern,
|
||||
guard,
|
||||
body,
|
||||
} = case;
|
||||
any_over_pattern(pattern, func)
|
||||
|| guard
|
||||
.as_ref()
|
||||
.map_or(false, |expr| any_over_expr(expr, func))
|
||||
|| any_over_body(body, func)
|
||||
})
|
||||
}
|
||||
StmtKind::Import { .. } => false,
|
||||
StmtKind::ImportFrom { .. } => false,
|
||||
StmtKind::Global { .. } => false,
|
||||
|
|
|
|||
|
|
@ -181,7 +181,10 @@ pub fn extract_globals(body: &[Stmt]) -> FxHashMap<&str, &Stmt> {
|
|||
/// Check if a node is parent of a conditional branch.
|
||||
pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
|
||||
parents.any(|parent| {
|
||||
if matches!(parent.node, StmtKind::If { .. } | StmtKind::While { .. }) {
|
||||
if matches!(
|
||||
parent.node,
|
||||
StmtKind::If { .. } | StmtKind::While { .. } | StmtKind::Match { .. }
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if let StmtKind::Expr { value } = &parent.node {
|
||||
|
|
|
|||
|
|
@ -205,7 +205,6 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
|||
visitor.visit_body(body);
|
||||
}
|
||||
StmtKind::Match { subject, cases } => {
|
||||
// TODO(charlie): Handle `cases`.
|
||||
visitor.visit_expr(subject);
|
||||
for match_case in cases {
|
||||
visitor.visit_match_case(match_case);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,19 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
|||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
if let Some(body) = cases.iter().find_map(|case| {
|
||||
if case.body.iter().contains(child) {
|
||||
Some(&case.body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
_ => bail!("Unable to find child in parent body"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) {
|
|||
| StmtKind::AsyncWith { body, .. } => {
|
||||
walk_stmt(checker, body, f);
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
for case in cases {
|
||||
walk_stmt(checker, &case.body, f);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,11 +57,17 @@ impl<'a> Visitor<'a> for RaiseVisitor {
|
|||
| StmtKind::AsyncFor { body, .. } => {
|
||||
visitor::walk_body(self, body);
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
for case in cases {
|
||||
visitor::walk_body(self, &case.body);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// B904
|
||||
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
|
||||
let mut visitor = RaiseVisitor {
|
||||
diagnostics: vec![],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
source: src/rules/flake8_bugbear/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
|
|
@ -112,4 +112,15 @@ expression: diagnostics
|
|||
column: 13
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
JumpStatementInFinally:
|
||||
name: break
|
||||
location:
|
||||
row: 118
|
||||
column: 16
|
||||
end_location:
|
||||
row: 118
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -52,4 +52,14 @@ expression: diagnostics
|
|||
column: 35
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
RaiseWithoutFromInsideExcept: ~
|
||||
location:
|
||||
row: 72
|
||||
column: 12
|
||||
end_location:
|
||||
row: 72
|
||||
column: 39
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ pub fn complex_raises(checker: &mut Checker, stmt: &Stmt, items: &[Withitem], bo
|
|||
}
|
||||
StmtKind::If { .. }
|
||||
| StmtKind::For { .. }
|
||||
| StmtKind::Match { .. }
|
||||
| StmtKind::AsyncFor { .. }
|
||||
| StmtKind::While { .. }
|
||||
| StmtKind::Try { .. }
|
||||
|
|
|
|||
|
|
@ -255,6 +255,13 @@ fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
|
|||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
for case in cases {
|
||||
if let Some(last_stmt) = case.body.last() {
|
||||
implicit_return(checker, last_stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::With { body, .. } | StmtKind::AsyncWith { body, .. } => {
|
||||
if let Some(last_stmt) = body.last() {
|
||||
implicit_return(checker, last_stmt);
|
||||
|
|
|
|||
|
|
@ -223,4 +223,21 @@ expression: diagnostics
|
|||
row: 261
|
||||
column: 20
|
||||
parent: ~
|
||||
- kind:
|
||||
ImplicitReturn: ~
|
||||
location:
|
||||
row: 277
|
||||
column: 12
|
||||
end_location:
|
||||
row: 277
|
||||
column: 19
|
||||
fix:
|
||||
content: "\n return None"
|
||||
location:
|
||||
row: 277
|
||||
column: 19
|
||||
end_location:
|
||||
row: 277
|
||||
column: 19
|
||||
parent: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,8 @@ where
|
|||
}
|
||||
self.finalize(None);
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
StmtKind::Match { subject, cases } => {
|
||||
self.visit_expr(subject);
|
||||
for match_case in cases {
|
||||
self.visit_match_case(match_case);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,12 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize {
|
|||
complexity += 1;
|
||||
}
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
complexity += 1;
|
||||
for case in cases {
|
||||
complexity += get_complexity_number(&case.body);
|
||||
}
|
||||
}
|
||||
StmtKind::Try {
|
||||
body,
|
||||
handlers,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ pub fn compound_statements(
|
|||
// Track the last seen instance of a variety of tokens.
|
||||
let mut colon = None;
|
||||
let mut semi = None;
|
||||
let mut case = None;
|
||||
let mut class = None;
|
||||
let mut elif = None;
|
||||
let mut else_ = None;
|
||||
|
|
@ -59,6 +60,7 @@ pub fn compound_statements(
|
|||
let mut finally = None;
|
||||
let mut for_ = None;
|
||||
let mut if_ = None;
|
||||
let mut match_ = None;
|
||||
let mut try_ = None;
|
||||
let mut while_ = None;
|
||||
let mut with = None;
|
||||
|
|
@ -114,6 +116,7 @@ pub fn compound_statements(
|
|||
// Reset.
|
||||
colon = None;
|
||||
semi = None;
|
||||
case = None;
|
||||
class = None;
|
||||
elif = None;
|
||||
else_ = None;
|
||||
|
|
@ -121,18 +124,21 @@ pub fn compound_statements(
|
|||
finally = None;
|
||||
for_ = None;
|
||||
if_ = None;
|
||||
match_ = None;
|
||||
try_ = None;
|
||||
while_ = None;
|
||||
with = None;
|
||||
}
|
||||
Tok::Colon => {
|
||||
if class.is_some()
|
||||
if case.is_some()
|
||||
|| class.is_some()
|
||||
|| elif.is_some()
|
||||
|| else_.is_some()
|
||||
|| except.is_some()
|
||||
|| finally.is_some()
|
||||
|| for_.is_some()
|
||||
|| if_.is_some()
|
||||
|| match_.is_some()
|
||||
|| try_.is_some()
|
||||
|| while_.is_some()
|
||||
|| with.is_some()
|
||||
|
|
@ -168,6 +174,7 @@ pub fn compound_statements(
|
|||
|
||||
// Reset.
|
||||
colon = None;
|
||||
case = None;
|
||||
class = None;
|
||||
elif = None;
|
||||
else_ = None;
|
||||
|
|
@ -175,6 +182,7 @@ pub fn compound_statements(
|
|||
finally = None;
|
||||
for_ = None;
|
||||
if_ = None;
|
||||
match_ = None;
|
||||
try_ = None;
|
||||
while_ = None;
|
||||
with = None;
|
||||
|
|
@ -186,6 +194,7 @@ pub fn compound_statements(
|
|||
Tok::Lambda => {
|
||||
// Reset.
|
||||
colon = None;
|
||||
case = None;
|
||||
class = None;
|
||||
elif = None;
|
||||
else_ = None;
|
||||
|
|
@ -193,10 +202,14 @@ pub fn compound_statements(
|
|||
finally = None;
|
||||
for_ = None;
|
||||
if_ = None;
|
||||
match_ = None;
|
||||
try_ = None;
|
||||
while_ = None;
|
||||
with = None;
|
||||
}
|
||||
Tok::Case => {
|
||||
case = Some((start, end));
|
||||
}
|
||||
Tok::If => {
|
||||
if_ = Some((start, end));
|
||||
}
|
||||
|
|
@ -227,6 +240,9 @@ pub fn compound_statements(
|
|||
Tok::With => {
|
||||
with = Some((start, end));
|
||||
}
|
||||
Tok::Match => {
|
||||
match_ = Some((start, end));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,4 +132,14 @@ expression: diagnostics
|
|||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultipleStatementsOnOneLineColon: ~
|
||||
location:
|
||||
row: 59
|
||||
column: 11
|
||||
end_location:
|
||||
row: 59
|
||||
column: 12
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -144,4 +144,44 @@ expression: diagnostics
|
|||
row: 52
|
||||
column: 21
|
||||
parent: ~
|
||||
- kind:
|
||||
UnusedImport:
|
||||
name: x
|
||||
ignore_init: false
|
||||
multiple: false
|
||||
location:
|
||||
row: 93
|
||||
column: 15
|
||||
end_location:
|
||||
row: 93
|
||||
column: 16
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 93
|
||||
column: 0
|
||||
end_location:
|
||||
row: 94
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
UnusedImport:
|
||||
name: y
|
||||
ignore_init: false
|
||||
multiple: false
|
||||
location:
|
||||
row: 94
|
||||
column: 15
|
||||
end_location:
|
||||
row: 94
|
||||
column: 16
|
||||
fix:
|
||||
content: pass
|
||||
location:
|
||||
row: 94
|
||||
column: 8
|
||||
end_location:
|
||||
row: 94
|
||||
column: 16
|
||||
parent: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -150,4 +150,40 @@ expression: diagnostics
|
|||
row: 85
|
||||
column: 31
|
||||
parent: ~
|
||||
- kind:
|
||||
UnusedVariable:
|
||||
name: msg3
|
||||
location:
|
||||
row: 102
|
||||
column: 4
|
||||
end_location:
|
||||
row: 102
|
||||
column: 8
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 102
|
||||
column: 0
|
||||
end_location:
|
||||
row: 103
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
UnusedVariable:
|
||||
name: Baz
|
||||
location:
|
||||
row: 115
|
||||
column: 4
|
||||
end_location:
|
||||
row: 115
|
||||
column: 7
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 115
|
||||
column: 4
|
||||
end_location:
|
||||
row: 115
|
||||
column: 10
|
||||
parent: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -186,4 +186,40 @@ expression: diagnostics
|
|||
row: 85
|
||||
column: 31
|
||||
parent: ~
|
||||
- kind:
|
||||
UnusedVariable:
|
||||
name: msg3
|
||||
location:
|
||||
row: 102
|
||||
column: 4
|
||||
end_location:
|
||||
row: 102
|
||||
column: 8
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 102
|
||||
column: 0
|
||||
end_location:
|
||||
row: 103
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
UnusedVariable:
|
||||
name: Baz
|
||||
location:
|
||||
row: 115
|
||||
column: 4
|
||||
end_location:
|
||||
row: 115
|
||||
column: 7
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 115
|
||||
column: 4
|
||||
end_location:
|
||||
row: 115
|
||||
column: 10
|
||||
parent: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ fn num_branches(stmts: &[Stmt]) -> usize {
|
|||
stmts
|
||||
.iter()
|
||||
.map(|stmt| {
|
||||
// TODO(charlie): Account for pattern match statement.
|
||||
match &stmt.node {
|
||||
StmtKind::If { body, orelse, .. } => {
|
||||
1 + num_branches(body)
|
||||
|
|
@ -41,6 +40,12 @@ fn num_branches(stmts: &[Stmt]) -> usize {
|
|||
})
|
||||
+ num_branches(orelse)
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
1 + cases
|
||||
.iter()
|
||||
.map(|case| num_branches(&case.body))
|
||||
.sum::<usize>()
|
||||
}
|
||||
StmtKind::For { body, orelse, .. }
|
||||
| StmtKind::AsyncFor { body, orelse, .. }
|
||||
| StmtKind::While { body, orelse, .. } => {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ impl Violation for TooManyStatements {
|
|||
fn num_statements(stmts: &[Stmt]) -> usize {
|
||||
let mut count = 0;
|
||||
for stmt in stmts {
|
||||
// TODO(charlie): Account for pattern match statement.
|
||||
match &stmt.node {
|
||||
StmtKind::If { body, orelse, .. } => {
|
||||
count += 1;
|
||||
|
|
@ -49,6 +48,12 @@ fn num_statements(stmts: &[Stmt]) -> usize {
|
|||
count += num_statements(body);
|
||||
count += num_statements(orelse);
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
count += 1;
|
||||
for case in cases {
|
||||
count += num_statements(&case.body);
|
||||
}
|
||||
}
|
||||
StmtKind::Try {
|
||||
body,
|
||||
handlers,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use std::ops::Deref;
|
|||
|
||||
use rustpython_parser::ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, ConversionFlag, Excepthandler,
|
||||
ExcepthandlerKind, Expr, ExprKind, Operator, Stmt, StmtKind, Suite, Withitem,
|
||||
ExcepthandlerKind, Expr, ExprKind, MatchCase, Operator, Pattern, PatternKind, Stmt, StmtKind,
|
||||
Suite, Withitem,
|
||||
};
|
||||
|
||||
use crate::source_code::stylist::{Indentation, LineEnding, Quote, Stylist};
|
||||
|
|
@ -456,7 +457,20 @@ impl<'a> Generator<'a> {
|
|||
});
|
||||
self.body(body);
|
||||
}
|
||||
StmtKind::Match { .. } => {}
|
||||
StmtKind::Match { subject, cases } => {
|
||||
statement!({
|
||||
self.p("match ");
|
||||
self.unparse_expr(subject, precedence::MAX);
|
||||
self.p(":");
|
||||
});
|
||||
for case in cases {
|
||||
self.indent_depth += 1;
|
||||
statement!({
|
||||
self.unparse_match_case(case);
|
||||
});
|
||||
self.indent_depth -= 1;
|
||||
}
|
||||
}
|
||||
StmtKind::Raise { exc, cause } => {
|
||||
statement!({
|
||||
self.p("raise");
|
||||
|
|
@ -635,6 +649,84 @@ impl<'a> Generator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn unparse_pattern<U>(&mut self, ast: &Pattern<U>) {
|
||||
match &ast.node {
|
||||
PatternKind::MatchValue { value } => {
|
||||
self.unparse_expr(value, precedence::MAX);
|
||||
}
|
||||
PatternKind::MatchSingleton { value } => {
|
||||
self.unparse_constant(value);
|
||||
}
|
||||
PatternKind::MatchSequence { patterns } => {
|
||||
self.p("[");
|
||||
let mut first = true;
|
||||
for pattern in patterns {
|
||||
self.p_delim(&mut first, ", ");
|
||||
self.unparse_pattern(pattern);
|
||||
}
|
||||
self.p("]");
|
||||
}
|
||||
PatternKind::MatchMapping {
|
||||
keys,
|
||||
patterns,
|
||||
rest,
|
||||
} => {
|
||||
self.p("{");
|
||||
let mut first = true;
|
||||
for (key, pattern) in keys.iter().zip(patterns) {
|
||||
self.p_delim(&mut first, ", ");
|
||||
self.unparse_expr(key, precedence::MAX);
|
||||
self.p(": ");
|
||||
self.unparse_pattern(pattern);
|
||||
}
|
||||
if let Some(rest) = rest {
|
||||
self.p_delim(&mut first, ", ");
|
||||
self.p("**");
|
||||
self.p(rest);
|
||||
}
|
||||
self.p("}");
|
||||
}
|
||||
PatternKind::MatchClass { .. } => {}
|
||||
PatternKind::MatchStar { name } => {
|
||||
self.p("*");
|
||||
if let Some(name) = name {
|
||||
self.p(name);
|
||||
} else {
|
||||
self.p("_");
|
||||
}
|
||||
}
|
||||
PatternKind::MatchAs { pattern, name } => {
|
||||
if let Some(pattern) = pattern {
|
||||
self.unparse_pattern(pattern);
|
||||
self.p(" as ");
|
||||
}
|
||||
if let Some(name) = name {
|
||||
self.p(name);
|
||||
} else {
|
||||
self.p("_");
|
||||
}
|
||||
}
|
||||
PatternKind::MatchOr { patterns } => {
|
||||
let mut first = true;
|
||||
for pattern in patterns {
|
||||
self.p_delim(&mut first, " | ");
|
||||
self.unparse_pattern(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unparse_match_case<U>(&mut self, ast: &MatchCase<U>) {
|
||||
self.p("case ");
|
||||
self.unparse_pattern(&ast.pattern);
|
||||
if let Some(guard) = &ast.guard {
|
||||
self.p(" if ");
|
||||
self.unparse_expr(guard, precedence::MAX);
|
||||
}
|
||||
self.p(":");
|
||||
self.body(&ast.body);
|
||||
}
|
||||
|
||||
pub fn unparse_expr<U>(&mut self, ast: &Expr<U>, level: u8) {
|
||||
macro_rules! opprec {
|
||||
($opty:ident, $x:expr, $enu:path, $($var:ident($op:literal, $prec:ident)),*$(,)?) => {
|
||||
|
|
@ -1310,6 +1402,13 @@ except Exception as e:
|
|||
pass
|
||||
except* Exception as e:
|
||||
pass"#
|
||||
);
|
||||
assert_round_trip!(
|
||||
r#"match x:
|
||||
case [1, 2, 3]:
|
||||
return 2
|
||||
case 4 as y:
|
||||
return y"#
|
||||
);
|
||||
assert_eq!(round_trip(r#"x = (1, 2, 3)"#), r#"x = 1, 2, 3"#);
|
||||
assert_eq!(round_trip(r#"-(1) + ~(2) + +(3)"#), r#"-1 + ~2 + +3"#);
|
||||
|
|
|
|||
Loading…
Reference in New Issue