unified: Port operator rules to swift-syntax

Retarget operator handling to the swift-syntax AST. Because Swift's
grammar has no operator precedence, the parser front-end folds operator
chains into nested `infixOperatorExpr`s (see swift-syntax-rs), so a
single rule replaces the tree-sitter grammar's per-precedence binary
rules (additive, multiplicative, comparison, equality, conjunction,
disjunction, bitwise, range, nil-coalescing). The output is unchanged;
only the input matching differs:

- `binaryOperatorExpr` unwraps to the `infix_operator` leaf.
- A `binaryOperator`-based `infixOperatorExpr` becomes `binary_expr`, or
  `compound_assign_expr` when the operator's spelling is a compound
  assignment — merging the tree-sitter grammar's separate binary and
  compound-assignment rules (the operator kinds are structurally
  identical, distinguishable only by spelling).
- An `assignmentExpr`-based `infixOperatorExpr` becomes `assign_expr`.
- An unresolved chain stays a flat `sequenceExpr` ->
  `unresolved_operator_sequence`.
- `prefixOperatorExpr` -> prefix `unary_expr`; `tupleExpr` -> opaque
  `tuple_expr`; `codeBlock` -> `block`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Taus
2026-07-17 14:07:40 +00:00
parent ad2ce9315f
commit 8328fba68a

View File

@@ -121,6 +121,13 @@ fn member_chain(
)
}
/// Compound-assignment operator spellings (`+=`, `<<=`, ...). Used to tell a
/// compound assignment from an ordinary binary application, both of which
/// arrive as a `binaryOperator`-based `infixOperatorExpr`.
const COMPOUND_ASSIGN_OPS: &[&str] = &[
"+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "|=", "^=", "&+=", "&-=", "&*=",
];
fn translation_rules() -> Vec<Rule<SwiftContext>> {
vec![
// ---- Top-level ----
@@ -159,55 +166,56 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
// `name_expr` too.
rule!((discardAssignmentExpr wildcard: @@w) => (name_expr identifier: (identifier #{w}))),
// ---- Operators ----
// All binary operators share the lhs/op/rhs shape.
rule!((additive_expression lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
rule!((multiplicative_expression lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
rule!((comparison_expression lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
rule!((equality_expression lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
rule!((conjunction_expression lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
rule!((disjunction_expression lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
rule!((infix_expression lhs: @l op: @op rhs: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
// Range expression `a..<b` / `a...b`
rule!((range_expression start: @l op: @op end: @r) => (binary_expr left: {l} operator: (infix_operator #{op}) right: {r})),
// Open-ended ranges `a...` / `...b`
rule!((open_end_range_expression start: @l) => (unary_expr operator: (postfix_operator "...") operand: {l})),
rule!((open_start_range_expression end: @r) => (unary_expr operator: (prefix_operator "...") operand: {r})),
// Custom operator declaration: `[prefix|infix|postfix] operator OP [: PrecedenceGroup]`.
// The fixity keyword is an anonymous child of `operator_declaration`, so we
// dispatch on it with one rule per keyword.
// The parser front-end folds operator chains into nested
// `infixOperatorExpr`s by precedence (see swift-syntax-rs), so
// `1 + 2 * 3` arrives here already structured.
//
// A `binaryOperatorExpr` wraps the operator token; unwrap it to the
// operator leaf. Used by `infixOperatorExpr` (folded) and `sequenceExpr`
// (unresolved).
rule!((binaryOperatorExpr operator: @op) => (infix_operator #{op})),
// Compound assignment (`x += y`) vs. an ordinary binary application
// (`a + b`): both are `binaryOperator`-based `infixOperatorExpr`s,
// distinguishable only by the operator's spelling. The query engine
// can't match on token text, so a small Rust block reads the spelling
// and routes to `compound_assign_expr` or `binary_expr`. The operator
// is captured raw (`@@op`) to read its spelling.
rule!(
(operator_declaration "prefix" (referenceable_operator _ @op) (simple_identifier)? @prec)
(infixOperatorExpr leftOperand: @l operator: (binaryOperatorExpr) @@op rightOperand: @r)
=>
(operator_syntax_declaration name: (identifier #{op}) fixity: (fixity "prefix") precedence: {prec})
expr {
if COMPOUND_ASSIGN_OPS.contains(&ctx.source_text(op).as_str()) {
tree!((compound_assign_expr target: {l} operator: (infix_operator #{op}) value: {r}))
} else {
tree!((binary_expr left: {l} operator: (infix_operator #{op}) right: {r}))
}
}
),
// Plain assignment (`x = y`). In a folded chain the `=` is an
// `assignmentExpr` node (distinct from other operators), matched by kind.
rule!(
(operator_declaration "postfix" (referenceable_operator _ @op) (simple_identifier)? @prec)
(infixOperatorExpr leftOperand: @l operator: (assignmentExpr) rightOperand: @r)
=>
(operator_syntax_declaration name: (identifier #{op}) fixity: (fixity "postfix") precedence: {prec})
(assign_expr target: {l} value: {r})
),
rule!(
(operator_declaration "infix" (referenceable_operator _ @op) (simple_identifier)? @prec)
=>
(operator_syntax_declaration
name: (identifier #{op})
fixity: (fixity "infix")
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})),
// Leading-dot member shorthand (e.g. `.some`, `.foo`) means member access
// on a contextually inferred type.
rule!((prefix_expression operation: "." target: @member) => (member_access_expr base: (inferred_type_expr) member: (identifier #{member}))),
// Prefix unary operators
rule!((prefix_expression operation: @op target: @operand) => (unary_expr operator: (prefix_operator #{op}) operand: {operand})),
// Postfix unary operators
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})),
// Blocks contain statement* directly.
rule!((block statement: _+ @stmts) => (block stmt: {stmts})),
rule!((block) => (block)),
// Escape hatch: an operator chain the front-end could not resolve
// (because it uses an operator of unknown precedence, e.g. imported from
// another module) stays a flat `sequenceExpr`. Preserve it as an
// `unresolved_operator_sequence` whose elements alternate operands and
// infix operators, rather than guessing a structure.
rule!((sequenceExpr elements: _* @els) => (unresolved_operator_sequence element: {els})),
// Prefix unary operators (`!a`, `-x`).
rule!((prefixOperatorExpr operator: @op expression: @operand) => (unary_expr operator: (prefix_operator #{op}) operand: {operand})),
// A `tupleExpr` is a tuple literal (`(a, b)`) or a parenthesised
// expression (`(x)`). For now it is kept as an opaque `tuple_expr` leaf
// (its source text); its elements are not descended into.
//
// TODO: a parenthesised single-element `tupleExpr` is really a grouping
// expression and should be elided (unwrapped to its inner expression)
// rather than modelled as a tuple.
rule!((tupleExpr) => (tuple_expr)),
// A code block contains its statements directly.
rule!((codeBlock statements: _* @stmts) => (block stmt: {stmts})),
// ---- Variables ----
// property_binding rules — these produce variable_declaration and/or accessor_declaration
// nodes for individual declarators. The outer property_declaration rule splices these out
@@ -441,22 +449,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
result
}
),
// Plain assignment: `x = expr`
rule!(
(assignment operator: "=" target: (directly_assignable_expression expr: @target) result: @value)
=>
(assign_expr target: {target} value: {value})
),
// Compound assignment: `x += expr` etc.
rule!(
(assignment operator: @op target: (directly_assignable_expression expr: @target) result: @value)
=>
(compound_assign_expr target: {target} operator: (infix_operator #{op}) value: {value})
),
// Unwrap `type` wrapper node
rule!((type name: @inner) => type_expr { inner }),
// `directly_assignable_expression` is just a wrapper; unwrap it
rule!((directly_assignable_expression expr: @inner) => expr { inner }),
// Pattern with bound_identifier → name_pattern.
rule!(
(pattern bound_identifier: @name)