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<Item: Into<Id>>` handles `Vec<Id>`, `Option<Id>`, and
arbitrary iterator chains.

With this, we no longer need to use the special splice syntax, and hence
we can get rid of it.
This commit is contained in:
Taus
2026-06-26 12:45:35 +00:00
parent 2cf88b46e2
commit dd9653877a
6 changed files with 245 additions and 224 deletions

View File

@@ -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<Id>`,
/// `Option<Id>`, 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

View File

@@ -429,45 +429,41 @@ fn parse_direct_node_inner(tokens: &mut Tokens, ctx: &Ident) -> Result<TokenStre
);
field_counter += 1;
// Check for field: {..expr}.chain or field: {expr}.chain — splice a Vec<Id> 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::<yeast::Id>::into)
}
} else {
let expr = group.stream();
quote! { { #expr }.into_iter() }
};
let chained = parse_chain_suffix(tokens, ctx, base)?;
stmts.push(quote! {
let #temp: Vec<yeast::Id> = #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<yeast::Id> = #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<yeast::Id> = 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<TokenStre
})
}
/// Parse a chain of `.method(args)` suffixes after a `{expr}` or `{..expr}`
/// placeholder in tree templates. Currently supports:
/// Parse a chain of `.method(args)` suffixes after a `{expr}` placeholder in tree templates. Currently supports:
///
/// ```text
/// .map(param -> template) -- iterator map: produces Vec<yeast::Id>
@@ -603,25 +598,15 @@ fn parse_direct_list(tokens: &mut Tokens, ctx: &Ident) -> Result<Vec<TokenStream
continue;
}
// {expr} or {..expr} (with optional .chain) — single node or splice
// {expr} (with optional `.chain` pipeline) — extend `__nodes` via
// `IntoFieldIds`, which handles single ids and iterables uniformly.
if peek_is_group(tokens, Delimiter::Brace) {
let group = expect_group(tokens, Delimiter::Brace)?;
let has_chain =
matches!(tokens.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '.');
let mut inner = group.stream().into_iter().peekable();
let is_splice = peek_is_dotdot(&inner);
if is_splice || has_chain {
let base: TokenStream = if is_splice {
inner.next(); // consume first .
inner.next(); // consume second .
let expr: TokenStream = inner.collect();
quote! {
{ #expr }.into_iter().map(::std::convert::Into::<yeast::Id>::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<Vec<TokenStream
} else {
let expr = group.stream();
items.push(quote! {
__nodes.push(::std::convert::Into::<yeast::Id>::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 == "_")
}

View File

@@ -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<Item: Into<Id>>` — extends with all yielded ids
(covers `Vec<Id>`, `Option<Id>`, 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<Id>
)
```
@@ -277,21 +291,17 @@ expressions (with `let` bindings) work too:
})
```
`{..expr}` splices a `Vec<Id>` (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<Id>
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<Id>`).
### Raw captures (`@@name`)
The default `@name` capture marker is *auto-translated*: in OneShot

View File

@@ -48,6 +48,36 @@ impl From<Id> 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<Item: Into<Id>>` handles `Vec<Id>`, `Option<Id>`,
/// 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<Id>);
}
impl IntoFieldIds for Id {
fn extend_into(self, out: &mut Vec<Id>) {
out.push(self);
}
}
impl<I, T> IntoFieldIds for I
where
I: IntoIterator<Item = T>,
T: Into<Id>,
{
fn extend_into(self, out: &mut Vec<Id>) {
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.
///

View File

@@ -635,7 +635,7 @@ fn ruby_rules() -> Vec<Rule> {
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<Rule> {
left: {pat}
right: (identifier $tmp)
)
stmt: {..body}
stmt: {body}
)
)
)
@@ -903,7 +903,7 @@ fn one_shot_xeq1_rules() -> Vec<Rule> {
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}))
}
),

View File

@@ -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<Id>` so it splices via `{..…}` to 0 or 1 ids.
/// rule. Returns `Option<Id>` so it splices via `{…}` to 0 or 1 ids.
fn chained_modifier(ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>) -> Option<yeast::Id> {
if ctx.is_chained {
Some(ctx.literal("modifier", "chained_declaration"))
@@ -100,7 +100,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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<SwiftContext>> {
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<Rule<SwiftContext>> {
(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<SwiftContext>> {
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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
(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<SwiftContext>> {
rule!(
(enum_entry case: _+ @@cases (modifiers)* @mods)
=>
{..{
{{
ctx.outer_modifiers = mods;
let mut result = Vec::new();
@@ -418,7 +418,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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<Rule<SwiftContext>> {
=>
(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<Rule<SwiftContext>> {
=>
(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<SwiftContext>> {
rule!(
(function_parameter parameter: @@p default_value: _? @def)
=>
{..{
{{
ctx.default_value = def;
ctx.translate(p)?
}}
@@ -475,7 +475,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
=>
(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<Rule<SwiftContext>> {
(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<SwiftContext>> {
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<Rule<SwiftContext>> {
// 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<Rule<SwiftContext>> {
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<Rule<SwiftContext>> {
(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<Rule<SwiftContext>> {
=>
(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,19 +635,19 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
rule!(
(switch_statement expr: @val entry: (switch_entry)* @cases)
=>
(switch_expr value: {val} case: {..cases})
(switch_expr value: {val} case: {cases})
),
// Switch entry with patterns and body
rule!(
(switch_entry pattern: (switch_pattern pattern: @pats)* statement: _* @body)
=>
(switch_case pattern: {..pats} body: (block stmt: {..body}))
(switch_case pattern: {pats} 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 let x = expr — the pattern is taken as-is (no Optional wrapping)
rule!(
@@ -693,8 +693,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(for_each_stmt
pattern: {pat}
iterable: {iter}
guard: {..guard}
body: (block stmt: {..body}))
guard: {guard}
body: (block stmt: {body}))
),
// While loop
rule!(
@@ -702,7 +702,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(while_stmt
condition: {and_chain(&mut ctx, cond)}
body: (block stmt: {..body}))
body: (block stmt: {body}))
),
// Repeat-while loop
rule!(
@@ -710,7 +710,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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) => {
@@ -720,18 +720,18 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
}),
// ---- 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
@@ -744,8 +744,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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!(
@@ -757,14 +757,14 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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!(
@@ -778,7 +778,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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})),
@@ -803,7 +803,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
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
@@ -814,7 +814,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(import_declaration
pattern: (bulk_importing_pattern)
imported_expr: {name}
modifier: {..mods})
modifier: {mods})
),
// ---- Types and classes ----
// Self expression → name_expr
@@ -822,7 +822,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// 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})),
@@ -839,7 +839,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// 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
@@ -856,7 +856,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
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
@@ -880,10 +880,10 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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!(
@@ -896,10 +896,10 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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!(
@@ -912,9 +912,9 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(class_like_declaration
modifier: (modifier #{kind})
modifier: {..mods}
modifier: {mods}
name: (identifier #{name})
base_type: {..bases})
base_type: {bases})
),
// Protocol declaration
rule!(
@@ -926,10 +926,10 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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!(
@@ -941,11 +941,11 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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).
@@ -956,9 +956,9 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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!(
@@ -967,15 +967,15 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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})
),
@@ -990,9 +990,9 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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
@@ -1009,7 +1009,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
type: _? @ty
(modifiers)* @mods)
=>
{..{
{{
ctx.property_name = Some(tree!((identifier #{name})));
ctx.property_type = ty;
ctx.outer_modifiers = mods;
@@ -1031,23 +1031,23 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(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
@@ -1056,58 +1056,58 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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
@@ -1116,24 +1116,24 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
(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)),