Use separate structs for expression and statement tracking (#6351)

## Summary

This PR fixes the performance degradation introduced in
https://github.com/astral-sh/ruff/pull/6345. Instead of using the
generic `Nodes` structs, we now use separate `Statement` and
`Expression` structs. Importantly, we can avoid tracking a bunch of
state for expressions that we need for parents: we don't need to track
reference-to-ID pointers (we just have no use-case for this -- I'd
actually like to remove this from statements too, but we need it for
branch detection right now), we don't need to track depth, etc.

In my testing, this entirely removes the regression on all-rules, and
gets us down to 2ms slower on the default rules (as a crude hyperfine
benchmark, so this is within margin of error IMO).

No behavioral changes.
This commit is contained in:
Charlie Marsh 2023-08-07 11:27:42 -04:00 committed by GitHub
parent 61d3977f95
commit b21abe0a57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 223 additions and 157 deletions

View file

@ -3,15 +3,15 @@ use std::iter;
use ruff_python_ast::{self as ast, ExceptHandler, Stmt};
use crate::node::{NodeId, Nodes};
use crate::statements::{StatementId, Statements};
/// 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<Stmt>,
) -> Option<NodeId> {
left: StatementId,
right: StatementId,
stop: Option<StatementId>,
node_tree: &Statements,
) -> Option<StatementId> {
if stop.is_some_and(|stop| left == stop || right == stop) {
return None;
}
@ -83,13 +83,13 @@ fn alternatives(stmt: &Stmt) -> Vec<Vec<&Stmt>> {
/// Return `true` if `stmt` is a descendent of any of the nodes in `ancestors`.
fn descendant_of<'a>(
stmt: NodeId,
stmt: StatementId,
ancestors: &[&'a Stmt],
stop: NodeId,
node_tree: &Nodes<'a, Stmt>,
stop: StatementId,
node_tree: &Statements<'a>,
) -> bool {
ancestors.iter().any(|ancestor| {
node_tree.node_id(ancestor).is_some_and(|ancestor| {
node_tree.statement_id(ancestor).is_some_and(|ancestor| {
common_ancestor(stmt, ancestor, Some(stop), node_tree).is_some()
})
})
@ -97,7 +97,7 @@ fn descendant_of<'a>(
/// 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<Stmt>) -> bool {
pub fn different_forks(left: StatementId, right: StatementId, node_tree: &Statements) -> 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);