From cc3c23263149d0f03305ef38bf9554caacea1bbb Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 26 Jun 2026 12:45:35 +0000 Subject: [PATCH] yeast: Replace `{..expr}` splice syntax with trait-dispatched `{expr}` In the initial implementation of yeast, the splice syntax was needed do distinguish between splicing multiple nodes or just a single node. However, this was always an ugly "wart" in the syntax, since the user shouldn't have to worry about these things. To fix this, we add an `IntoFieldIds` trait that dispatches on the value's type: `Id` pushes a single id, and a blanket impl for `IntoIterator>` handles `Vec`, `Option`, and arbitrary iterator chains. With this, we no longer need to use the special splice syntax, and hence we can get rid of it. --- shared/yeast-macros/src/lib.rs | 13 +- shared/yeast-macros/src/parse.rs | 100 +++---- shared/yeast/doc/yeast.md | 38 ++- shared/yeast/src/lib.rs | 30 ++ shared/yeast/tests/test.rs | 16 +- .../extractor/src/languages/swift/swift.rs | 274 +++++++++--------- 6 files changed, 246 insertions(+), 225 deletions(-) diff --git a/shared/yeast-macros/src/lib.rs b/shared/yeast-macros/src/lib.rs index 07077be51f0..420f9fc70c6 100644 --- a/shared/yeast-macros/src/lib.rs +++ b/shared/yeast-macros/src/lib.rs @@ -41,15 +41,18 @@ pub fn query(input: TokenStream) -> TokenStream { /// (kind "literal") - leaf with static content /// (kind #{expr}) - leaf with computed content (expr.to_string()) /// (kind $fresh) - leaf with auto-generated unique name -/// {expr} - embed a Rust expression returning Id -/// {..expr} - splice an iterable of Id (in child/field position) -/// field: {..expr} - splice into a named field +/// {expr} - embed a Rust expression, dispatched via +/// the `IntoFieldIds` trait: `Id` pushes a +/// single id; iterables (`Vec`, +/// `Option`, iterator chains) splice +/// their elements +/// field: {expr} - extend a named field with `{expr}`'s ids /// {expr}.map(p -> tpl) - apply tpl to each element; splice result /// {expr}.reduce_left(f -> init, acc, e -> fold) /// - fold with per-element init; splice 0 or 1 result /// ``` /// -/// Chain syntax after `{expr}` or `{..expr}`: +/// Chain syntax after `{expr}`: /// - `.map(param -> template)` — one output node per input element. /// - `.reduce_left(first -> init, acc, elem -> fold)` — fold left; the first /// element is converted by `init`, subsequent elements are folded by `fold` @@ -100,7 +103,7 @@ pub fn trees(input: TokenStream) -> TokenStream { /// rule!( /// (query_pattern field: (_) @name (kind)* @repeated (_)? @optional) /// => -/// (output_template field: {name} {..repeated}) +/// (output_template field: {name} {repeated}) /// ) /// /// // Shorthand: captures become fields on the output node diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 7b5e783e4ed..2422d0a8a5c 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -429,45 +429,41 @@ fn parse_direct_node_inner(tokens: &mut Tokens, ctx: &Ident) -> Result into the field + // Check for field: {expr}.chain (chain pipeline) or plain field: {expr} + // (trait-dispatched: handles single ids and iterables uniformly). if peek_is_group(tokens, Delimiter::Brace) { - let group_clone = tokens.clone().next().unwrap(); - if let TokenTree::Group(g) = &group_clone { - let mut inner_check = g.stream().into_iter(); - let is_splice = matches!(inner_check.next(), Some(TokenTree::Punct(p)) if p.as_char() == '.') - && matches!(inner_check.next(), Some(TokenTree::Punct(p)) if p.as_char() == '.'); - // Determine if a chain (.map(..)) follows the `{}` group. - let mut after = tokens.clone(); - after.next(); // skip the brace group - let has_chain = - matches!(after.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '.'); + // Determine if a chain (.map(..)) follows the `{}` group. + let mut after = tokens.clone(); + after.next(); // skip the brace group + let has_chain = matches!(after.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '.'); - if is_splice || has_chain { - let group = expect_group(tokens, Delimiter::Brace)?; - let base: TokenStream = if is_splice { - let mut inner = group.stream().into_iter().peekable(); - inner.next(); // consume first . - inner.next(); // consume second . - let expr: TokenStream = inner.collect(); - quote! { - { #expr }.into_iter().map(::std::convert::Into::::into) - } - } else { - let expr = group.stream(); - quote! { { #expr }.into_iter() } - }; - let chained = parse_chain_suffix(tokens, ctx, base)?; - stmts.push(quote! { - let #temp: Vec = #chained.collect(); - }); - // An empty splice means the field is absent — skip it - // entirely rather than emitting an empty named field. - field_args.push(quote! { - if !#temp.is_empty() { __fields.push((#field_str, #temp)); } - }); - continue; - } + if has_chain { + let group = expect_group(tokens, Delimiter::Brace)?; + let expr = group.stream(); + let base = quote! { { #expr }.into_iter() }; + let chained = parse_chain_suffix(tokens, ctx, base)?; + stmts.push(quote! { + let #temp: Vec = #chained.collect(); + }); + // An empty pipeline means the field is absent — skip it + // entirely rather than emitting an empty named field. + field_args.push(quote! { + if !#temp.is_empty() { __fields.push((#field_str, #temp)); } + }); + continue; } + + // Plain `{expr}` — trait-dispatched extend. + let group = expect_group(tokens, Delimiter::Brace)?; + let expr = group.stream(); + stmts.push(quote! { + let mut #temp: Vec = Vec::new(); + yeast::IntoFieldIds::extend_into({ #expr }, &mut #temp); + }); + field_args.push(quote! { + if !#temp.is_empty() { __fields.push((#field_str, #temp)); } + }); + continue; } let value = parse_direct_node(tokens, ctx)?; @@ -495,8 +491,7 @@ fn parse_direct_node_inner(tokens: &mut Tokens, ctx: &Ident) -> Result template) -- iterator map: produces Vec @@ -603,25 +598,15 @@ fn parse_direct_list(tokens: &mut Tokens, ctx: &Ident) -> Result::into) - } - } else { - let expr = group.stream(); - quote! { { #expr }.into_iter() } - }; + if has_chain { + let expr = group.stream(); + let base = quote! { { #expr }.into_iter() }; let chained = parse_chain_suffix(tokens, ctx, base)?; items.push(quote! { __nodes.extend(#chained); @@ -629,7 +614,7 @@ fn parse_direct_list(tokens: &mut Tokens, ctx: &Ident) -> Result::into({ #expr })); + yeast::IntoFieldIds::extend_into({ #expr }, &mut __nodes); }); } continue; @@ -951,13 +936,6 @@ fn peek_is_hash(tokens: &mut Tokens) -> bool { matches!(tokens.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '#') } -/// Check for `..` (two consecutive dot punctuation tokens). -fn peek_is_dotdot(tokens: &Tokens) -> bool { - let mut lookahead = tokens.clone(); - matches!(lookahead.next(), Some(TokenTree::Punct(p)) if p.as_char() == '.') - && matches!(lookahead.next(), Some(TokenTree::Punct(p)) if p.as_char() == '.') -} - fn peek_is_underscore(tokens: &mut Tokens) -> bool { matches!(tokens.peek(), Some(TokenTree::Ident(id)) if *id == "_") } diff --git a/shared/yeast/doc/yeast.md b/shared/yeast/doc/yeast.md index dad36bb0edb..8ea2e67b2de 100644 --- a/shared/yeast/doc/yeast.md +++ b/shared/yeast/doc/yeast.md @@ -214,7 +214,7 @@ yeast::tree!(ctx, ```rust yeast::trees!(ctx, (assignment left: {tmp} right: {right}) - {..body} + {body} ) ``` @@ -256,12 +256,26 @@ occurrences of the same `$name` within one `BuildCtx` share the same value: ### Embedded Rust expressions -`{expr}` embeds a Rust expression that returns a single node `Id`: +`{expr}` embeds a Rust expression whose value is appended to the +enclosing field (or to the rule body's id list). Dispatch happens via +the [`IntoFieldIds`] trait, which is implemented for: + +- `Id` — pushes the single id. +- Any `IntoIterator>` — extends with all yielded ids + (covers `Vec`, `Option`, iterator chains, etc.). + +So the same `{expr}` syntax handles single ids, splices, and zero-or-many +options uniformly: ```rust (assignment - left: {some_node_id} // insert a pre-built node - right: {rhs} // insert a captured value (inside rule!) + left: {some_node_id} // a single Id + right: {rhs} // a captured value (inside rule!) +) + +yeast::trees!(ctx, + (assignment left: {tmp} right: {right}) + {extra_nodes} // splices a Vec ) ``` @@ -277,21 +291,17 @@ expressions (with `let` bindings) work too: }) ``` -`{..expr}` splices a `Vec` (or any iterable of `Id`); the contents -are likewise a Rust block, so the splice can be the result of arbitrary -computation: +Inside `rule!`, captures are Rust variables — `{name}` works for +single, optional, and repeated captures alike: ```rust -yeast::trees!(ctx, - (assignment left: {tmp} right: {right}) - {..extra_nodes} // splice a Vec +rule!( + (assignment left: @lhs right: _* @parts) + => + (assignment left: {lhs} right: (block stmt: {parts})) ) ``` -Inside `rule!`, captures are Rust variables, so `{name}` inserts a -single capture (`Id`) and `{..name}` splices a repeated capture -(`Vec`). - ### Raw captures (`@@name`) The default `@name` capture marker is *auto-translated*: in OneShot diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index bfcaface53a..2c53f756fdf 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -48,6 +48,36 @@ impl From for usize { type FieldId = u16; type KindId = u16; +/// Trait for values that can be appended to a field's id list inside a +/// `tree!`/`trees!`/`rule!` template (in `{expr}` placeholders). +/// +/// `Id` pushes a single id; the blanket impl for +/// `IntoIterator>` handles `Vec`, `Option`, +/// arbitrary iterators yielding `Id`, etc. +/// +/// This lets `{expr}` interpolate any of these shapes without a +/// dedicated splice syntax — the macro emits the same trait-dispatched +/// call regardless of the value's type. +pub trait IntoFieldIds { + fn extend_into(self, out: &mut Vec); +} + +impl IntoFieldIds for Id { + fn extend_into(self, out: &mut Vec) { + out.push(self); + } +} + +impl IntoFieldIds for I +where + I: IntoIterator, + T: Into, +{ + fn extend_into(self, out: &mut Vec) { + out.extend(self.into_iter().map(Into::into)); + } +} + /// Like [`std::fmt::Display`], but the formatting routine is given access to /// the [`Ast`] so that node references can resolve to their source text. /// diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 0399316b305..3cc02838fad 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -635,7 +635,7 @@ fn ruby_rules() -> Vec { left: (identifier $tmp) right: {right} ) - {..left.iter().enumerate().map(|(i, &lhs)| + {left.iter().enumerate().map(|(i, &lhs)| yeast::tree!( (assignment left: {lhs} @@ -667,7 +667,7 @@ fn ruby_rules() -> Vec { left: {pat} right: (identifier $tmp) ) - stmt: {..body} + stmt: {body} ) ) ) @@ -903,7 +903,7 @@ fn one_shot_xeq1_rules() -> Vec { yeast::rule!( (program (_)* @stmts) => - (program stmt: {..stmts}) + (program stmt: {stmts}) ), yeast::rule!( (assignment left: (_) @left right: (_) @right) @@ -979,7 +979,7 @@ fn test_one_shot_recurses_into_returned_capture() { yeast::rule!( (program (_)* @stmts) => - (program stmt: {..stmts}) + (program stmt: {stmts}) ), // Returns the captured `left` verbatim, discarding `right`. yeast::rule!( @@ -1021,7 +1021,7 @@ fn test_one_shot_does_not_recurse_into_wrapper_output() { yeast::rule!( (program (_)* @stmts) => - (program stmt: {..stmts}) + (program stmt: {stmts}) ), // Wraps `left` in nested `first_node`/`second_node` output kinds. // Neither wrapper kind has a matching rule, so a buggy implementation @@ -1072,7 +1072,7 @@ fn test_raw_capture_marker() { yeast::rule!( (program (_)* @stmts) => - (program stmt: {..stmts}) + (program stmt: {stmts}) ), // `@@raw_lhs` is untranslated: the body reads its source text // ("x") and embeds it directly as the identifier content. `@rhs` @@ -1130,7 +1130,7 @@ fn test_raw_capture_marker_explicit_translate() { yeast::rule!( (program (_)* @stmts) => - (program stmt: {..stmts}) + (program stmt: {stmts}) ), yeast::rule!( (assignment left: (_) @@raw_lhs right: (_) @rhs) @@ -1138,7 +1138,7 @@ fn test_raw_capture_marker_explicit_translate() { { let translated_lhs = ctx.translate(raw_lhs)?; tree!((call - method: {..translated_lhs} + method: {translated_lhs} receiver: {rhs})) } ), diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 53ecd397514..5689d930bff 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -45,7 +45,7 @@ struct SwiftContext { /// Build a freshly-created `chained_declaration` modifier node if /// `ctx.is_chained`, else `None`. Used by inner declaration rules to /// emit the chained tag for non-first children of a flattening outer -/// rule. Returns `Option` so it splices via `{..…}` to 0 or 1 ids. +/// rule. Returns `Option` so it splices via `{…}` to 0 or 1 ids. fn chained_modifier(ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>) -> Option { if ctx.is_chained { Some(ctx.literal("modifier", "chained_declaration")) @@ -100,7 +100,7 @@ fn translation_rules() -> Vec> { (source_file statement: _* @children) => (top_level - body: (block stmt: {..children}) + body: (block stmt: {children}) ) ), // Declarations may be wrapped in local/global wrapper nodes. @@ -144,12 +144,12 @@ fn translation_rules() -> Vec> { rule!( (operator_declaration "prefix" (referenceable_operator _ @op) (simple_identifier)? @prec) => - (operator_syntax_declaration name: (identifier #{op}) fixity: (fixity "prefix") precedence: {..prec}) + (operator_syntax_declaration name: (identifier #{op}) fixity: (fixity "prefix") precedence: {prec}) ), rule!( (operator_declaration "postfix" (referenceable_operator _ @op) (simple_identifier)? @prec) => - (operator_syntax_declaration name: (identifier #{op}) fixity: (fixity "postfix") precedence: {..prec}) + (operator_syntax_declaration name: (identifier #{op}) fixity: (fixity "postfix") precedence: {prec}) ), rule!( (operator_declaration "infix" (referenceable_operator _ @op) (simple_identifier)? @prec) @@ -157,7 +157,7 @@ fn translation_rules() -> Vec> { (operator_syntax_declaration name: (identifier #{op}) fixity: (fixity "infix") - precedence: {..prec}) + precedence: {prec}) ), rule!((bitwise_operation lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})), rule!((nil_coalescing_expression value: @l if_nil: @r) => (binary_expr left: {l} operator: (infix_operator "??") right: {r})), @@ -170,9 +170,9 @@ fn translation_rules() -> Vec> { rule!((postfix_expression operation: @op target: @operand) => (unary_expr operator: (postfix_operator #{op}) operand: {operand})), // TODO: Parenthesised single-value tuple is a grouping expression and should pass through. // Multi-value tuples become tuple_expr. - rule!((tuple_expression value: _* @v) => (tuple_expr element: {..v})), + rule!((tuple_expression value: _* @v) => (tuple_expr element: {v})), // Blocks contain statement* directly. - rule!((block statement: _+ @stmts) => (block stmt: {..stmts})), + rule!((block statement: _+ @stmts) => (block stmt: {stmts})), rule!((block) => (block)), // ---- Variables ---- // property_binding rules — these produce variable_declaration and/or accessor_declaration @@ -198,7 +198,7 @@ fn translation_rules() -> Vec> { type: _? @ty computed_value: (computed_property accessor: _+ @@accessors)) => - {..{ + {{ ctx.property_name = Some(tree!((identifier #{pattern}))); ctx.property_type = ty; @@ -223,13 +223,13 @@ fn translation_rules() -> Vec> { computed_value: (computed_property statement: _* @body)) => (accessor_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} name: (identifier #{name}) - type: {..ty} + type: {ty} accessor_kind: (accessor_kind "get") - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // Stored property with willSet/didSet observers (initializer // optional) → a `variable_declaration` followed by one @@ -249,15 +249,15 @@ fn translation_rules() -> Vec> { value: _? @val observers: (willset_didset_block willset: _? @@ws didset: _? @@ds)) => - {..{ + {{ let var_decl = tree!( (variable_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} pattern: (name_pattern identifier: (identifier #{name})) - type: {..ty} - value: {..val}) + type: {ty} + value: {val}) ); // Publish the property name for the observer rules. @@ -282,12 +282,12 @@ fn translation_rules() -> Vec> { value: _? @val) => (variable_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} pattern: {pattern} - type: {..ty} - value: {..val}) + type: {ty} + value: {val}) ), // property_declaration: flatten declarators (each may translate // to multiple nodes — variable_declaration and/or @@ -305,7 +305,7 @@ fn translation_rules() -> Vec> { declarator: _* @@decls (modifiers)* @mods) => - {..{ + {{ let binding_text = ctx.ast.source_text(binding_kind); ctx.binding_modifier = Some(ctx.literal("modifier", &binding_text)); ctx.outer_modifiers = mods; @@ -342,19 +342,19 @@ fn translation_rules() -> Vec> { data_contents: (enum_type_parameters parameter: _* @params)) => (class_like_declaration - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} modifier: (modifier "enum_case") name: (identifier #{name}) - member: (constructor_declaration parameter: {..params} body: (block))) + member: (constructor_declaration parameter: {params} body: (block))) ), // enum_case_entry with explicit raw value → variable_declaration with that value. rule!( (enum_case_entry name: @name raw_value: @val) => (variable_declaration - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} modifier: (modifier "enum_case") pattern: (name_pattern identifier: (identifier #{name})) value: {val}) @@ -364,8 +364,8 @@ fn translation_rules() -> Vec> { (enum_case_entry name: @name) => (variable_declaration - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} modifier: (modifier "enum_case") pattern: (name_pattern identifier: (identifier #{name}))) ), @@ -376,7 +376,7 @@ fn translation_rules() -> Vec> { rule!( (enum_entry case: _+ @@cases (modifiers)* @mods) => - {..{ + {{ ctx.outer_modifiers = mods; let mut result = Vec::new(); @@ -418,7 +418,7 @@ fn translation_rules() -> Vec> { => (constructor_pattern constructor: (member_access_expr base: {typ} member: (identifier #{name})) - element: {..items}) + element: {items}) ), // case .foo(x,y) pattern rule!( @@ -426,10 +426,10 @@ fn translation_rules() -> Vec> { => (constructor_pattern constructor: (member_access_expr base: (inferred_type_expr #{dot}) member: (identifier #{name})) - element: {..items}) + element: {items}) ), // Tuple pattern and its (optionally named) items - rule!((pattern kind: (tuple_pattern item: _* @elems)) => (tuple_pattern element: {..elems})), + rule!((pattern kind: (tuple_pattern item: _* @elems)) => (tuple_pattern element: {elems})), rule!((tuple_pattern_item name: @key pattern: @pat) => (pattern_element key: (identifier #{key}) pattern: {pat})), rule!((tuple_pattern_item pattern: @pat) => (pattern_element pattern: {pat})), // Type casting pattern (TODO) @@ -452,9 +452,9 @@ fn translation_rules() -> Vec> { => (function_declaration name: (identifier #{name}) - parameter: {..params} - return_type: {..ret} - body: (block stmt: {..body_stmts})) + parameter: {params} + return_type: {ret} + body: (block stmt: {body_stmts})) ), // Parameters are wrapped in function_parameter, which also carries // optional default values. Publishes the default value into `ctx` @@ -463,7 +463,7 @@ fn translation_rules() -> Vec> { rule!( (function_parameter parameter: @@p default_value: _? @def) => - {..{ + {{ ctx.default_value = def; ctx.translate(p)? }} @@ -475,7 +475,7 @@ fn translation_rules() -> Vec> { (parameter external_name: (identifier #{ext}) pattern: (name_pattern identifier: (identifier #{name})) - default: {..ctx.default_value}) + default: {ctx.default_value}) ), rule!( (parameter external_name: @ext name: @name type: @ty) @@ -484,7 +484,7 @@ fn translation_rules() -> Vec> { external_name: (identifier #{ext}) pattern: (name_pattern identifier: (identifier #{name})) type: {ty} - default: {..ctx.default_value}) + default: {ctx.default_value}) ), // Parameter with just name and type (no external name) rule!( @@ -492,7 +492,7 @@ fn translation_rules() -> Vec> { => (parameter pattern: (name_pattern identifier: (identifier #{name})) - default: {..ctx.default_value}) + default: {ctx.default_value}) ), rule!( (parameter name: @name type: @ty) @@ -500,7 +500,7 @@ fn translation_rules() -> Vec> { (parameter pattern: (name_pattern identifier: (identifier #{name})) type: {ty} - default: {..ctx.default_value}) + default: {ctx.default_value}) ), // Reference to a function, f(x:y:z:). This is parsed as a call with a single argument with multiple reference_specifier labels. // We don't want downstream QL to try to handle this as a call_expr with a weird argument, so explicitly mark it as unsupported for now. @@ -514,7 +514,7 @@ fn translation_rules() -> Vec> { rule!( (call_expression function: @func suffix: (call_suffix arguments: (value_arguments argument: (value_argument)* @args))) => - (call_expr callee: {func} argument: {..args}) + (call_expr callee: {func} argument: {args}) ), // Value argument with label (value: _ matches both named nodes and anonymous tokens like nil) rule!( @@ -537,7 +537,7 @@ fn translation_rules() -> Vec> { // Return / break / continue, one rule per keyword. // The anonymous "return"/"break"/"continue" keywords are matched as // string literals. - rule!((control_transfer_statement kind: "return" result: _? @val) => (return_expr value: {..val})), + rule!((control_transfer_statement kind: "return" result: _? @val) => (return_expr value: {val})), rule!((control_transfer_statement kind: "break" result: @lbl) => (break_expr label: (identifier #{lbl}))), rule!((control_transfer_statement kind: "break") => (break_expr)), rule!((control_transfer_statement kind: "continue" result: @lbl) => (continue_expr label: (identifier #{lbl}))), @@ -556,20 +556,20 @@ fn translation_rules() -> Vec> { statement: _* @body) => (function_expr - modifier: {..attrs} - capture_declaration: {..captures} - parameter: {..params} - return_type: {..ret} - body: (block stmt: {..body})) + modifier: {attrs} + capture_declaration: {captures} + parameter: {params} + return_type: {ret} + body: (block stmt: {body})) ), // capture_list_item with ownership modifier (e.g. [weak self], [unowned x]) rule!( (capture_list_item ownership: _? @ownership name: @name value: _? @val) => (variable_declaration - modifier: {..ownership} + modifier: {ownership} pattern: (name_pattern identifier: (identifier #{name})) - value: {..val}) + value: {val}) ), // Lambda parameter with type and optional external name rule!( @@ -615,7 +615,7 @@ fn translation_rules() -> Vec> { (if_expr condition: {and_chain(&mut ctx, cond)} then: {then_body} - else: {..else_stmts}) + else: {else_stmts}) ), // Guard statement rule!( @@ -623,7 +623,7 @@ fn translation_rules() -> Vec> { => (guard_if_stmt condition: {and_chain(&mut ctx, cond)} - else: (block stmt: {..else_stmts})) + else: (block stmt: {else_stmts})) ), // Ternary expression → if_expr rule!( @@ -635,7 +635,7 @@ fn translation_rules() -> Vec> { rule!( (switch_statement expr: @val entry: (switch_entry)* @cases) => - (switch_expr value: {val} case: {..cases}) + (switch_expr value: {val} case: {cases}) ), // Switch entry with multiple patterns and body rule!( @@ -644,19 +644,19 @@ fn translation_rules() -> Vec> { pattern: (switch_pattern pattern: @rest)+ statement: _* @body) => - (switch_case pattern: (or_pattern pattern: {first} pattern: {..rest}) body: (block stmt: {..body})) + (switch_case pattern: (or_pattern pattern: {first} pattern: {rest}) body: (block stmt: {body})) ), // Switch entry with exactly one pattern and body rule!( (switch_entry pattern: (switch_pattern pattern: @pat) statement: _* @body) => - (switch_case pattern: {pat} body: (block stmt: {..body})) + (switch_case pattern: {pat} body: (block stmt: {body})) ), // Switch entry: default case (no patterns) rule!( (switch_entry default: (default_keyword) statement: _* @body) => - (switch_case body: (block stmt: {..body})) + (switch_case body: (block stmt: {body})) ), // if case PATTERN = expr — preserve the pattern directly (no Optional wrapping) rule!( @@ -702,8 +702,8 @@ fn translation_rules() -> Vec> { (for_each_stmt pattern: {pat} iterable: {iter} - guard: {..guard} - body: (block stmt: {..body})) + guard: {guard} + body: (block stmt: {body})) ), // While loop rule!( @@ -711,7 +711,7 @@ fn translation_rules() -> Vec> { => (while_stmt condition: {and_chain(&mut ctx, cond)} - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // Repeat-while loop rule!( @@ -719,7 +719,7 @@ fn translation_rules() -> Vec> { => (do_while_stmt condition: {and_chain(&mut ctx, cond)} - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // Labeled statement (e.g. `outer: for ...`). Strip the trailing ':' from the label token. rule!((labeled_statement label: (statement_label) @lbl statement: @stmt) => { @@ -729,18 +729,18 @@ fn translation_rules() -> Vec> { }), // ---- Collections ---- // Array literal - rule!((array_literal element: _* @elems) => (array_literal element: {..elems})), + rule!((array_literal element: _* @elems) => (array_literal element: {elems})), // Empty array literal rule!((array_literal) => (array_literal)), // Dictionary literal — zip keys and values into key_value_pairs rule!( (dictionary_literal key: _* @keys value: _* @vals) => - (map_literal element: {..keys.into_iter().zip(vals).map(|(k, v)| + (map_literal element: {keys.into_iter().zip(vals).map(|(k, v)| tree!((key_value_pair key: {k} value: {v})) )}) ), - rule!((dictionary_literal element: _* @elems) => (map_literal element: {..elems})), + rule!((dictionary_literal element: _* @elems) => (map_literal element: {elems})), rule!((dictionary_literal_item key: @k value: @v) => (key_value_pair key: {k} value: {v})), // ---- Optionals and errors ---- // Optional chaining — unwrap the marker @@ -753,8 +753,8 @@ fn translation_rules() -> Vec> { (do_statement body: (block statement: _* @body) catch: (catch_block)* @catches) => (try_expr - body: (block stmt: {..body}) - catch_clause: {..catches}) + body: (block stmt: {body}) + catch_clause: {catches}) ), // Catch block with bound identifier; optional where-clause guard. rule!( @@ -766,14 +766,14 @@ fn translation_rules() -> Vec> { => (catch_clause pattern: {pattern} - guard: {..guard} - body: (block stmt: {..body})) + guard: {guard} + body: (block stmt: {body})) ), // Catch block without error binding rule!( (catch_block keyword: (catch_keyword) body: (block statement: _* @body)) => - (catch_clause body: (block stmt: {..body})) + (catch_clause body: (block stmt: {body})) ), // Empty catch block: catch {} rule!( @@ -787,7 +787,7 @@ fn translation_rules() -> Vec> { => (catch_clause pattern: {pat} - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // As expression (type cast) — as?, as! rule!((as_expression (as_operator) @op expr: @val type: @ty) => (type_cast_expr expr: {val} operator: (infix_operator #{op}) type: {ty})), @@ -812,7 +812,7 @@ fn translation_rules() -> Vec> { pattern: (name_pattern identifier: (identifier #{parts.last().unwrap()})) imported_expr: {name} modifier: (modifier #{kind}) - modifier: {..mods}) + modifier: {mods}) ), // Non-scoped import declaration (for example `import Foundation`): // flatten the identifier parts into a member_access_expr and use a @@ -823,7 +823,7 @@ fn translation_rules() -> Vec> { (import_declaration pattern: (bulk_importing_pattern) imported_expr: {name} - modifier: {..mods}) + modifier: {mods}) ), // ---- Types and classes ---- // Self expression → name_expr @@ -831,7 +831,7 @@ fn translation_rules() -> Vec> { // Super expression → super_expr rule!((super_expression) => (super_expr)), // Modifiers — unwrap to individual modifier children - rule!((modifiers _* @mods) => {..mods}), + rule!((modifiers _* @mods) => {mods}), rule!((attribute) @m => (modifier #{m})), rule!((visibility_modifier) @m => (modifier #{m})), rule!((function_modifier) @m => (modifier #{m})), @@ -848,7 +848,7 @@ fn translation_rules() -> Vec> { // Keep a conservative textual fallback to avoid dropping type information. rule!((user_type) @ty => (named_type_expr name: (identifier #{ty}))), // Tuple type → tuple_type_expr - rule!((tuple_type element: _* @elems) => (tuple_type_expr element: {..elems})), + rule!((tuple_type element: _* @elems) => (tuple_type_expr element: {elems})), rule!((tuple_type_item name: @name type: @ty) => (tuple_type_element name: (identifier #{name}) type: {ty})), rule!((tuple_type_item type: @ty) => (tuple_type_element type: {ty})), // Array type `[T]` → generic_type_expr with Array base @@ -865,7 +865,7 @@ fn translation_rules() -> Vec> { base: (named_type_expr name: (identifier "Optional")) type_argument: {w})), // Function type `(Params) -> Ret` → function_type_expr. - rule!((function_type parameter: _* @ps return_type: @ret) => (function_type_expr parameter: {..ps} return_type: {ret})), + rule!((function_type parameter: _* @ps return_type: @ret) => (function_type_expr parameter: {ps} return_type: {ret})), rule!((function_type_parameter name: @name type: @ty) => (parameter external_name: (identifier #{name}) type: {ty})), rule!((function_type_parameter type: @ty) => (parameter type: {ty})), // Selector expression: `#selector(inner)` -- not yet supported @@ -889,10 +889,10 @@ fn translation_rules() -> Vec> { => (class_like_declaration modifier: (modifier #{kind}) - modifier: {..mods} + modifier: {mods} name: (identifier #{name}) - base_type: {..bases} - member: {..members}) + base_type: {bases} + member: {members}) ), // Enum class declaration: same as a regular class but with an enum body. rule!( @@ -905,10 +905,10 @@ fn translation_rules() -> Vec> { => (class_like_declaration modifier: (modifier #{kind}) - modifier: {..mods} + modifier: {mods} name: (identifier #{name}) - base_type: {..bases} - member: {..members}) + base_type: {bases} + member: {members}) ), // Class declaration with empty body rule!( @@ -921,9 +921,9 @@ fn translation_rules() -> Vec> { => (class_like_declaration modifier: (modifier #{kind}) - modifier: {..mods} + modifier: {mods} name: (identifier #{name}) - base_type: {..bases}) + base_type: {bases}) ), // Protocol declaration rule!( @@ -935,10 +935,10 @@ fn translation_rules() -> Vec> { => (class_like_declaration modifier: (modifier "protocol") - modifier: {..mods} + modifier: {mods} name: (identifier #{name}) - base_type: {..bases} - member: {..members}) + base_type: {bases} + member: {members}) ), // Protocol function — return type and body statements both optional. rule!( @@ -950,11 +950,11 @@ fn translation_rules() -> Vec> { (modifiers)* @mods) => (function_declaration - modifier: {..mods} + modifier: {mods} name: (identifier #{name}) - parameter: {..params} - return_type: {..ret} - body: (block stmt: {..body_stmts})) + parameter: {params} + return_type: {ret} + body: (block stmt: {body_stmts})) ), // Init declaration → constructor_declaration. Body statements optional; // body itself is also optional (protocol requirement). @@ -965,9 +965,9 @@ fn translation_rules() -> Vec> { (modifiers)* @mods) => (constructor_declaration - modifier: {..mods} - parameter: {..params} - body: (block stmt: {..body_stmts})) + modifier: {mods} + parameter: {params} + body: (block stmt: {body_stmts})) ), // Deinit declaration → destructor_declaration. Body statements optional. rule!( @@ -976,15 +976,15 @@ fn translation_rules() -> Vec> { (modifiers)* @mods) => (destructor_declaration - modifier: {..mods} - body: (block stmt: {..body_stmts})) + modifier: {mods} + body: (block stmt: {body_stmts})) ), // Typealias declaration rule!( (typealias_declaration name: @name value: @val (modifiers)* @mods) => (type_alias_declaration - modifier: {..mods} + modifier: {mods} name: (identifier #{name}) r#type: {val}) ), @@ -999,9 +999,9 @@ fn translation_rules() -> Vec> { (associatedtype_declaration name: @name inherits_from: _? @bound (modifiers)* @mods) => (associated_type_declaration - modifier: {..mods} + modifier: {mods} name: (identifier #{name}) - bound: {..bound}) + bound: {bound}) ), // Protocol property declaration: translate each accessor // requirement to an `accessor_declaration` carrying the property @@ -1018,7 +1018,7 @@ fn translation_rules() -> Vec> { type: _? @ty (modifiers)* @mods) => - {..{ + {{ ctx.property_name = Some(tree!((identifier #{name}))); ctx.property_type = ty; ctx.outer_modifiers = mods; @@ -1040,23 +1040,23 @@ fn translation_rules() -> Vec> { => (accessor_declaration name: {ctx.property_name.ok_or("getter_specifier outside protocol_property_declaration context")?} - type: {..ctx.property_type} + type: {ctx.property_type} accessor_kind: (accessor_kind "get") - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)}) + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)}) ), rule!( (setter_specifier) => (accessor_declaration name: {ctx.property_name.ok_or("setter_specifier outside protocol_property_declaration context")?} - type: {..ctx.property_type} + type: {ctx.property_type} accessor_kind: (accessor_kind "set") - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)}) + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)}) ), // protocol_property_requirements wrapper — should be consumed by above; fallback - rule!((protocol_property_requirements accessor: _* @accs) => {..accs}), + rule!((protocol_property_requirements accessor: _* @accs) => {accs}), // Computed getter → accessor_declaration (body optional). // Reads property name/type from the outer property_binding rule // and binding/outer modifiers + chained tag from the outer @@ -1065,58 +1065,58 @@ fn translation_rules() -> Vec> { (computed_getter body: (block statement: _* @body)?) => (accessor_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} name: {ctx.property_name.ok_or("computed_getter outside property_binding context")?} - type: {..ctx.property_type} + type: {ctx.property_type} accessor_kind: (accessor_kind "get") - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // Computed setter with explicit parameter name. rule!( (computed_setter parameter: @param body: (block statement: _* @body)) => (accessor_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} name: {ctx.property_name.ok_or("computed_setter outside property_binding context")?} - type: {..ctx.property_type} + type: {ctx.property_type} accessor_kind: (accessor_kind "set") parameter: (parameter pattern: (name_pattern identifier: (identifier #{param}))) - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // Computed setter without explicit parameter name; body optional. rule!( (computed_setter body: (block statement: _* @body)?) => (accessor_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} name: {ctx.property_name.ok_or("computed_setter outside property_binding context")?} - type: {..ctx.property_type} + type: {ctx.property_type} accessor_kind: (accessor_kind "set") - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // Computed modify → accessor_declaration rule!( (computed_modify body: (block statement: _* @body)) => (accessor_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} name: {ctx.property_name.ok_or("computed_modify outside property_binding context")?} - type: {..ctx.property_type} + type: {ctx.property_type} accessor_kind: (accessor_kind "modify") - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // willset/didset block — spread to children (only reachable as a // fallback; the outer property_binding manual rule normally // captures the willset/didset clauses directly). - rule!((willset_didset_block _* @clauses) => {..clauses}), + rule!((willset_didset_block _* @clauses) => {clauses}), // willset clause → accessor_declaration (body optional). Reads // `ctx.property_name` set by the outer property_binding rule and // binding/outer modifiers + chained tag from the outer @@ -1125,24 +1125,24 @@ fn translation_rules() -> Vec> { (willset_clause body: (block statement: _* @body)?) => (accessor_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} name: {ctx.property_name.ok_or("willset_clause outside property_binding context")?} accessor_kind: (accessor_kind "willSet") - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // didset clause → accessor_declaration (body optional). rule!( (didset_clause body: (block statement: _* @body)?) => (accessor_declaration - modifier: {..ctx.binding_modifier} - modifier: {..ctx.outer_modifiers.clone()} - modifier: {..chained_modifier(&mut ctx)} + modifier: {ctx.binding_modifier} + modifier: {ctx.outer_modifiers.clone()} + modifier: {chained_modifier(&mut ctx)} name: {ctx.property_name.ok_or("didset_clause outside property_binding context")?} accessor_kind: (accessor_kind "didSet") - body: (block stmt: {..body})) + body: (block stmt: {body})) ), // Preprocessor conditionals — unsupported rule!((diagnostic) => (unsupported_node)),