Skip to content

Commit 93a2dcd

Browse files
committed
yeast: Consolidate multiple captures appropriately
Getting even more into the weeds of the query matching semantics, what should something like ``` (foo bar: (bar) @item baz: (baz) @@item ) ``` do? Or what about something like ``` (foo bar: (bar) @item baz: (baz)* @item ) ``` ? At present, we permit captures to have multiple occurrences in a query. Each occurrence simply pushes yet another value into the capture. With this in mind, the sensible semantics (which we implement in this commit) seem to be the following: - If any of the captures (with a given identifier) are raw, then so is the entire capture. - If we capture both individual items and lists, then the entire capture is a list. Note that we don't actually (as far as I know) use these constructions anywhere, but the previous behaviour would just cause weird things to potentially happen. At least now the behaviour should be a bit more predictable.
1 parent f3f66db commit 93a2dcd

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

‎shared/yeast-macros/src/lower.rs‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,27 @@ impl Pattern {
193193
}
194194

195195
fn captures(&self) -> Vec<BoundCapture> {
196-
let mut captures = Vec::new();
197-
self.collect_captures(Cardinality::SINGLE, &mut captures);
196+
let mut occurrences = Vec::new();
197+
self.collect_captures(Cardinality::SINGLE, &mut occurrences);
198+
199+
let mut captures: Vec<BoundCapture> = Vec::new();
200+
for occurrence in occurrences {
201+
if let Some(existing) = captures
202+
.iter_mut()
203+
.find(|capture| capture.capture.name == occurrence.capture.name)
204+
{
205+
// Two capture sites can both contribute values, so their
206+
// combined binding is necessarily a vector even when each
207+
// occurrence captures at most one node.
208+
existing.cardinality.multiple = true;
209+
existing.cardinality.required &= occurrence.cardinality.required;
210+
// Captures are stored by name at runtime. If any occurrence is
211+
// raw, the whole bucket must remain untranslated.
212+
existing.capture.raw |= occurrence.capture.raw;
213+
} else {
214+
captures.push(occurrence);
215+
}
216+
}
198217
captures
199218
}
200219

‎shared/yeast/tests/test.rs‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,50 @@ fn test_query_repeated_capture() {
468468
assert_eq!(captures.get_all("names").len(), 3);
469469
}
470470

471+
#[test]
472+
fn test_reused_capture_name_is_promoted_to_vector() {
473+
let rules: Vec<Rule> = vec![yeast::rule!(
474+
(assignment
475+
left: (_) @items
476+
right: (_) @items)
477+
=>
478+
identifier {
479+
let items: Vec<yeast::Id> = items;
480+
tree!((identifier #{items.len()}))
481+
}
482+
)];
483+
484+
assert_dump_eq(
485+
&run_and_dump("x = 1", rules),
486+
r#"
487+
program
488+
identifier "2"
489+
"#,
490+
);
491+
}
492+
493+
#[test]
494+
fn test_reused_capture_name_combines_single_and_repeated_sites() {
495+
let rules: Vec<Rule> = vec![yeast::rule!(
496+
(assignment
497+
"="? @@items
498+
left: (identifier)* @@items)
499+
=>
500+
identifier {
501+
let items: Vec<yeast::Id> = items;
502+
tree!((identifier #{items.len()}))
503+
}
504+
)];
505+
506+
assert_dump_eq(
507+
&run_and_dump("x = 1", rules),
508+
r#"
509+
program
510+
identifier "2"
511+
"#,
512+
);
513+
}
514+
471515
#[test]
472516
fn test_capture_unnamed_node_parenthesized() {
473517
// `("=") @op` captures the unnamed `=` token between left and right.

0 commit comments

Comments
 (0)