From a3e7e4cfd2fa1b042bfb60d5c2f292e02852a6d3 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 1 May 2026 15:22:58 +0000 Subject: [PATCH] Yeast: Propagate errors via Result instead of panicking Runner::run and run_from_tree now return Result instead of panicking on errors. Errors from query matching (unknown node kinds or field names), parser setup, and unexpected result counts are all propagated. The extractor's extract_and_desugar gracefully falls back to the un-desugared AST on error, logging the failure via tracing::error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/extractor/mod.rs | 7 ++- shared/yeast/src/bin/main.rs | 2 +- shared/yeast/src/lib.rs | 61 +++++++++---------- shared/yeast/tests/test.rs | 12 ++-- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/shared/tree-sitter-extractor/src/extractor/mod.rs b/shared/tree-sitter-extractor/src/extractor/mod.rs index 3eab0144bc1..bdd23ad7301 100644 --- a/shared/tree-sitter-extractor/src/extractor/mod.rs +++ b/shared/tree-sitter-extractor/src/extractor/mod.rs @@ -837,7 +837,12 @@ pub fn extract_and_desugar( schema, ); let runner = yeast::Runner::new(language.clone(), rules); - let ast = runner.run_from_tree(&tree); + let ast = runner.run_from_tree(&tree) + .unwrap_or_else(|e| { + tracing::error!("Desugaring failed: {e}"); + // Fall back to the un-desugared AST + yeast::Ast::from_tree(language.clone(), &tree) + }); traverse_yeast(&ast, &mut visitor); diff --git a/shared/yeast/src/bin/main.rs b/shared/yeast/src/bin/main.rs index 92c1530982d..e9ee1f38078 100644 --- a/shared/yeast/src/bin/main.rs +++ b/shared/yeast/src/bin/main.rs @@ -21,6 +21,6 @@ fn main() { let language = get_language(&args.language); let source = std::fs::read_to_string(&args.file).unwrap(); let runner = yeast::Runner::new(language, vec![]); - let ast = runner.run(&source); + let ast = runner.run(&source).unwrap(); println!("{}", ast.print(&source, ast.get_root())); } diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 59a21f6a37c..ef38d446dc9 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -539,52 +539,49 @@ impl Rule { Self { query, transform } } - fn try_rule(&self, ast: &mut Ast, node: Id, fresh: &tree_builder::FreshScope) -> Option> { + fn try_rule(&self, ast: &mut Ast, node: Id, fresh: &tree_builder::FreshScope) -> Result>, String> { let mut captures = Captures::new(); - if self.query.do_match(ast, node, &mut captures).unwrap() { + if self.query.do_match(ast, node, &mut captures)? { fresh.next_scope(); - // Get the source range of the matched node for location info let source_range = ast.get_node(node).and_then(|n| { match n.content { NodeContent::Range(r) => Some(r), _ => n.source_range, } }); - Some((self.transform)(ast, captures, fresh, source_range)) + Ok(Some((self.transform)(ast, captures, fresh, source_range))) } else { - None + Ok(None) } } } -fn apply_rules(rules: &Vec, ast: &mut Ast, id: Id, fresh: &tree_builder::FreshScope) -> Vec { - // apply the transformation rules on this node +fn apply_rules(rules: &Vec, ast: &mut Ast, id: Id, fresh: &tree_builder::FreshScope) -> Result, String> { for rule in rules { - 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, fresh)) - .collect(); + if let Some(result_node) = rule.try_rule(ast, id, fresh)? { + let mut results = Vec::new(); + for node in result_node { + results.extend(apply_rules(rules, ast, node, fresh)?); + } + return Ok(results); } } - // copy the current node let mut node = ast.nodes[id].clone(); // recursively descend into all the fields for vec in node.fields.values_mut() { - let mut old = Vec::new(); - mem::swap(vec, &mut old); - *vec = old - .iter() - .flat_map(|node| apply_rules(rules, ast, *node, fresh)) - .collect(); + let old = std::mem::take(vec); + let mut new = Vec::new(); + for child_id in old { + new.extend(apply_rules(rules, ast, child_id, fresh)?); + } + *vec = new; } node.id = ast.nodes.len(); ast.nodes.push(node); - vec![ast.nodes.len() - 1] + Ok(vec![ast.nodes.len() - 1]) } pub struct Runner { @@ -597,28 +594,30 @@ impl Runner { Self { language, rules } } - pub fn run_from_tree(&self, tree: &tree_sitter::Tree) -> Ast { + pub fn run_from_tree(&self, tree: &tree_sitter::Tree) -> Result { 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, &fresh); + let res = apply_rules(&self.rules, &mut ast, 0, &fresh)?; if res.len() != 1 { - panic!("Expected at exactly one result node, got {}", res.len()); + return Err(format!("Expected exactly one result node, got {}", res.len())); } ast.set_root(res[0]); - ast + Ok(ast) } - pub fn run(&self, input: &str) -> Ast { + pub fn run(&self, input: &str) -> Result { 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(); + parser.set_language(&self.language) + .map_err(|e| format!("Failed to set language: {e}"))?; + let tree = parser.parse(input, None) + .ok_or_else(|| "Failed to parse input".to_string())?; let mut ast = Ast::from_tree(self.language.clone(), &tree); - let res = apply_rules(&self.rules, &mut ast, 0, &fresh); + let res = apply_rules(&self.rules, &mut ast, 0, &fresh)?; if res.len() != 1 { - panic!("Expected at exactly one result node, got {}", res.len()); + return Err(format!("Expected exactly one result node, got {}", res.len())); } ast.set_root(res[0]); - ast + Ok(ast) } } diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 9df89c8c3d4..9cc9df72945 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -6,7 +6,7 @@ use yeast::*; /// Helper: parse Ruby source, apply rules, return dump of result. fn run_and_dump(input: &str, rules: Vec) -> String { let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), rules); - let ast = runner.run(input); + let ast = runner.run(input).unwrap(); dump_ast(&ast, ast.get_root(), input) } @@ -64,7 +64,7 @@ program #[test] fn test_query_match() { let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), vec![]); - let ast = runner.run("x = 1"); + let ast = runner.run("x = 1").unwrap(); let query = yeast::query!( (program @@ -85,7 +85,7 @@ fn test_query_match() { #[test] fn test_query_no_match() { let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), vec![]); - let ast = runner.run("x = 1"); + let ast = runner.run("x = 1").unwrap(); let query = yeast::query!( (program @@ -103,7 +103,7 @@ fn test_query_no_match() { #[test] fn test_query_repeated_capture() { let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), vec![]); - let ast = runner.run("x, y, z = 1"); + let ast = runner.run("x, y, z = 1").unwrap(); let query = yeast::query!( (assignment @@ -129,7 +129,7 @@ fn test_query_repeated_capture() { #[test] fn test_tree_builder() { let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), vec![]); - let mut ast = runner.run("x = 1"); + let mut ast = runner.run("x = 1").unwrap(); let input = "x = 1"; let query = yeast::query!( @@ -286,7 +286,7 @@ program #[test] fn test_cursor_navigation() { let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), vec![]); - let ast = runner.run("x = 1"); + let ast = runner.run("x = 1").unwrap(); let mut cursor = AstCursor::new(&ast); // Start at root