big_code_analysis_ast/node/ancestors.rs
1//! [`Ancestors`], the chain of a node's ancestors a walker threads down
2//! to its predicates, and the [`SiblingCache`] and [`FoldCache`] it can
3//! lend with it.
4
5use std::cell::RefCell;
6use std::collections::HashMap;
7
8use super::Node;
9
10/// The chain of a node's ancestors, root first, as recorded by a walker
11/// that descended to that node.
12///
13/// A predicate that asks a node for its parent pays [`Node::parent`]'s
14/// `O(depth)` once per node, so `O(depth²)` over a deeply nested file,
15/// however few parent steps it takes (#1084). A walker that visits
16/// parents before children already holds the chain, and handing it down
17/// turns each step into a slice index — the upward counterpart of the
18/// downward flag propagation #1052 and #1062 used.
19///
20/// Callers that reached a node some other way pass
21/// [`Ancestors::unknown`], which climbs with [`Node::parent`]: the same
22/// answers at the original cost.
23#[derive(Clone, Copy, Debug)]
24pub struct Ancestors<'tree, 'chain> {
25 chain: Option<&'chain [Node<'tree>]>,
26 siblings: Option<&'chain SiblingCache<'tree>>,
27 folds: Option<&'chain FoldCache>,
28}
29
30impl<'tree, 'chain> Ancestors<'tree, 'chain> {
31 /// No chain is available; every query climbs with [`Node::parent`].
32 #[inline]
33 #[must_use]
34 pub const fn unknown() -> Self {
35 Self {
36 chain: None,
37 siblings: None,
38 folds: None,
39 }
40 }
41
42 /// `chain` lists every ancestor of the node about to be queried,
43 /// root first — so `chain.last()` is its parent and an empty chain
44 /// means the node is the root.
45 #[inline]
46 #[must_use]
47 pub const fn known(chain: &'chain [Node<'tree>]) -> Self {
48 Self {
49 chain: Some(chain),
50 siblings: None,
51 folds: None,
52 }
53 }
54
55 /// This chain, answering [`siblings_after`](Self::siblings_after) and
56 /// [`siblings_before`](Self::siblings_before) through `cache`, which
57 /// the walker that keeps the chain owns for the whole walk.
58 #[inline]
59 #[must_use]
60 pub const fn with_siblings(self, cache: &'chain SiblingCache<'tree>) -> Self {
61 Self {
62 siblings: Some(cache),
63 ..self
64 }
65 }
66
67 /// This chain, memoising `Ancestors::fold_down` through
68 /// `cache`, which the walker that keeps the chain owns for the whole
69 /// walk.
70 #[inline]
71 #[must_use]
72 pub const fn with_folds(self, cache: &'chain FoldCache) -> Self {
73 Self {
74 folds: Some(cache),
75 ..self
76 }
77 }
78
79 /// The state of a fold of `step` down `node`'s ancestry, leaving out
80 /// the `skip` ancestors nearest `node`.
81 ///
82 /// The fold starts at `init` above the root and, for each ancestor
83 /// `a` from the root down, applies `step(state, a, a's chain, below)`,
84 /// where `below` is the next node on the path to `node` — the ancestor
85 /// under `a`, or `node` itself under its parent. A predicate that
86 /// would otherwise climb every ancestor of every candidate node, and
87 /// so cost `O(candidates × depth)`, asks this instead (#1597).
88 ///
89 /// Under a walk that lent a [`FoldCache`], the state at each ancestor
90 /// is stored per depth, keyed by the ancestor's id, so each one is
91 /// folded once per walk and a query costs amortised `O(1)`. A node
92 /// fixes its own ancestry, so a stored state whose id matches the
93 /// chain is exact; a slot left by a node the walk has moved past holds
94 /// another id and is refolded. `step` must answer the same for every
95 /// call on one `lane` within a walk, and must not fold itself. The
96 /// state at `node` (`skip == 0`) depends on `node`, so it is folded
97 /// fresh. Without a known chain the ancestry is climbed with
98 /// [`Node::parent`] first, which is `O(depth²)` per query; only tests
99 /// and chainless callers reach that path, never the metrics walk.
100 pub(crate) fn fold_down(
101 self,
102 node: &Node<'tree>,
103 skip: usize,
104 lane: FoldLane,
105 init: u8,
106 step: impl Fn(u8, &Node<'tree>, Ancestors<'tree, 'chain>, &Node<'tree>) -> u8,
107 ) -> u8 {
108 let Some(chain) = self.chain else {
109 return fold_climbed(node, skip, init, step);
110 };
111 let end = chain.len().saturating_sub(skip);
112 let step_at = |state: u8, depth: usize| {
113 let outer = Ancestors {
114 chain: Some(&chain[..depth]),
115 siblings: self.siblings,
116 folds: None,
117 };
118 step(
119 state,
120 &chain[depth],
121 outer,
122 chain.get(depth + 1).unwrap_or(node),
123 )
124 };
125 match self.folds {
126 Some(cache) => cache.fold(lane, chain, end, init, step_at),
127 None => (0..end).fold(init, step_at),
128 }
129 }
130
131 /// The walk's per-node memo, when it lent a [`FoldCache`].
132 pub(crate) fn node_memo(self) -> Option<&'chain NodeMemo> {
133 self.folds.map(|cache| &cache.nodes)
134 }
135
136 /// Whether [`fold_down`](Self::fold_down) is memoised on this chain:
137 /// one a walk lent a [`FoldCache`] with. Otherwise every fold runs
138 /// from the root, so a predicate whose own climb is bounded takes that
139 /// instead.
140 pub(crate) fn memoises_folds(self) -> bool {
141 self.folds.is_some() && self.chain.is_some()
142 }
143
144 /// [`Ancestors::known`], but first checks that `chain` really is
145 /// `node`'s ancestry.
146 ///
147 /// The parity test below proves the walker's truncate/push rule on a
148 /// *replica* walker, so it cannot see `metrics_inner` itself
149 /// desynchronising — a `chain.push` moved ahead of the per-node
150 /// computes, say, or a `continue` inserted above the truncate.
151 /// [`Ancestors::parent`] trusts `chain.last()` unvalidated, so such a
152 /// drift would feed every predicate a wrong ancestor silently rather
153 /// than fail. Walkers that maintain a chain should construct through
154 /// here; [`Ancestors::known`] stays unchecked for the callers that
155 /// deliberately pair a chain with a foreign node.
156 ///
157 /// # Two checks, because the exact one is not affordable by default
158 ///
159 /// The invariant is "`chain.last()` **is** `node.parent()`", and
160 /// asking that outright costs [`Node::parent`]'s `O(depth)` — the
161 /// very lookup #1084 exists to remove. Per node, on every walk that
162 /// constructs a checked chain, it made every debug-build walk
163 /// `O(nodes × depth)` while the shipped walk is `O(nodes)`: a tax on
164 /// every `cargo test`, worst on the deep-nesting regression tests
165 /// that exist to pin the shipped walk's linearity (#1122). It now
166 /// runs only under `--cfg chain_audit`, which `make chain-audit` and
167 /// the CI lane of the same name set — and there as a plain
168 /// `assert_eq!`, so the audit has teeth in a release profile too.
169 ///
170 /// What stays on in every debug build is an `O(1)` *consequence* of
171 /// that invariant: a parent's byte span contains its child's, and no
172 /// node is its own parent. Strictly weaker — a grandparent contains
173 /// the node as well — but it is shaped to the two ways a walker
174 /// desynchronises. A `push` moved ahead of the per-node computes
175 /// leaves `chain.last() == node`; a dropped `truncate` leaves the
176 /// previous subtree's path, whose last entry is disjoint from the
177 /// node that follows it. Both trip here, on the first node that
178 /// shows them, at four integer comparisons.
179 #[must_use]
180 pub fn checked(chain: &'chain [Node<'tree>], node: &Node<'tree>) -> Self {
181 // Opt-in only: `Node::parent` restarts at the root, so this is
182 // the `O(nodes × depth)` walk described above.
183 #[cfg(chain_audit)]
184 assert_eq!(
185 chain.last().map(Node::id),
186 node.parent().map(|parent| parent.id()),
187 "ancestor chain desynchronised on a {} node",
188 node.kind()
189 );
190 debug_assert!(
191 chain.last().is_none_or(|parent| {
192 parent.id() != node.id()
193 && parent.start_byte() <= node.start_byte()
194 && node.end_byte() <= parent.end_byte()
195 }),
196 "ancestor chain desynchronised on a {} node: chain.last() neither \
197 contains it nor differs from it",
198 node.kind()
199 );
200 Self::known(chain)
201 }
202
203 /// How far the node this chain describes sits from the root, or
204 /// `None` when no chain is known — deriving it then would cost the
205 /// [`Node::parent`] climb the chain exists to remove.
206 #[inline]
207 pub fn depth(self) -> Option<usize> {
208 self.chain.map(<[Node<'tree>]>::len)
209 }
210
211 /// The chain of `node`'s children, `self` being `node`'s own: a copy
212 /// extended by `node`, for a predicate that looks into a node the walk
213 /// has not descended into yet. `None` without a known chain, where
214 /// [`Ancestors::unknown`] gives the same answers by climbing.
215 pub(crate) fn children_chain(self, node: &Node<'tree>) -> Option<Vec<Node<'tree>>> {
216 self.chain
217 .map(|chain| [chain, std::slice::from_ref(node)].concat())
218 }
219
220 /// `node`'s parent.
221 #[inline]
222 #[must_use]
223 pub fn parent(self, node: &Node<'tree>) -> Option<Node<'tree>> {
224 match self.chain {
225 Some(chain) => chain.last().copied(),
226 None => node.parent(),
227 }
228 }
229
230 /// Whether `node`'s parent has kind id `kind`.
231 ///
232 /// The compound-leaf guard of `.claude/rules/grammar-dispatch.md`
233 /// section 5 asks this and nothing else: a delimiter or keyword
234 /// token is suppressed *only* directly under the construct that
235 /// owns it, never under an arbitrary ancestor. Eleven dispatch
236 /// arms across `getter`, `checker` and the metric walkers spelled
237 /// it out as `.parent(node).is_some_and(|p| p.kind_id() == X as
238 /// u16)`, which wraps onto four rustfmt lines inside a match guard
239 /// and buries the question under the plumbing; #1314 added six more
240 /// and folded all seventeen onto this. Three sites spelling their
241 /// binding `|parent|` were left alone and would each fit:
242 /// `impl_is_else_if_parent_clause!` (`src/checker.rs`),
243 /// `is_stabby_lambda_body` (`src/checker/ruby.rs`) and
244 /// `is_useful_comment` (`src/checker/rust.rs`, a let-chain).
245 ///
246 /// A `false` when `node` has no parent is the answer every one of
247 /// those call sites wants: a root node's token is not inside the
248 /// construct, so it is not suppressed.
249 #[inline]
250 #[must_use]
251 pub fn parent_has_kind(self, node: &Node<'tree>, kind: u16) -> bool {
252 self.parent(node).is_some_and(|p| p.kind_id() == kind)
253 }
254
255 /// The sibling immediately before `node`, or `None` when `node` is
256 /// its parent's first child or has no parent.
257 ///
258 /// `tree_sitter`'s own `prev_sibling` resolves the parent first
259 /// (`ts_node__prev_sibling` opens with `ts_node_parent`), so it
260 /// carries the same `O(depth)` cost [`Node::parent`] does. With a
261 /// known chain the parent is free and what remains is a cursor walk
262 /// over the siblings — or, for a wide parent the walk recorded in its
263 /// [`SiblingCache`], a binary search, since a walk over every child
264 /// of a Kotlin string's `$name` run was quadratic in it (#1584).
265 #[inline]
266 #[must_use]
267 pub fn previous_sibling(self, node: &Node<'tree>) -> Option<Node<'tree>> {
268 let Some(chain) = self.chain else {
269 return node.previous_sibling();
270 };
271 let parent = chain.last()?;
272 if let Lookup::Wide(children, index) = self.lookup(node) {
273 return match index {
274 Some(index) => children[..index].last().copied(),
275 None => node.previous_sibling(),
276 };
277 }
278 let mut previous = None;
279 for child in parent.children() {
280 if child.id() == node.id() {
281 return previous;
282 }
283 previous = Some(child);
284 }
285 // `node` is not among `parent.children()`, so this chain does
286 // not describe `node`. Answering `None` would claim "no
287 // previous sibling", which is a different (and wrong) answer;
288 // fall back to the authoritative lookup instead.
289 node.previous_sibling()
290 }
291
292 /// `node`'s ancestors, nearest first, each paired with *its* own
293 /// ancestry so a predicate applied to an ancestor stays as cheap as
294 /// one applied to `node`.
295 #[inline]
296 #[must_use]
297 pub fn iter(self, node: &Node<'tree>) -> AncestorIter<'tree, 'chain> {
298 match self.chain {
299 Some(chain) => AncestorIter::Chain(chain, self.siblings),
300 None => AncestorIter::Climb(node.parent()),
301 }
302 }
303
304 /// The siblings after `node`, nearest first: what
305 /// [`Node::children_after`] yields on `node`'s parent, and nothing
306 /// for the root. `self` is `node`'s chain.
307 ///
308 /// The seek [`Node::children_after`] runs is `O(index)` over a flat
309 /// child array, so a walk that asks of every child of a wide
310 /// `ERROR` is quadratic in it (#1584). A walk that lends a
311 /// [`SiblingCache`] has already collected a wide parent's children,
312 /// and the lookup is a binary search among them.
313 pub fn siblings_after(
314 self,
315 node: &Node<'tree>,
316 ) -> impl Iterator<Item = Node<'tree>> + use<'tree, 'chain> {
317 let (wide, narrow) = match self.lookup(node) {
318 Lookup::Wide(children, index) => (index.map(|index| &children[index + 1..]), None),
319 Lookup::Narrow(parent) => (None, parent.map(|parent| parent.children_after(node))),
320 };
321 let wide = wide.into_iter().flatten().copied();
322 wide.chain(narrow.into_iter().flatten())
323 }
324
325 /// The siblings before `node`, nearest first: what
326 /// [`Node::children_before`] yields on `node`'s parent, and nothing
327 /// for the root. `self` is `node`'s chain; see
328 /// [`siblings_after`](Self::siblings_after).
329 pub fn siblings_before(
330 self,
331 node: &Node<'tree>,
332 ) -> impl Iterator<Item = Node<'tree>> + use<'tree, 'chain> {
333 let (wide, narrow) = match self.lookup(node) {
334 Lookup::Wide(children, index) => (index.map(|index| &children[..index]), None),
335 Lookup::Narrow(parent) => (None, parent.map(|parent| parent.children_before(node))),
336 };
337 let wide = wide.into_iter().flatten().rev().copied();
338 wide.chain(narrow.into_iter().flatten())
339 }
340
341 // Where `node`'s siblings are read from: its parent's children as the
342 // walk recorded them and `node`'s index among them, or the parent to
343 // seek in when the walk recorded none.
344 fn lookup(self, node: &Node<'tree>) -> Lookup<'tree, 'chain> {
345 let parent = self.parent(node);
346 let recorded = match (self.siblings, self.chain, parent) {
347 (Some(cache), Some(chain), Some(parent)) => {
348 let recorded = cache.children(&parent, chain.len() - 1);
349 // A walk that stopped recording would answer the same,
350 // only quadratically, which no metric value shows.
351 debug_assert!(
352 recorded.is_some() || parent.child_count() <= WIDE_PARENT,
353 "a walk lending a SiblingCache must record every wide node on its \
354 chain (#1584), but not this {}",
355 parent.kind()
356 );
357 recorded
358 }
359 _ => None,
360 };
361 match recorded {
362 Some(children) => Lookup::Wide(children, position_by_start(children, node)),
363 None => Lookup::Narrow(parent),
364 }
365 }
366}
367
368enum Lookup<'tree, 'chain> {
369 Wide(&'chain [Node<'tree>], Option<usize>),
370 Narrow(Option<Node<'tree>>),
371}
372
373/// Children past which a walk records a node's children for sibling
374/// lookups. A seek over this many is a short scan however the grammar
375/// stores them, so recording a narrower node would copy it for nothing.
376///
377/// `pub` and `#[doc(hidden)]` only so the wide-parent fixtures in this
378/// crate's and `big-code-analysis`'s tests size themselves from it: a
379/// hand-copied width would silently go narrow if this were raised.
380#[doc(hidden)]
381pub const WIDE_PARENT: usize = 64;
382
383// `node`'s index in `children`, a parent's children in order. Their start
384// bytes never decrease, so a binary search lands on the first child
385// starting where `node` does; only zero-width children, which recovery
386// inserts, can share that byte with it.
387fn position_by_start(children: &[Node], node: &Node) -> Option<usize> {
388 let first = children.partition_point(|child| child.start_byte() < node.start_byte());
389 children[first..]
390 .iter()
391 .take_while(|child| child.start_byte() == node.start_byte())
392 .position(|child| child.id() == node.id())
393 .map(|offset| first + offset)
394}
395
396/// The children of each wide node on a walk's chain, recorded as the
397/// walk pushes them, so that [`Ancestors::siblings_after`] and
398/// [`Ancestors::siblings_before`] find a sibling in a flat `ERROR`
399/// without a scan from its start (#1584).
400///
401/// One slot per depth, keyed by the node's id. A walk descends into a
402/// node only after pushing its children, so every wide node on the
403/// chain has its slot; a slot left behind by a node the walk has moved
404/// past holds another id and is not read. Recording copies what the
405/// walk already holds, once per wide node.
406#[derive(Debug, Default)]
407pub struct SiblingCache<'tree>(Vec<(usize, Vec<Node<'tree>>)>);
408
409impl<'tree> SiblingCache<'tree> {
410 /// Records `children`, in order, as the children of `node`, which
411 /// sits at `depth` in the walk's chain. A node of no more than a
412 /// short scan's children is not recorded.
413 pub fn record(
414 &mut self,
415 node: &Node<'tree>,
416 depth: usize,
417 children: impl IntoIterator<Item = Node<'tree>>,
418 ) {
419 if node.child_count() <= WIDE_PARENT {
420 return;
421 }
422 if self.0.len() <= depth {
423 self.0.resize_with(depth + 1, Default::default);
424 }
425 // An unused slot's id is 0, which no node has: tree-sitter's id
426 // is the address of the node's subtree.
427 let (id, recorded) = &mut self.0[depth];
428 *id = node.id();
429 recorded.clear();
430 recorded.extend(children);
431 }
432
433 // `parent`'s children, when the walk recorded them at `depth`.
434 fn children(&self, parent: &Node<'tree>, depth: usize) -> Option<&[Node<'tree>]> {
435 self.0
436 .get(depth)
437 .filter(|(id, _)| *id == parent.id())
438 .map(|(_, children)| children.as_slice())
439 }
440}
441
442// The deepest depth at or above `from` whose slot still holds the state of
443// the ancestor the chain has there, or 0 when none does: a node fixes its
444// ancestry, so a matching id is an exact state.
445fn deepest_stored(slots: &[(usize, u8)], chain: &[Node<'_>], from: usize) -> usize {
446 (1..=from)
447 .rev()
448 .find(|&depth| slots[depth].0 == chain[depth].id())
449 .unwrap_or(0)
450}
451
452// `Ancestors::fold_down` for a caller that holds no chain: the ancestry is
453// climbed first and folded unmemoised.
454fn fold_climbed<'tree, 'chain>(
455 node: &Node<'tree>,
456 skip: usize,
457 init: u8,
458 step: impl Fn(u8, &Node<'tree>, Ancestors<'tree, 'chain>, &Node<'tree>) -> u8,
459) -> u8
460where
461 'tree: 'chain,
462{
463 let climbed = climbed_chain(node);
464 let end = climbed.len().saturating_sub(skip);
465 (0..end).fold(init, |state, depth| {
466 let below = climbed.get(depth + 1).unwrap_or(node);
467 step(state, &climbed[depth], Ancestors::unknown(), below)
468 })
469}
470
471// `node`'s ancestors, root first, climbed with `Node::parent` for a caller
472// that holds no chain. tree-sitter finds a parent by descending from the
473// root, so the climb is `O(depth²)`.
474fn climbed_chain<'tree>(node: &Node<'tree>) -> Vec<Node<'tree>> {
475 let mut climbed: Vec<_> = std::iter::successors(node.parent(), Node::parent).collect();
476 climbed.reverse();
477 climbed
478}
479
480/// Which memoised fold a [`FoldCache`] slot holds. A language may run
481/// more than one fold over the same walk, and two folds' states must not
482/// share a slot.
483#[derive(Clone, Copy, Debug, PartialEq, Eq)]
484pub(crate) enum FoldLane {
485 /// Whether an ancestor holds a doc comment in a function body.
486 DocInBody,
487 /// A positional language's declaration scope: Go's package level,
488 /// Ruby's YARD namespace. One lane serves both because a walk parses
489 /// one language.
490 DocScope,
491 /// Python: the climb from a node through the parentheses above it to
492 /// a bare string statement (#1610).
493 BareStringClimb,
494}
495
496impl FoldLane {
497 /// How many lanes a [`FoldCache`] holds; one per variant above.
498 const COUNT: usize = 3;
499}
500
501/// The per-depth states of the walk's memoised folds, for
502/// `Ancestors::fold_down` (#1597).
503///
504/// One slot per depth and lane, keyed by the id of the ancestor at that
505/// depth: the state folded over every ancestor above it. Interior
506/// mutability, because [`Ancestors`] lends it shared, as it lends a
507/// [`SiblingCache`]; a fold never re-enters itself, so the borrow is
508/// never contended.
509#[derive(Debug, Default)]
510pub struct FoldCache {
511 lanes: RefCell<[Vec<(usize, u8)>; FoldLane::COUNT]>,
512 nodes: NodeMemo,
513}
514
515/// Answers about a node's own subtree, memoised for one walk and keyed
516/// by node id, which is unique within a tree: Doxygen's "does this
517/// preprocessor conditional hold an entry", asked of every conditional
518/// a doc comment's stretch meets. Unlike a [`FoldLane`] it is not tied to
519/// the chain, so an answer stored for one sibling subtree serves every
520/// later question about it (#1613). It holds one question; a second
521/// would need a key of its own.
522#[derive(Debug, Default)]
523pub(crate) struct NodeMemo(RefCell<HashMap<usize, bool>>);
524
525impl NodeMemo {
526 /// The stored answer for `node`, if any.
527 pub(crate) fn get(&self, node: &Node<'_>) -> Option<bool> {
528 self.0.borrow().get(&node.id()).copied()
529 }
530
531 /// Stores `answer` for `node`.
532 pub(crate) fn set(&self, node: &Node<'_>, answer: bool) {
533 self.0.borrow_mut().insert(node.id(), answer);
534 }
535}
536
537impl FoldCache {
538 // The state after folding `step(state, depth)` over `chain[..end]`,
539 // reusing and storing the per-depth states on `lane`.
540 fn fold(
541 &self,
542 lane: FoldLane,
543 chain: &[Node<'_>],
544 end: usize,
545 init: u8,
546 step: impl Fn(u8, usize) -> u8,
547 ) -> u8 {
548 let mut lanes = self.lanes.borrow_mut();
549 let slots = &mut lanes[lane as usize];
550 // `slots[d]` is the state at `chain[d]`: folded over `chain[..d]`.
551 // Depth 0 is `init`; the state at `node` itself is never stored.
552 let memo_end = end.min(chain.len().saturating_sub(1));
553 if slots.len() <= memo_end {
554 // Id 0 marks an empty slot: a tree-sitter node id is its
555 // address, never 0.
556 slots.resize(memo_end + 1, (0, 0));
557 }
558 let mut depth = deepest_stored(slots, chain, memo_end);
559 let mut state = if depth == 0 { init } else { slots[depth].1 };
560 while depth < memo_end {
561 state = step(state, depth);
562 depth += 1;
563 slots[depth] = (chain[depth].id(), state);
564 }
565 if end > memo_end {
566 state = step(state, memo_end);
567 }
568 state
569 }
570}
571
572/// Ancestor iterator returned by [`Ancestors::iter`], nearest first.
573pub enum AncestorIter<'tree, 'chain> {
574 /// The not-yet-yielded prefix of a known chain, and the walk's
575 /// [`SiblingCache`] if it lent one. The prefix's last element is
576 /// the next ancestor, and the prefix before it is that ancestor's
577 /// own chain — so splitting from the back hands out both at once.
578 Chain(&'chain [Node<'tree>], Option<&'chain SiblingCache<'tree>>),
579 /// The next ancestor to yield, reached by climbing.
580 Climb(Option<Node<'tree>>),
581}
582
583impl<'tree, 'chain> Iterator for AncestorIter<'tree, 'chain> {
584 type Item = (Node<'tree>, Ancestors<'tree, 'chain>);
585
586 fn next(&mut self) -> Option<Self::Item> {
587 match self {
588 Self::Chain(remaining, siblings) => {
589 let (&nearest, above) = remaining.split_last()?;
590 *remaining = above;
591 let ancestors = Ancestors {
592 chain: Some(above),
593 siblings: *siblings,
594 folds: None,
595 };
596 Some((nearest, ancestors))
597 }
598 Self::Climb(next) => {
599 let nearest = (*next)?;
600 *next = nearest.parent();
601 Some((nearest, Ancestors::unknown()))
602 }
603 }
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610 use crate::node::Tree;
611
612 // The ids of `nodes`, so a failing comparison prints which differ.
613 #[cfg(feature = "perl")]
614 fn ids<'a>(nodes: impl Iterator<Item = Node<'a>>) -> Vec<usize> {
615 nodes.map(|node| node.id()).collect()
616 }
617
618 // `parent`'s children through `child(i)`, the raw walk that neither
619 // the seek nor the recorded array is built on.
620 #[cfg(feature = "perl")]
621 fn raw_children(parent: &Node) -> Vec<usize> {
622 let raw = parent.as_tree_sitter();
623 (0..raw.child_count() as u32)
624 .filter_map(|i| raw.child(i))
625 .map(|child| child.id())
626 .collect()
627 }
628
629 // `my @r = (` and a body never closed: recovery makes the root an
630 // `ERROR` whose children are the body's tokens, one flat array of
631 // well over `WIDE_PARENT` of them.
632 #[cfg(feature = "perl")]
633 fn flat_error() -> Tree {
634 let source = format!("my @r = ({}", "{\nlast,\n$a && $b,\n".repeat(20));
635 Tree::new::<crate::langs::PerlCode>(source.as_bytes())
636 }
637
638 /// `fold_down` answers the same through all three of its paths: the
639 /// walk's memoised [`FoldCache`], a known chain with no cache, and the
640 /// chainless climb. Queried in walk order, as the metrics walk queries
641 /// it, and twice per node so the second query is answered from the
642 /// stored slots (#1597). The step depends on order, on `below` and on
643 /// the parent its `outer` chain names, so a slot read at the wrong
644 /// depth, a climb in the wrong direction or an `outer` cut at the wrong
645 /// place changes the answer. The climb hands `Ancestors::unknown()`,
646 /// whose `parent` falls back to `Node::parent`, so all three agree only
647 /// when the chains are right.
648 #[cfg(feature = "rust")]
649 #[test]
650 fn fold_down_agrees_across_its_cache_chain_and_climb_paths() {
651 let tree = Tree::new::<crate::langs::RustCode>(
652 b"mod a {\n mod b {\n fn f() { let x = (1 + 2) * 3; }\n }\n fn g() {}\n}\nfn h() { if true { 4 } else { 5 }; }\n",
653 );
654 let step = |state: u8, ancestor: &Node, outer: Ancestors, below: &Node| {
655 let parent = outer
656 .parent(ancestor)
657 .map_or(0, |parent| parent.kind_id().to_le_bytes()[0]);
658 state
659 .wrapping_mul(31)
660 .wrapping_add(ancestor.kind_id().to_le_bytes()[0])
661 .wrapping_add(below.kind_id().to_le_bytes()[0].rotate_left(3))
662 .wrapping_add(parent.rotate_left(5))
663 };
664 let cache = FoldCache::default();
665 let mut stack = vec![(tree.get_root(), 0_usize)];
666 let mut chain: Vec<Node> = Vec::new();
667 let mut queried = 0_usize;
668 while let Some((node, depth)) = stack.pop() {
669 chain.truncate(depth);
670 for skip in 0..=3 {
671 let cached = |c: &[Node<'_>]| {
672 Ancestors::known(c).with_folds(&cache).fold_down(
673 &node,
674 skip,
675 FoldLane::DocInBody,
676 7,
677 step,
678 )
679 };
680 let first = cached(&chain);
681 let again = cached(&chain);
682 let uncached =
683 Ancestors::known(&chain).fold_down(&node, skip, FoldLane::DocInBody, 7, step);
684 let climbed =
685 Ancestors::unknown().fold_down(&node, skip, FoldLane::DocInBody, 7, step);
686 assert_eq!(
687 (first, again, uncached),
688 (climbed, climbed, climbed),
689 "{} at depth {depth}, skip {skip}",
690 node.kind()
691 );
692 queried += 1;
693 }
694 chain.push(node);
695 let children: Vec<_> = node.children().collect();
696 stack.extend(children.into_iter().rev().map(|child| (child, depth + 1)));
697 }
698 assert!(
699 queried > 100,
700 "the fixture must exercise a real walk: {queried}"
701 );
702 }
703
704 /// A recorded wide parent answers every child's siblings, and its
705 /// previous sibling, from the recorded array, and answers exactly
706 /// what the raw walk does on either side of it — the `ERROR` whose
707 /// flat array made the seek `O(index)` (#1584). A node that is no
708 /// child of it gets nothing.
709 #[cfg(feature = "perl")]
710 #[test]
711 fn siblings_of_a_recorded_wide_parent_match_the_child_walk() {
712 let tree = flat_error();
713 let root = tree.get_root();
714 assert!(root.is_error(), "the fixture's tail must fail to parse");
715 assert!(root.child_count() > WIDE_PARENT, "the ERROR must be wide");
716
717 let mut cache = SiblingCache::default();
718 cache.record(&root, 0, root.children());
719 let chain = [root];
720 let ancestors = Ancestors::known(&chain).with_siblings(&cache);
721 let all = raw_children(&root);
722
723 let (mut observed, mut expected) = (Vec::new(), Vec::new());
724 for (index, child) in root.children().enumerate() {
725 let recorded_index = match ancestors.lookup(&child) {
726 Lookup::Wide(_, index) => index,
727 Lookup::Narrow(_) => None,
728 };
729 observed.push((
730 index,
731 recorded_index,
732 ids(ancestors.siblings_after(&child)),
733 ids(ancestors.siblings_before(&child)),
734 ancestors.previous_sibling(&child).map(|node| node.id()),
735 ));
736 expected.push((
737 index,
738 Some(index),
739 all[index + 1..].to_vec(),
740 all[..index].iter().rev().copied().collect(),
741 index.checked_sub(1).map(|previous| all[previous]),
742 ));
743 }
744 assert_eq!(observed, expected);
745
746 // A grandchild starting where its parent does, so the search
747 // reaches it and only the id comparison turns it away.
748 let stray = root
749 .children()
750 .find_map(|child| {
751 child
752 .child(0)
753 .filter(|g| g.start_byte() == child.start_byte())
754 })
755 .expect("the fixture nests a node at its parent's start");
756 assert!(matches!(ancestors.lookup(&stray), Lookup::Wide(_, None)));
757 assert_eq!(ancestors.siblings_after(&stray).count(), 0);
758 assert_eq!(ancestors.siblings_before(&stray).count(), 0);
759 // `previous_sibling` falls back to the node's own lookup instead,
760 // which finds a stray's real previous sibling: `$b`'s is the `&&`
761 // of its own chain, not anything in the root.
762 let operand = root
763 .children()
764 .find_map(|child| child.child(2))
765 .expect("the fixture holds `$a && $b`");
766 let previous = ancestors
767 .previous_sibling(&operand)
768 .expect("the fallback finds the `&&`");
769 assert_eq!(previous.kind(), "&&");
770 }
771
772 /// A narrow parent is never recorded, and its siblings come from
773 /// the seek — with or without a cache lent.
774 #[cfg(feature = "perl")]
775 #[test]
776 fn a_narrow_parent_is_sought_not_recorded() {
777 let tree = flat_error();
778 let root = tree.get_root();
779 let chain = [root];
780 let narrow = root
781 .children()
782 .find(|child| (2..=WIDE_PARENT).contains(&child.child_count()))
783 .expect("the fixture holds a narrow node with several children");
784 let mut cache = SiblingCache::default();
785 cache.record(&narrow, 1, narrow.children());
786 assert!(cache.0.is_empty(), "a narrow node must not be recorded");
787
788 let narrow_chain = [root, narrow];
789 let all = raw_children(&narrow);
790 for ancestors in [
791 Ancestors::known(&narrow_chain).with_siblings(&cache),
792 Ancestors::known(&narrow_chain),
793 ] {
794 let first = narrow.child(0).expect("narrow node has children");
795 assert!(matches!(ancestors.lookup(&first), Lookup::Narrow(Some(_))));
796 assert_eq!(ids(ancestors.siblings_after(&first)), all[1..]);
797 }
798 // Without a cache a wide parent is sought too.
799 let first = root.child(0).expect("root has children");
800 assert!(matches!(
801 Ancestors::known(&chain).lookup(&first),
802 Lookup::Narrow(Some(_))
803 ));
804 }
805
806 /// A walk that lends a cache but did not record a wide node on its
807 /// chain would answer correctly and quadratically; the assertion is
808 /// what makes that visible to every test that walks one.
809 #[cfg(all(feature = "perl", debug_assertions))]
810 #[test]
811 #[should_panic(expected = "must record every wide node on its chain")]
812 fn an_unrecorded_wide_parent_trips_the_walk_assertion() {
813 let tree = flat_error();
814 let root = tree.get_root();
815 let cache = SiblingCache::default();
816 let chain = [root];
817 let first = root.child(0).expect("root has children");
818 let _ = Ancestors::known(&chain)
819 .with_siblings(&cache)
820 .lookup(&first);
821 }
822
823 /// The binary search finds every child at its index, including a
824 /// zero-width one: recovery inserts it at the byte its successor
825 /// starts at, so only the id tells the two apart. The Kotlin
826 /// fixture's `vararg: Int` holds one.
827 #[cfg(feature = "kotlin")]
828 #[test]
829 fn position_by_start_finds_a_zero_width_child() {
830 let tree = Tree::new::<crate::langs::KotlinCode>(b"fun f(vararg: Int, b: Int) {}");
831 let mut zero_width = 0;
832 for node in tree.get_root().preorder() {
833 let children: Vec<_> = node.children().collect();
834 for (index, child) in children.iter().enumerate() {
835 zero_width += usize::from(child.start_byte() == child.end_byte());
836 assert_eq!(position_by_start(&children, child), Some(index));
837 }
838 }
839 assert!(
840 zero_width > 0,
841 "fixture must hold an inserted zero-width child"
842 );
843 }
844}