mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00

**Summary** [Option::is_some_and](https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.is_some_and) and [Result::is_ok_and](https://doc.rust-lang.org/std/result/enum.Result.html#method.is_ok_and) are new methods is rust 1.70. I find them way more readable than `.map_or(false, ...)`. The changes are `s/.map_or(false,/.is_some_and(/g`, then manually switching to `is_ok_and` where the value is a Result rather than an Option. **Test Plan** n/a^
111 lines
3.2 KiB
Rust
111 lines
3.2 KiB
Rust
use std::cmp::Ordering;
|
|
use std::iter;
|
|
|
|
use ruff_python_ast::{self as ast, ExceptHandler, Stmt};
|
|
|
|
use crate::node::{NodeId, Nodes};
|
|
|
|
/// Return the common ancestor of `left` and `right` below `stop`, or `None`.
|
|
fn common_ancestor(
|
|
left: NodeId,
|
|
right: NodeId,
|
|
stop: Option<NodeId>,
|
|
node_tree: &Nodes,
|
|
) -> Option<NodeId> {
|
|
if stop.is_some_and(|stop| left == stop || right == stop) {
|
|
return None;
|
|
}
|
|
|
|
if left == right {
|
|
return Some(left);
|
|
}
|
|
|
|
let left_depth = node_tree.depth(left);
|
|
let right_depth = node_tree.depth(right);
|
|
|
|
match left_depth.cmp(&right_depth) {
|
|
Ordering::Less => {
|
|
let right = node_tree.parent_id(right)?;
|
|
common_ancestor(left, right, stop, node_tree)
|
|
}
|
|
Ordering::Equal => {
|
|
let left = node_tree.parent_id(left)?;
|
|
let right = node_tree.parent_id(right)?;
|
|
common_ancestor(left, right, stop, node_tree)
|
|
}
|
|
Ordering::Greater => {
|
|
let left = node_tree.parent_id(left)?;
|
|
common_ancestor(left, right, stop, node_tree)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return the alternative branches for a given node.
|
|
fn alternatives(stmt: &Stmt) -> Vec<Vec<&Stmt>> {
|
|
match stmt {
|
|
Stmt::If(ast::StmtIf {
|
|
body,
|
|
elif_else_clauses,
|
|
..
|
|
}) => iter::once(body.iter().collect())
|
|
.chain(
|
|
elif_else_clauses
|
|
.iter()
|
|
.map(|clause| clause.body.iter().collect()),
|
|
)
|
|
.collect(),
|
|
Stmt::Try(ast::StmtTry {
|
|
body,
|
|
handlers,
|
|
orelse,
|
|
..
|
|
})
|
|
| Stmt::TryStar(ast::StmtTryStar {
|
|
body,
|
|
handlers,
|
|
orelse,
|
|
..
|
|
}) => vec![body.iter().chain(orelse.iter()).collect()]
|
|
.into_iter()
|
|
.chain(handlers.iter().map(|handler| {
|
|
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { body, .. }) =
|
|
handler;
|
|
body.iter().collect()
|
|
}))
|
|
.collect(),
|
|
Stmt::Match(ast::StmtMatch { cases, .. }) => cases
|
|
.iter()
|
|
.map(|case| case.body.iter().collect())
|
|
.collect(),
|
|
_ => vec![],
|
|
}
|
|
}
|
|
|
|
/// Return `true` if `stmt` is a descendent of any of the nodes in `ancestors`.
|
|
fn descendant_of<'a>(
|
|
stmt: NodeId,
|
|
ancestors: &[&'a Stmt],
|
|
stop: NodeId,
|
|
node_tree: &Nodes<'a>,
|
|
) -> bool {
|
|
ancestors.iter().any(|ancestor| {
|
|
node_tree.node_id(ancestor).is_some_and(|ancestor| {
|
|
common_ancestor(stmt, ancestor, Some(stop), node_tree).is_some()
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Return `true` if `left` and `right` are on different branches of an `if` or
|
|
/// `try` statement.
|
|
pub fn different_forks(left: NodeId, right: NodeId, node_tree: &Nodes) -> bool {
|
|
if let Some(ancestor) = common_ancestor(left, right, None, node_tree) {
|
|
for items in alternatives(node_tree[ancestor]) {
|
|
let l = descendant_of(left, &items, ancestor, node_tree);
|
|
let r = descendant_of(right, &items, ancestor, node_tree);
|
|
if l ^ r {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|