From 86972fa04f28f9b6e71dbe5f0aa3af39d4560f41 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 1 May 2026 14:04:21 +0000 Subject: [PATCH] Yeast: Share FreshScope across rule applications The fresh identifier counter is now shared across all rule applications within a single Runner::run call. Each rule application gets a fresh "scope" (resolved names are cleared) but the counter keeps incrementing. This ensures that $tmp in different rules produces distinct names: the for-rule gets $tmp-0, the assignment rule gets $tmp-1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- shared/yeast-macros/src/parse.rs | 4 ++-- shared/yeast/src/build.rs | 6 +++--- shared/yeast/src/lib.rs | 25 +++++++++++++------------ shared/yeast/src/tree_builder.rs | 7 +++++++ shared/yeast/tests/test.rs | 9 +++++---- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 2cd3d91e167..670ede99096 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -623,9 +623,9 @@ pub fn parse_rule_top(input: TokenStream) -> Result { Ok(quote! { { let __query = #query_code; - yeast::Rule::new(__query, Box::new(|__ast: &mut yeast::Ast, __captures: yeast::captures::Captures| { + yeast::Rule::new(__query, Box::new(|__ast: &mut yeast::Ast, __captures: yeast::captures::Captures, __fresh: &yeast::tree_builder::FreshScope| { #(#bindings)* - let mut #ctx_ident = yeast::build::BuildCtx::new(__ast, &__captures); + let mut #ctx_ident = yeast::build::BuildCtx::new(__ast, &__captures, __fresh); #transform_body })) } diff --git a/shared/yeast/src/build.rs b/shared/yeast/src/build.rs index 239b0478891..81cfea78f85 100644 --- a/shared/yeast/src/build.rs +++ b/shared/yeast/src/build.rs @@ -12,15 +12,15 @@ use crate::{Ast, Id, NodeContent, CHILD_FIELD}; pub struct BuildCtx<'a> { pub ast: &'a mut Ast, pub captures: &'a Captures, - pub fresh: FreshScope, + pub fresh: &'a FreshScope, } impl<'a> BuildCtx<'a> { - pub fn new(ast: &'a mut Ast, captures: &'a Captures) -> Self { + pub fn new(ast: &'a mut Ast, captures: &'a Captures, fresh: &'a FreshScope) -> Self { Self { ast, captures, - fresh: FreshScope::new(), + fresh, } } diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index f2370e8b873..8141f84613f 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -500,32 +500,33 @@ impl From for NodeContent { pub struct Rule { query: QueryNode, - transform: Box Vec>, + transform: Box Vec>, } impl Rule { - pub fn new(query: QueryNode, transform: Box Vec>) -> Self { + pub fn new(query: QueryNode, transform: Box Vec>) -> Self { Self { query, transform } } - fn try_rule(&self, ast: &mut Ast, node: Id) -> Option> { + fn try_rule(&self, ast: &mut Ast, node: Id, fresh: &tree_builder::FreshScope) -> Option> { let mut captures = Captures::new(); if self.query.do_match(ast, node, &mut captures).unwrap() { - Some((self.transform)(ast, captures)) + fresh.next_scope(); + Some((self.transform)(ast, captures, fresh)) } else { None } } } -fn apply_rules(rules: &Vec, ast: &mut Ast, id: Id) -> Vec { +fn apply_rules(rules: &Vec, ast: &mut Ast, id: Id, fresh: &tree_builder::FreshScope) -> Vec { // apply the transformation rules on this node for rule in rules { - if let Some(result_node) = rule.try_rule(ast, id) { + if let Some(result_node) = rule.try_rule(ast, id, fresh) { // We transformed it so now recurse into the result return result_node .iter() - .flat_map(|node| apply_rules(rules, ast, *node)) + .flat_map(|node| apply_rules(rules, ast, *node, fresh)) .collect(); } } @@ -539,7 +540,7 @@ fn apply_rules(rules: &Vec, ast: &mut Ast, id: Id) -> Vec { mem::swap(vec, &mut old); *vec = old .iter() - .flat_map(|node| apply_rules(rules, ast, *node)) + .flat_map(|node| apply_rules(rules, ast, *node, fresh)) .collect(); } @@ -559,8 +560,9 @@ impl Runner { } pub fn run_from_tree(&self, tree: &tree_sitter::Tree) -> Ast { + let fresh = tree_builder::FreshScope::new(); let mut ast = Ast::from_tree(self.language.clone(), tree); - let res = apply_rules(&self.rules, &mut ast, 0); + let res = apply_rules(&self.rules, &mut ast, 0, &fresh); if res.len() != 1 { panic!("Expected at exactly one result node, got {}", res.len()); } @@ -569,13 +571,12 @@ impl Runner { } pub fn run(&self, input: &str) -> Ast { - // Parse the input into an AST - + let fresh = tree_builder::FreshScope::new(); let mut parser = tree_sitter::Parser::new(); parser.set_language(&self.language).unwrap(); let tree = parser.parse(input, None).unwrap(); let mut ast = Ast::from_tree(self.language.clone(), &tree); - let res = apply_rules(&self.rules, &mut ast, 0); + let res = apply_rules(&self.rules, &mut ast, 0, &fresh); if res.len() != 1 { panic!("Expected at exactly one result node, got {}", res.len()); } diff --git a/shared/yeast/src/tree_builder.rs b/shared/yeast/src/tree_builder.rs index 3f1440bd57c..b3b4c832c4f 100644 --- a/shared/yeast/src/tree_builder.rs +++ b/shared/yeast/src/tree_builder.rs @@ -27,4 +27,11 @@ impl FreshScope { }) .clone() } + + /// Clear resolved names but keep the counter. Called between rule + /// applications so that `$tmp` in different rules gets different values + /// while the counter increases monotonically. + pub fn next_scope(&self) { + self.resolved.borrow_mut().clear(); + } } diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index f04a7b68a18..9df89c8c3d4 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -145,7 +145,8 @@ fn test_tree_builder() { query.do_match(&ast, ast.get_root(), &mut captures).unwrap(); // Swap left and right - let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures); + let fresh = yeast::tree_builder::FreshScope::new(); + let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &fresh); let new_id = yeast::tree!(ctx, (program child: (assignment @@ -324,19 +325,19 @@ program body: block_body assignment - left: identifier \"$tmp-0\" + left: identifier \"$tmp-1\" right: identifier \"$tmp-0\" assignment left: identifier \"a\" right: element_reference - object: identifier \"$tmp-0\" + object: identifier \"$tmp-1\" integer \"0\" assignment left: identifier \"b\" right: element_reference - object: identifier \"$tmp-0\" + object: identifier \"$tmp-1\" integer \"1\" identifier \"x\" parameters: