mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:36:22 -04:00
fix(query): transfer a leading boundary anchor across a zero-matched quantifier
`(P . Q* Y)` with zero `Q` dropped the leading `.`, so `Y` matched at any
position instead of being pinned to the parent's first named child. On a `?`/`*`
zero-skip, when the skipped step is the parent's first child and carries a
leading anchor, transfer that first-child requirement to the skip target.
(cherry picked from commit 1ffd612be5)
This commit is contained in:
parent
fcb38e9dcf
commit
5ccdbb6b84
|
|
@ -1336,6 +1336,52 @@ function foo() {}
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_matches_with_leading_anchor_before_zero_quantifier() {
|
||||
allocations::record(|| {
|
||||
let language = get_language("c");
|
||||
let query = Query::new(
|
||||
&language,
|
||||
"(translation_unit . (comment)* (function_definition) @f)",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The leading `.` anchors the comment run to the parent's first child. When the
|
||||
// run matches zero comments, that first-child requirement transfers to the
|
||||
// function, so it matches only when it is itself the first child.
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
"
|
||||
int main() {}
|
||||
",
|
||||
&[(0, vec![("f", "int main() {}")])],
|
||||
);
|
||||
|
||||
// The function is the second child, so with no leading comments it must not match.
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
"
|
||||
int a;
|
||||
int main() {}
|
||||
",
|
||||
&[],
|
||||
);
|
||||
|
||||
// With a leading comment the run starts at the first child and the function follows.
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
"
|
||||
// c
|
||||
int main() {}
|
||||
",
|
||||
&[(0, vec![("f", "int main() {}")])],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_matches_with_last_named_child() {
|
||||
allocations::record(|| {
|
||||
|
|
|
|||
|
|
@ -4445,13 +4445,26 @@ static inline bool ts_query_cursor__advance(
|
|||
copy->seeking_immediate_match = true;
|
||||
}
|
||||
// Taking a `?`/`*` zero-skip means the quantified subpattern matched
|
||||
// nothing, so an immediately-following anchor is vacuous for this copy,
|
||||
// UNLESS the skipped step carried a leading anchor of its own. In that
|
||||
// case the adjacency transfers through the empty run to the step we skip
|
||||
// to, so `A . Q* . B` with zero `Q` still requires `A` and `B` to be
|
||||
// immediate siblings.
|
||||
if (child_step->alternative_is_skip && !child_step->is_immediate) {
|
||||
copy->skipped_quantifier = true;
|
||||
// nothing. How an adjacent anchor behaves then depends on where it sat:
|
||||
if (child_step->alternative_is_skip) {
|
||||
if (!child_step->is_immediate) {
|
||||
// No leading anchor on the skipped step, so an immediately-following
|
||||
// anchor on the skip target is vacuous (`Q* . B` with zero `Q` lets
|
||||
// `B` match anywhere).
|
||||
copy->skipped_quantifier = true;
|
||||
} else if (
|
||||
array_get(&self->query->steps, child_state->step_index - 1)->depth <
|
||||
child_step->depth
|
||||
) {
|
||||
// The skipped step was the parent's first child pattern and carried a
|
||||
// leading *boundary* anchor (`(P . Q* Y)`). Transfer the first-child
|
||||
// requirement to the skip target so it survives the empty run: `Y`
|
||||
// must still be the parent's first named child.
|
||||
copy->seeking_immediate_match = true;
|
||||
}
|
||||
// Otherwise the skipped step carried a leading *between* anchor
|
||||
// (`A . Q* ...`): with zero `Q` that adjacency vanishes, while the skip
|
||||
// target's own anchor, if any, still applies (`A . Q* . B` stays adjacent).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue