diff --git a/shared/yeast-macros/src/lib.rs b/shared/yeast-macros/src/lib.rs index e9f7b46d0570..6793c48857c3 100644 --- a/shared/yeast-macros/src/lib.rs +++ b/shared/yeast-macros/src/lib.rs @@ -131,8 +131,6 @@ pub fn trees(input: TokenStream) -> TokenStream { /// (output_template) /// ) /// -/// // Shorthand: captures become fields on the output node -/// rule!((query ...) => output_kind) /// ``` /// /// Captures become Rust variables automatically: diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 87fb5435bcd1..c81febe7a649 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -768,8 +768,7 @@ fn extract_captures_inner( /// ``` /// /// Template bodies (`=> (kind …)`) never carry an annotation — the -/// output kind is the template root. The shorthand `=> kind` (no -/// body) also carries no annotation. See `parse_rule_top` for dispatch. +/// output kind is the template root. #[derive(Clone, Debug)] struct ReturnAnnotation { kind: Ident, @@ -793,7 +792,6 @@ enum AnnotationMultiplicity { /// `kind {` → annotation (single) /// `kind? {` → annotation (optional) /// `kind* {` → annotation (repeated) -/// `kind` → shorthand form (no `{` follows) — NOT an annotation /// anything else → template or bare block — NOT an annotation fn try_consume_return_annotation(tokens: &mut Tokens) -> Result> { // Must start with an identifier (the kind name). @@ -881,15 +879,13 @@ pub fn parse_rule_top(input: TokenStream) -> Result { let raw_bindings = capture_bindings(raw_captures.into_iter()); let translated_bindings = capture_bindings(translated_captures.into_iter()); - // Parse transform: the token(s) after `=>` fall into one of three + // Parse transform: the token(s) after `=>` fall into one of two // shapes, dispatched in order: // // 1. `kind [? | *] { rust_body }` — annotated Rust body (NEW). // Static-analysis-ready: the annotation declares the output // kind and multiplicity in the schema's own vocabulary. - // 2. `kind` alone — shorthand: emit `(kind field: {@cap})…` from - // the query's captures. - // 3. anything else — full template form (`(kind …)` or bare + // 2. anything else — full template form (`(kind …)` or bare // `{ … }` splice via `parse_direct_list`). let annotation = try_consume_return_annotation(&mut tokens)?; @@ -926,65 +922,6 @@ pub fn parse_rule_top(input: TokenStream) -> Result { yeast::IntoFieldIds::extend_into(__value, &mut __ids); __ids } - } else if peek_is_field(&mut tokens) && { - // Shorthand form: bare identifier = output node kind. - // Auto-generate template from captures. - let mut lookahead = tokens.clone(); - lookahead.next(); // skip ident - lookahead.peek().is_none() // nothing after = shorthand - } { - let output_kind = expect_ident(&mut tokens, "expected output node kind")?; - let output_kind_str = output_kind.to_string(); - - // Generate field assignments from captures - let field_stmts: Vec = captures - .iter() - .map(|cap| { - let name = Ident::new(&cap.name, Span::call_site()); - let name_str = &cap.name; - match cap.multiplicity { - CaptureMultiplicity::Repeated => quote! { - let __field_id = #ctx_ident.ast.field_id_for_name(#name_str) - .unwrap_or_else(|| panic!("field '{}' not found", #name_str)); - __fields.insert( - __field_id, - #name.into_iter() - .map(::std::convert::Into::::into) - .collect(), - ); - }, - CaptureMultiplicity::Optional => quote! { - let __field_id = #ctx_ident.ast.field_id_for_name(#name_str) - .unwrap_or_else(|| panic!("field '{}' not found", #name_str)); - if let Some(__id) = #name { - __fields.entry(__field_id).or_insert_with(Vec::new) - .push(::std::convert::Into::::into(__id)); - } - }, - CaptureMultiplicity::Single => quote! { - let __field_id = #ctx_ident.ast.field_id_for_name(#name_str) - .unwrap_or_else(|| panic!("field '{}' not found", #name_str)); - __fields.entry(__field_id).or_insert_with(Vec::new) - .push(::std::convert::Into::::into(#name)); - }, - } - }) - .collect(); - - quote! { - let __kind = #ctx_ident.ast.id_for_node_kind(#output_kind_str) - .unwrap_or_else(|| panic!("node kind '{}' not found", #output_kind_str)); - let mut __fields = std::collections::BTreeMap::new(); - #(#field_stmts)* - let __id = #ctx_ident.ast.create_node_with_range( - __kind, - yeast::NodeContent::DynamicString(String::new()), - __fields, - true, - __source_range, - ); - vec![__id] - } } else { // Reject bare `{ ... }` transforms — they used to be accepted // as either a Rust body producing a `Vec` or a template @@ -1482,8 +1419,5 @@ mod rules_tests { // Match expressions inside a block: `=>` is inside braces. let toks = quote! { { match x { 1 => 2, _ => 3 } } }; assert!(!has_top_level_arrow(&toks)); - // Bare shorthand form: top-level `=>` followed by a bare ident. - let toks = quote! { (a) => kind }; - assert!(has_top_level_arrow(&toks)); } } diff --git a/shared/yeast/doc/yeast.md b/shared/yeast/doc/yeast.md index 3e3e1cd3610f..7e1678168d38 100644 --- a/shared/yeast/doc/yeast.md +++ b/shared/yeast/doc/yeast.md @@ -422,7 +422,7 @@ automatically: single captures bind as `Id`, repeated captures (after ## The `rule!` macro `rule!` combines a query and a transform into a single declaration. -There are three transform forms, each suited to a different level of +There are two transform forms, each suited to a different level of rule complexity: ```rust @@ -433,13 +433,7 @@ yeast::rule!( (output_template field: {capture}) ) -// 2. Shorthand form — captures become fields on a bare output kind. -yeast::rule!( - (query_pattern field: (_) @capture) - => output_kind -) - -// 3. Annotation form — a Rust block body preceded by the output kind. +// 2. Annotation form — a Rust block body preceded by the output kind. yeast::rule!( (query_pattern child: (_)+ @@children) => @@ -455,9 +449,6 @@ yeast::rule!( ) ``` -The shorthand `=> kind` form auto-generates the template, mapping each -capture name to a field of the same name on the output node. - ### Guards A rule may include a Rust guard between its query and `=>`. The guard runs @@ -537,8 +528,6 @@ having to inspect the block's expression. Prefer the simplest form that fits: - If the whole transform is a tree literal, use the **template form**. -- If the transform is a template whose root matches a query capture - 1:1, use the **shorthand form**. - If the transform needs Rust logic (loops, `let` bindings, calls to `ctx.translate`, etc.), use the **annotation form**. diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 35393685b5ca..e0abe51053d9 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -896,28 +896,6 @@ fn test_desugar_for_loop() { ); } -#[test] -fn test_shorthand_rule() { - let rule: Rule = yeast::rule!( - (assignment - left: (_) @method - right: (_) @receiver - ) - => call - ); - - let dump = run_and_dump("x = 1", vec![rule]); - assert_dump_eq( - &dump, - r#" - program - call - method: identifier "x" - receiver: integer "1" - "#, - ); -} - #[derive(Clone, Default)] struct GuardTestContext { enabled: bool, @@ -1024,19 +1002,19 @@ fn test_chained_rules_output_only_kind() { // first_node → second_node (output-only → output-only) // The matcher must look up `first_node` against the schema, which only // knows about it via the YAML node-types file. - let assignment_to_first = yeast::rule!( + let assignment_to_first: Rule = yeast::rule!( (assignment left: (_) @left right: (_) @right ) - => first_node + => (first_node left: {left} right: {right}) ); - let first_to_second = yeast::rule!( + let first_to_second: Rule = yeast::rule!( (first_node left: (_) @left right: (_) @right ) - => second_node + => (second_node left: {left} right: {right}) ); let dump = run_and_dump("x = 1", vec![assignment_to_first, first_to_second]); @@ -1103,19 +1081,19 @@ fn test_phased_desugaring() { // Two phases that could equally have been a single one with chained // rules. Splitting them makes the intent (cleanup, then desugar) // explicit and provides per-phase error messages. - let cleanup = vec![yeast::rule!( + let cleanup: Vec = vec![yeast::rule!( (assignment left: (_) @left right: (_) @right ) - => first_node + => (first_node left: {left} right: {right}) )]; - let desugar = vec![yeast::rule!( + let desugar: Vec = vec![yeast::rule!( (first_node left: (_) @left right: (_) @right ) - => second_node + => (second_node left: {left} right: {right}) )]; let dump = run_phased_and_dump( @@ -1731,33 +1709,6 @@ fn test_rules_macro_accepts_bare_rule_body() { ); } -/// The bare-rule-body shorthand `=> output_kind` should also be accepted. -#[test] -fn test_rules_macro_accepts_bare_shorthand_form() { - let rules: Vec = yeast::rules! { - input: "tests/input-types.yml", - output: "tests/node-types.yml", - [ - (assignment - left: (_) @method - right: (_) @receiver - ) - => call, - ] - }; - - let dump = run_and_dump("x = 1", rules); - assert_dump_eq( - &dump, - r#" - program - call - method: identifier "x" - receiver: integer "1" - "#, - ); -} - #[test] fn test_rules_macro_accepts_bare_guarded_rule() { let rules: Vec = yeast::rules! { @@ -1926,27 +1877,3 @@ fn test_rule_annotation_single() { } assert!(has_assignment, "expected an assignment node"); } - -/// The shorthand `=> kind` form (no body, no annotation) must still be -/// distinguished from the annotation form and continue to work. -#[test] -fn test_shorthand_still_works_alongside_annotation_syntax() { - let r: Rule = rule!( - (assignment left: (_) @method right: (_) @receiver) - => - call - ); - let ast = run_and_ast("x = 1", vec![r]); - let mut has_call = false; - for id in ast.reachable_node_ids() { - if let Some(n) = ast.get_node(id) { - if n.kind_name() == "call" { - has_call = true; - } - } - } - assert!( - has_call, - "shorthand form should still produce a `call` node" - ); -}