Skip to content

Commit 7566ffa

Browse files
NewoskoWillLillis
authored andcommitted
fix(lib): continue search for later named siblings in
`ts_tree_cursor_current_status` By terminating early on `has_later_siblings`, `has_later_named_siblings` was incorrectly reported as `false` in some cases. This led to the execution of some queries to terminate early. Also remove some dead branches inside `ts_tree_cursor_current_status`. Co-authored-by: Will Lillis <will.lillis24@gmail.com> (cherry picked from commit 308aee0)
1 parent 4b7e2c9 commit 7566ffa

4 files changed

Lines changed: 133 additions & 4 deletions

File tree

crates/cli/src/tests/query_test.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6406,3 +6406,64 @@ export default grammar({
64066406

64076407
assert_query_matches(&language, &query, source, &[(0, vec![("tuple", "()")])]);
64086408
}
6409+
6410+
#[test]
6411+
fn test_last_child_anchor_looks_past_hidden_repeat() {
6412+
let language = get_test_fixture_language("last_child_anchor_past_hidden_repeat");
6413+
6414+
let source = "T a.b.c\nL a.b.c\nN a!.b!.c\n";
6415+
6416+
let query = Query::new(
6417+
&language,
6418+
"
6419+
(trailing_sep (name) @last .)
6420+
(leading_sep (name) @last .)
6421+
(trailing_named (name) @last .)
6422+
",
6423+
)
6424+
.unwrap();
6425+
6426+
assert_query_matches(
6427+
&language,
6428+
&query,
6429+
source,
6430+
&[
6431+
(0, vec![("last", "c")]),
6432+
(1, vec![("last", "c")]),
6433+
(2, vec![("last", "c")]),
6434+
],
6435+
);
6436+
6437+
let query = Query::new(
6438+
&language,
6439+
"
6440+
(trailing_sep . (name) @first)
6441+
(trailing_sep (name) @a . (name) @b)
6442+
",
6443+
)
6444+
.unwrap();
6445+
6446+
assert_query_matches(
6447+
&language,
6448+
&query,
6449+
source,
6450+
&[
6451+
(0, vec![("first", "a")]),
6452+
(1, vec![("a", "a"), ("b", "b")]),
6453+
(1, vec![("a", "b"), ("b", "c")]),
6454+
],
6455+
);
6456+
}
6457+
6458+
#[test]
6459+
fn test_last_child_anchor_looks_past_hidden_node() {
6460+
allocations::record(|| {
6461+
let language = get_language("c");
6462+
6463+
let query = Query::new(&language, "(translation_unit (_) @last .)").unwrap();
6464+
6465+
let source = "enum E { A };\nint x;\nint y;\n";
6466+
6467+
assert_query_matches(&language, &query, source, &[(0, vec![("last", "int y;")])]);
6468+
});
6469+
}

lib/src/tree_cursor.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,10 @@ void ts_tree_cursor_current_status(
552552
(*supertype_count)++;
553553
}
554554

555-
// Determine if the current node has later siblings.
556-
if (!*has_later_siblings) {
555+
// Determine if the current node has later siblings. A later *anonymous*
556+
// sibling settles `has_later_siblings` but says nothing about later *named*
557+
// siblings.
558+
if (!*has_later_named_siblings) {
557559
unsigned sibling_count = parent_entry->subtree->ptr->child_count;
558560
unsigned structural_child_index = entry->structural_child_index;
559561
if (!ts_subtree_extra(*entry->subtree)) structural_child_index++;
@@ -565,14 +567,12 @@ void ts_tree_cursor_current_status(
565567
);
566568
if (sibling_metadata.visible) {
567569
*has_later_siblings = true;
568-
if (*has_later_named_siblings) break;
569570
if (sibling_metadata.named) {
570571
*has_later_named_siblings = true;
571572
break;
572573
}
573574
} else if (ts_subtree_visible_child_count(sibling) > 0) {
574575
*has_later_siblings = true;
575-
if (*has_later_named_siblings) break;
576576
if (sibling.ptr->named_child_count > 0) {
577577
*has_later_named_siblings = true;
578578
break;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=========================================
2+
Repeat ending with an anonymous separator
3+
=========================================
4+
5+
T a.b.c
6+
7+
---
8+
9+
(source_file
10+
(trailing_sep
11+
(name)
12+
(name)
13+
(name)))
14+
15+
===============================
16+
Repeat ending with a named node
17+
===============================
18+
19+
L a.b.c
20+
21+
---
22+
23+
(source_file
24+
(leading_sep
25+
(name)
26+
(name)
27+
(name)))
28+
29+
============================================================
30+
Repeat ending with an anonymous separator, preceded by a tag
31+
============================================================
32+
33+
N a!.b!.c
34+
35+
---
36+
37+
(source_file
38+
(trailing_named
39+
(name)
40+
(tag)
41+
(name)
42+
(tag)
43+
(name)))
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// These three rules all parse to a flat run of `name` children, but `repeat`
2+
// builds a different hidden `_repeat1` node for each of them, so a query's
3+
// trailing `.` anchor has to walk past a different set of hidden nodes to
4+
// decide whether a `name` is the last *named* child of its rule.
5+
export default grammar({
6+
name: 'last_child_anchor_past_hidden_repeat',
7+
8+
rules: {
9+
source_file: $ => repeat(choice($.trailing_sep, $.leading_sep, $.trailing_named)),
10+
11+
// The hidden repeat ends with an anonymous node, and a named node follows it.
12+
trailing_sep: $ => seq('T', repeat(seq($.name, '.')), $.name),
13+
14+
// The hidden repeat ends with a named node.
15+
leading_sep: $ => seq('L', $.name, repeat(seq('.', $.name))),
16+
17+
// Same shape as `trailing_sep`, but a named node sits inside the repeat
18+
// ahead of the anonymous separator.
19+
trailing_named: $ => seq('N', repeat(seq($.name, $.tag, '.')), $.name),
20+
21+
tag: _ => '!',
22+
23+
name: _ => /[a-z]+/,
24+
}
25+
});

0 commit comments

Comments
 (0)