fix(query): avoid overriding states not seeking immediate match with states seeking immediate match

Problem: In queries matching a parent node with anchored children
(siblings) where the first anchored sibling has a quantifier, the
deduplication logic for states is overly aggressive. The logic causes
the state split on the first capture (with the parent) to be dropped in
favor of the loopback state. This results in the query not being able to
match beyond the first occurrence.

Solution: Prevent the deduplication logic from dropping a state that is
not seeking an immediate match for one that is seeking an immediate
match, since the one not seeking for an immediate match could still
match later nodes.
This commit is contained in:
Lucas M. de Jong Larrarte 2026-05-18 00:00:47 +02:00 committed by Christian Clason
parent 1b110f62dd
commit 8b43f42dca
2 changed files with 43 additions and 2 deletions

View file

@ -1625,6 +1625,41 @@ fn test_matches_with_anchor_sibling_inside_parent() {
});
}
#[test]
fn test_matches_with_anchor_sibling_with_quantifier_captured_inside_parent() {
allocations::record(|| {
let language = get_language("rust");
let query = Query::new(
&language,
"
(source_file
(line_comment)+ @doc
.
(function_item
name: (identifier) @name)
)",
)
.unwrap();
assert_query_matches(
&language,
&query,
"
// A
fn a() {}
// B
fn b() {}
",
&[
(0, vec![("doc", "// A"), ("name", "a")]),
(0, vec![("doc", "// B"), ("name", "b")]),
],
);
});
}
#[test]
fn test_query_matches_with_trailing_optional_nodes() {
allocations::record(|| {

View file

@ -4373,7 +4373,10 @@ static inline bool ts_query_cursor__advance(
&right_contains_left
);
if (left_contains_right) {
if (state->step_index == other_state->step_index) {
if (
state->step_index == other_state->step_index &&
(other_state->seeking_immediate_match || !state->seeking_immediate_match)
) {
LOG(
" drop shorter state. pattern: %u, step_index: %u\n",
state->pattern_index,
@ -4387,7 +4390,10 @@ static inline bool ts_query_cursor__advance(
other_state->has_in_progress_alternatives = true;
}
if (right_contains_left) {
if (state->step_index == other_state->step_index) {
if (
state->step_index == other_state->step_index &&
(state->seeking_immediate_match || !other_state->seeking_immediate_match)
) {
LOG(
" drop shorter state. pattern: %u, step_index: %u\n",
state->pattern_index,