ruff/crates/ruff_python_semantic/src/analyze/branch_detection.rs
konsti 1df7e9831b
Replace .map_or(false, $closure) with .is_some_and(closure) (#6244)
**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^
2023-08-01 19:29:42 +02:00

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
}