Add support for TryStar (#3089)

This commit is contained in:
Charlie Marsh 2023-02-21 13:42:20 -05:00 committed by GitHub
parent 50ec6d3b0f
commit cdc4e86158
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 289 additions and 15 deletions

8
Cargo.lock generated
View file

@ -2146,7 +2146,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=61b48f108982d865524f86624a9d5bc2ae3bccef#61b48f108982d865524f86624a9d5bc2ae3bccef"
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
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=61b48f108982d865524f86624a9d5bc2ae3bccef#61b48f108982d865524f86624a9d5bc2ae3bccef"
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
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=61b48f108982d865524f86624a9d5bc2ae3bccef#61b48f108982d865524f86624a9d5bc2ae3bccef"
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
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=61b48f108982d865524f86624a9d5bc2ae3bccef#61b48f108982d865524f86624a9d5bc2ae3bccef"
source = "git+https://github.com/RustPython/RustPython.git?rev=ef873b4b606f0a58e3640b6186416631fdeead26#ef873b4b606f0a58e3640b6186416631fdeead26"
dependencies = [
"ahash",
"anyhow",

View file

@ -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 = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "ef873b4b606f0a58e3640b6186416631fdeead26" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "ef873b4b606f0a58e3640b6186416631fdeead26" }
schemars = { version = "0.8.11" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }

View file

@ -20,6 +20,24 @@ def bad():
logger.exception("something failed")
def bad():
try:
a = process()
if not a:
raise MyException(a)
raise MyException(a)
try:
b = process()
if not b:
raise MyException(b)
except* Exception:
logger.exception("something failed")
except* Exception:
logger.exception("something failed")
def good():
try:
a = process() # This throws the exception now

View file

@ -59,6 +59,12 @@ fn alternatives<'a>(stmt: &'a RefEquality<'a, Stmt>) -> Vec<Vec<RefEquality<'a,
handlers,
orelse,
..
}
| StmtKind::TryStar {
body,
handlers,
orelse,
..
} => vec![body.iter().chain(orelse.iter()).map(RefEquality).collect()]
.into_iter()
.chain(handlers.iter().map(|handler| {

View file

@ -654,6 +654,12 @@ pub enum ComparableStmt<'a> {
orelse: Vec<ComparableStmt<'a>>,
finalbody: Vec<ComparableStmt<'a>>,
},
TryStar {
body: Vec<ComparableStmt<'a>>,
handlers: Vec<ComparableExcepthandler<'a>>,
orelse: Vec<ComparableStmt<'a>>,
finalbody: Vec<ComparableStmt<'a>>,
},
Assert {
test: ComparableExpr<'a>,
msg: Option<ComparableExpr<'a>>,
@ -827,6 +833,17 @@ impl<'a> From<&'a Stmt> for ComparableStmt<'a> {
orelse: orelse.iter().map(Into::into).collect(),
finalbody: finalbody.iter().map(Into::into).collect(),
},
StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => Self::TryStar {
body: body.iter().map(Into::into).collect(),
handlers: handlers.iter().map(Into::into).collect(),
orelse: orelse.iter().map(Into::into).collect(),
finalbody: finalbody.iter().map(Into::into).collect(),
},
StmtKind::Assert { test, msg } => Self::Assert {
test: test.into(),
msg: msg.as_ref().map(Into::into),

View file

@ -391,6 +391,12 @@ where
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
any_over_body(body, func)
|| handlers.iter().any(|handler| {

View file

@ -198,7 +198,10 @@ pub fn in_nested_block<'a>(mut parents: impl Iterator<Item = &'a Stmt>) -> bool
parents.any(|parent| {
matches!(
parent.node,
StmtKind::Try { .. } | StmtKind::If { .. } | StmtKind::With { .. }
StmtKind::Try { .. }
| StmtKind::TryStar { .. }
| StmtKind::If { .. }
| StmtKind::With { .. }
)
})
}

View file

@ -232,6 +232,19 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
visitor.visit_body(orelse);
visitor.visit_body(finalbody);
}
StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
visitor.visit_body(body);
for excepthandler in handlers {
visitor.visit_excepthandler(excepthandler);
}
visitor.visit_body(orelse);
visitor.visit_body(finalbody);
}
StmtKind::Assert { test, msg } => {
visitor.visit_expr(test);
if let Some(expr) = msg {

View file

@ -53,6 +53,12 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))

View file

@ -1705,6 +1705,13 @@ where
orelse,
finalbody,
..
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
..
} => {
if self.settings.rules.enabled(&Rule::DefaultExceptNotLast) {
if let Some(diagnostic) =
@ -1992,6 +1999,12 @@ where
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
// TODO(charlie): The use of `smallvec` here leads to a lifetime issue.
let handler_names = extract_handler_names(handlers)

View file

@ -46,6 +46,7 @@ fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) {
}
StmtKind::If { body, .. }
| StmtKind::Try { body, .. }
| StmtKind::TryStar { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. } => {
walk_stmt(checker, body, f);

View file

@ -44,7 +44,8 @@ impl<'a> Visitor<'a> for RaiseVisitor {
StmtKind::ClassDef { .. }
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::Try { .. } => {}
| StmtKind::Try { .. }
| StmtKind::TryStar { .. } => {}
StmtKind::If { body, orelse, .. } => {
visitor::walk_body(self, body);
visitor::walk_body(self, orelse);

View file

@ -116,7 +116,8 @@ pub fn complex_raises(checker: &mut Checker, stmt: &Stmt, items: &[Withitem], bo
| StmtKind::For { .. }
| StmtKind::AsyncFor { .. }
| StmtKind::While { .. }
| StmtKind::Try { .. } => {
| StmtKind::Try { .. }
| StmtKind::TryStar { .. } => {
is_too_complex = true;
}
_ => {}

View file

@ -260,7 +260,10 @@ fn implicit_return(checker: &mut Checker, stmt: &Stmt) {
implicit_return(checker, last_stmt);
}
}
StmtKind::Return { .. } | StmtKind::Raise { .. } | StmtKind::Try { .. } => {}
StmtKind::Return { .. }
| StmtKind::Raise { .. }
| StmtKind::Try { .. }
| StmtKind::TryStar { .. } => {}
StmtKind::Expr { value, .. }
if matches!(
&value.node,

View file

@ -136,7 +136,7 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
visitor::walk_stmt(self, stmt);
self.parents.pop();
}
StmtKind::Try { .. } => {
StmtKind::Try { .. } | StmtKind::TryStar { .. } => {
self.stack
.tries
.push((stmt.location, stmt.end_location.unwrap()));

View file

@ -226,6 +226,12 @@ where
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
for excepthandler in handlers {
self.visit_excepthandler(excepthandler);

View file

@ -87,6 +87,12 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize {
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
complexity += 1;
complexity += get_complexity_number(body);

View file

@ -56,6 +56,12 @@ fn num_branches(stmts: &[Stmt]) -> usize {
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
1 + num_branches(body)
+ (if orelse.is_empty() {

View file

@ -54,6 +54,12 @@ fn num_statements(stmts: &[Stmt]) -> usize {
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
count += 1;
count += num_statements(body);

View file

@ -29,6 +29,13 @@ fn loop_exits_early(body: &[Stmt]) -> bool {
orelse,
finalbody,
..
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
..
} => {
loop_exits_early(body)
|| loop_exits_early(orelse)

View file

@ -29,7 +29,7 @@ where
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match stmt.node {
StmtKind::Raise { .. } => self.raises.push(stmt),
StmtKind::Try { .. } => (),
StmtKind::Try { .. } | StmtKind::TryStar { .. } => (),
_ => visitor::walk_stmt(self, stmt),
}
}

View file

@ -32,6 +32,9 @@ where
StmtKind::Raise { exc, cause } => self.raises.push((exc.as_deref(), cause.as_deref())),
StmtKind::Try {
body, finalbody, ..
}
| StmtKind::TryStar {
body, finalbody, ..
} => {
for stmt in body.iter().chain(finalbody.iter()) {
visitor::walk_stmt(self, stmt);

View file

@ -1,5 +1,5 @@
---
source: src/rules/tryceratops/mod.rs
source: crates/ruff/src/rules/tryceratops/mod.rs
expression: diagnostics
---
- kind:
@ -32,4 +32,34 @@ expression: diagnostics
column: 36
fix: ~
parent: ~
- kind:
RaiseWithinTry: ~
location:
row: 27
column: 12
end_location:
row: 27
column: 32
fix: ~
parent: ~
- kind:
RaiseWithinTry: ~
location:
row: 29
column: 8
end_location:
row: 29
column: 28
fix: ~
parent: ~
- kind:
RaiseWithinTry: ~
location:
row: 34
column: 16
end_location:
row: 34
column: 36
fix: ~
parent: ~

View file

@ -483,7 +483,37 @@ impl<'a> Generator<'a> {
for handler in handlers {
statement!({
self.unparse_excepthandler(handler);
self.unparse_excepthandler(handler, false);
});
}
if !orelse.is_empty() {
statement!({
self.p("else:");
});
self.body(orelse);
}
if !finalbody.is_empty() {
statement!({
self.p("finally:");
});
self.body(finalbody);
}
}
StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
statement!({
self.p("try:");
});
self.body(body);
for handler in handlers {
statement!({
self.unparse_excepthandler(handler, true);
});
}
@ -584,10 +614,13 @@ impl<'a> Generator<'a> {
}
}
fn unparse_excepthandler<U>(&mut self, ast: &Excepthandler<U>) {
fn unparse_excepthandler<U>(&mut self, ast: &Excepthandler<U>, star: bool) {
match &ast.node {
ExcepthandlerKind::ExceptHandler { type_, name, body } => {
self.p("except");
if star {
self.p("*");
}
if let Some(type_) = type_ {
self.p(" ");
self.unparse_expr(type_, precedence::MAX);
@ -1265,6 +1298,18 @@ mod tests {
r#"@functools.lru_cache(maxsize=None)
def f(x: int, y: int) -> int:
return x + y"#
);
assert_round_trip!(
r#"try:
pass
except Exception as e:
pass"#
);
assert_round_trip!(
r#"try:
pass
except* Exception as e:
pass"#
);
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"#);

View file

@ -234,6 +234,19 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a mut Stm
visitor.visit_body(orelse);
visitor.visit_body(finalbody);
}
StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
visitor.visit_body(body);
for excepthandler in handlers {
visitor.visit_excepthandler(excepthandler);
}
visitor.visit_body(orelse);
visitor.visit_body(finalbody);
}
StmtKind::Assert { test, msg } => {
visitor.visit_expr(test);
if let Some(expr) = msg {

View file

@ -249,6 +249,12 @@ pub enum StmtKind {
orelse: Vec<Stmt>,
finalbody: Vec<Stmt>,
},
TryStar {
body: Vec<Stmt>,
handlers: Vec<Excepthandler>,
orelse: Vec<Stmt>,
finalbody: Vec<Stmt>,
},
Assert {
test: Box<Expr>,
msg: Option<Box<Expr>>,
@ -815,6 +821,23 @@ impl From<rustpython_parser::ast::Stmt> for Stmt {
trivia: vec![],
parentheses: Parenthesize::Never,
},
rustpython_parser::ast::StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => Stmt {
location: stmt.location,
end_location: stmt.end_location,
node: StmtKind::TryStar {
body: body.into_iter().map(Into::into).collect(),
handlers: handlers.into_iter().map(Into::into).collect(),
orelse: orelse.into_iter().map(Into::into).collect(),
finalbody: finalbody.into_iter().map(Into::into).collect(),
},
trivia: vec![],
parentheses: Parenthesize::Never,
},
rustpython_parser::ast::StmtKind::Import { names } => Stmt {
location: stmt.location,
end_location: stmt.end_location,

View file

@ -476,6 +476,28 @@ fn format_try(
Ok(())
}
fn format_try_star(
f: &mut Formatter<ASTFormatContext<'_>>,
stmt: &Stmt,
body: &[Stmt],
handlers: &[Excepthandler],
orelse: &[Stmt],
finalbody: &[Stmt],
) -> FormatResult<()> {
write!(f, [text("try:"), block_indent(&block(body))])?;
for handler in handlers {
// TODO(charlie): Include `except*`.
write!(f, [handler.format()])?;
}
if !orelse.is_empty() {
write!(f, [text("else:"), block_indent(&block(orelse))])?;
}
if !finalbody.is_empty() {
write!(f, [text("finally:"), block_indent(&block(finalbody))])?;
}
Ok(())
}
fn format_assert(
f: &mut Formatter<ASTFormatContext<'_>>,
stmt: &Stmt,
@ -832,6 +854,12 @@ impl Format<ASTFormatContext<'_>> for FormatStmt<'_> {
orelse,
finalbody,
} => format_try(f, self.item, body, handlers, orelse, finalbody),
StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => format_try_star(f, self.item, body, handlers, orelse, finalbody),
StmtKind::Assert { test, msg } => {
format_assert(f, self.item, test, msg.as_ref().map(|expr| &**expr))
}

View file

@ -226,6 +226,12 @@ impl<'a> Visitor<'a> for NewlineNormalizer {
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
self.depth = Depth::Nested;
self.trailer = Trailer::CompoundStatement;

View file

@ -81,6 +81,7 @@ impl<'a> Visitor<'a> for ParenthesesNormalizer {
StmtKind::Match { .. } => {}
StmtKind::Raise { .. } => {}
StmtKind::Try { .. } => {}
StmtKind::TryStar { .. } => {}
StmtKind::Assert { test, msg } => {
use_inferred_parens(test);
if let Some(msg) = msg {

View file

@ -411,6 +411,12 @@ fn sorted_child_nodes_inner<'a>(node: &Node<'a>, result: &mut Vec<Node<'a>>) {
handlers,
orelse,
finalbody,
}
| StmtKind::TryStar {
body,
handlers,
orelse,
finalbody,
} => {
for stmt in body {
result.push(Node::Stmt(stmt));