From 8b43f42dca37a0acc60b9e49cfe948fdfe2539d3 Mon Sep 17 00:00:00 2001 From: "Lucas M. de Jong Larrarte" Date: Mon, 18 May 2026 00:00:47 +0200 Subject: [PATCH] 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. --- crates/cli/src/tests/query_test.rs | 35 ++++++++++++++++++++++++++++++ lib/src/query.c | 10 +++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/tests/query_test.rs b/crates/cli/src/tests/query_test.rs index 2f7d7f77b..609cf65e6 100644 --- a/crates/cli/src/tests/query_test.rs +++ b/crates/cli/src/tests/query_test.rs @@ -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(|| { diff --git a/lib/src/query.c b/lib/src/query.c index 2bf9dd58d..974454aa0 100644 --- a/lib/src/query.c +++ b/lib/src/query.c @@ -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,