Skip to content

Commit f48bd79

Browse files
committed
yeast: Evaluate fields and locations in order
Instead of filtering out the location overrides and doing them all at the end, we now evaluate them as we encounter them, gathering up all of the node Ids (if any) in a list. This list is then traversed (trivially so, if there are no entries) after we construct the node, to get the final location (or None if no overrides were present), and the newly constructed node's location is extended with this location information.
1 parent 70fb3a6 commit f48bd79

2 files changed

Lines changed: 94 additions & 71 deletions

File tree

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

Lines changed: 69 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ impl Node {
319319
let ctx = Ident::new("ctx", Span::call_site());
320320
let kind_str = self.kind.to_string();
321321

322-
if let Some(content) = &self.content {
322+
let node_init = if let Some(content) = &self.content {
323323
let node = match content {
324324
Content::Static(lit) => quote! { #ctx.literal(#kind_str, #lit) },
325325
Content::Computed(block) => {
@@ -362,97 +362,96 @@ impl Node {
362362
}
363363
}
364364
};
365-
return self.with_location(node, &ctx);
366-
}
365+
quote! { let __node: yeast::Id = #node; }
366+
} else {
367+
TokenStream::new()
368+
};
367369

368370
let mut stmts = Vec::new();
369371
let mut field_args = Vec::new();
370372

371373
for (field_counter, field) in self.fields.iter().enumerate() {
372-
let Field::Named {
373-
name,
374-
value,
375-
optional,
376-
} = field
377-
else {
378-
continue;
379-
};
380-
let field_name = name.to_string();
381-
let field_str = field_name
382-
.strip_prefix("r#")
383-
.unwrap_or(&field_name)
384-
.to_string();
385-
let temp = Ident::new(
386-
&format!("__field_{field_str}_{field_counter}"),
387-
Span::call_site(),
388-
);
389-
390-
match value {
391-
Template::Splice(block) => {
392-
debug_assert!(!optional);
374+
match field {
375+
Field::Location { block } => {
393376
let block = rust_block(block);
394377
stmts.push(quote! {
395-
let mut #temp: Vec<yeast::Id> = Vec::new();
396-
yeast::IntoFieldIds::extend_into(#block, &mut #temp);
397-
});
398-
field_args.push(quote! {
399-
if !#temp.is_empty() {
400-
__fields.push((#field_str, #temp));
401-
}
378+
yeast::IntoFieldIds::extend_into(#block, &mut __location_ids);
402379
});
403380
}
404-
Template::Node(child) if *optional => {
405-
let label = fresh_fallible_label();
406-
let value = child.lower(&LoweringContext {
407-
fallible_scope: Some(&label),
408-
});
409-
stmts.push(quote! {
410-
#[allow(unused_labels)]
411-
let #temp: ::std::option::Option<yeast::Id> = #label: {
412-
::std::option::Option::Some(#value)
413-
};
414-
});
415-
field_args.push(quote! {
416-
if let ::std::option::Option::Some(__id) = #temp {
417-
__fields.push((#field_str, vec![__id]));
381+
Field::Named {
382+
name,
383+
value,
384+
optional,
385+
} => {
386+
let field_name = name.to_string();
387+
let field_str = field_name
388+
.strip_prefix("r#")
389+
.unwrap_or(&field_name)
390+
.to_string();
391+
let temp = Ident::new(
392+
&format!("__field_{field_str}_{field_counter}"),
393+
Span::call_site(),
394+
);
395+
396+
match value {
397+
Template::Splice(block) => {
398+
debug_assert!(!optional);
399+
let block = rust_block(block);
400+
stmts.push(quote! {
401+
let mut #temp: Vec<yeast::Id> = Vec::new();
402+
yeast::IntoFieldIds::extend_into(#block, &mut #temp);
403+
});
404+
field_args.push(quote! {
405+
if !#temp.is_empty() {
406+
__fields.push((#field_str, #temp));
407+
}
408+
});
418409
}
419-
});
420-
}
421-
Template::Node(child) => {
422-
let value = child.lower(context);
423-
stmts.push(quote! { let #temp: yeast::Id = #value; });
424-
field_args.push(quote! {
425-
__fields.push((#field_str, vec![#temp]));
426-
});
410+
Template::Node(child) if *optional => {
411+
let label = fresh_fallible_label();
412+
let value = child.lower(&LoweringContext {
413+
fallible_scope: Some(&label),
414+
});
415+
stmts.push(quote! {
416+
#[allow(unused_labels)]
417+
let #temp: ::std::option::Option<yeast::Id> = #label: {
418+
::std::option::Option::Some(#value)
419+
};
420+
});
421+
field_args.push(quote! {
422+
if let ::std::option::Option::Some(__id) = #temp {
423+
__fields.push((#field_str, vec![__id]));
424+
}
425+
});
426+
}
427+
Template::Node(child) => {
428+
let value = child.lower(context);
429+
stmts.push(quote! { let #temp: yeast::Id = #value; });
430+
field_args.push(quote! {
431+
__fields.push((#field_str, vec![#temp]));
432+
});
433+
}
434+
}
427435
}
428436
}
429437
}
430438

431-
let node = quote! {
432-
{
433-
#(#stmts)*
439+
let build_node = if self.content.is_none() {
440+
quote! {
434441
let mut __fields: Vec<(&str, Vec<yeast::Id>)> = Vec::new();
435442
#(#field_args)*
436-
#ctx.node(#kind_str, __fields)
443+
let __node: yeast::Id = #ctx.node(#kind_str, __fields);
437444
}
445+
} else {
446+
TokenStream::new()
438447
};
439-
self.with_location(node, &ctx)
440-
}
441-
442-
fn with_location(&self, node: TokenStream, ctx: &Ident) -> TokenStream {
443-
let locations = self.fields.iter().filter_map(|field| match field {
444-
Field::Named { .. } => None,
445-
Field::Location { block } => Some(rust_block(block)),
446-
});
447-
let locations: Vec<_> = locations.collect();
448-
if locations.is_empty() {
449-
return node;
450-
}
451448

452449
quote! {
453450
{
454451
let mut __location_ids: Vec<yeast::Id> = Vec::new();
455-
#(yeast::IntoFieldIds::extend_into(#locations, &mut __location_ids);)*
452+
#node_init
453+
#(#stmts)*
454+
#build_node
456455
let __source_range = __location_ids
457456
.into_iter()
458457
.filter_map(|__source| {
@@ -461,7 +460,6 @@ impl Node {
461460
.and_then(|__node| __node.source_range())
462461
})
463462
.reduce(yeast::Range::union);
464-
let __node: yeast::Id = #node;
465463
#ctx.set_node_source_range(__node, __source_range)
466464
}
467465
}

‎shared/yeast/tests/test.rs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,31 @@ fn test_multiple_location_fields_combine_iterable_locations() {
17561756
assert_eq!(arguments.byte_range(), 0..5);
17571757
}
17581758

1759+
#[test]
1760+
fn test_location_and_named_fields_are_evaluated_left_to_right() {
1761+
let rule: Rule = rule!(
1762+
(assignment
1763+
left: (identifier) @left
1764+
right: (integer) @right)
1765+
=>
1766+
call {
1767+
let mut order = Vec::new();
1768+
let call = tree!((call
1769+
location: {order.push("first location"); left}
1770+
method: {order.push("method"); left}
1771+
location: {order.push("second location"); right}
1772+
arguments: {order.push("arguments"); right}));
1773+
assert_eq!(
1774+
order,
1775+
["first location", "method", "second location", "arguments"]
1776+
);
1777+
call
1778+
}
1779+
);
1780+
1781+
run_and_ast("x = 1", vec![rule]);
1782+
}
1783+
17591784
#[test]
17601785
fn test_location_field_is_available_on_literal_nodes() {
17611786
let rule: Rule = rule!(

0 commit comments

Comments
 (0)