From d17fd2d964dbcc97f6f9583cdf063bee30cd0ba2 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 18 Jun 2026 13:42:23 +0200 Subject: [PATCH] unified/swift: add variable/property/accessor and enum mappings --- .../extractor/src/languages/swift/swift.rs | 203 ++++++++++++++++++ .../extractor/tests/corpus/swift/closures.txt | 32 ++- .../tests/corpus/swift/collections.txt | 66 +++++- .../tests/corpus/swift/control-flow.txt | 8 +- .../corpus/swift/optionals-and-errors.txt | 60 +++++- .../tests/corpus/swift/variables.txt | 72 ++++++- 6 files changed, 414 insertions(+), 27 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index c9f01a9e7ee..5bed312ed6d 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -83,6 +83,209 @@ fn translation_rules() -> Vec { // Blocks contain statement* directly. rule!((block statement: _+ @stmts) => (block stmt: {..stmts})), rule!((block) => (block)), + // ---- Variables ---- + // property_binding rules — these produce variable_declaration and/or accessor_declaration + // nodes for individual declarators. The outer property_declaration rule splices these out + // and attaches binding/modifiers from the parent. + + // Computed property with explicit accessors (get/set/modify) → + // a sequence of accessor_declaration nodes, each with the property name + // attached. Subsequent accessors will be tagged chained_declaration by + // the outer property_declaration rule. + rule!( + (property_binding + name: @pattern + type: _? @ty + computed_value: (computed_property accessor: _+ @accessors)) + => + {..{ + let name_text = __yeast_ctx.ast.source_text(pattern.into()); + let ty_ids: Vec = ty.iter().map(|&t| t.into()).collect(); + let acc_ids: Vec = accessors.iter().map(|&a| a.into()).collect(); + for &acc_id in &acc_ids { + let ident = __yeast_ctx.literal("identifier", &name_text); + __yeast_ctx.prepend_field(acc_id, "name", ident); + for &ty_id in ty_ids.iter().rev() { + __yeast_ctx.prepend_field(acc_id, "type", ty_id); + } + } + acc_ids + }} + ), + // Computed property: shorthand getter (no explicit get/set, just statements) → + // a single accessor_declaration with kind "get". + rule!( + (property_binding + name: (pattern bound_identifier: @name) + type: _? @ty + computed_value: (computed_property statement: _* @body)) + => + (accessor_declaration + name: (identifier #{name}) + type: {..ty} + accessor_kind: (accessor_kind "get") + body: (block stmt: {..body})) + ), + // Stored property with willSet/didSet observers (initializer optional) → + // variable_declaration followed by one accessor_declaration per observer, + // each carrying the property name. Subsequent items are tagged + // chained_declaration by the outer property_declaration rule. + rule!( + (property_binding + name: (pattern bound_identifier: @name) + type: _? @ty + value: _? @val + observers: (willset_didset_block willset: _? @ws didset: _? @ds)) + => + {..{ + let name_text = __yeast_ctx.ast.source_text(name.into()); + let val_ids: Vec = val.iter().map(|&v| v.into()).collect(); + let ty_ids: Vec = ty.iter().map(|&t| t.into()).collect(); + let mut obs_ids: Vec = Vec::new(); + obs_ids.extend(ws.iter().map(|&o| { let id: usize = o.into(); id })); + obs_ids.extend(ds.iter().map(|&o| { let id: usize = o.into(); id })); + let ident_for_var = __yeast_ctx.literal("identifier", &name_text); + let pat = __yeast_ctx.node("name_pattern", vec![("identifier", vec![ident_for_var])]); + let mut var_fields: Vec<(&str, Vec)> = vec![("pattern", vec![pat])]; + if !ty_ids.is_empty() { + var_fields.push(("type", ty_ids)); + } + if !val_ids.is_empty() { + var_fields.push(("value", val_ids)); + } + let var_id = __yeast_ctx.node("variable_declaration", var_fields); + let mut result = vec![var_id]; + for obs_id in obs_ids { + let ident = __yeast_ctx.literal("identifier", &name_text); + __yeast_ctx.prepend_field(obs_id, "name", ident); + result.push(obs_id); + } + result + }} + ), + // property_binding with any pattern name (identifier or destructuring) + rule!( + (property_binding + name: @pattern + type: _? @ty + value: _? @val) + => + (variable_declaration + pattern: {pattern} + type: {..ty} + value: {..val}) + ), + // property_declaration: splice declarators (each may translate to multiple nodes — + // variable_declaration and/or accessor_declaration), and attach the binding modifier + // (let/var) and any outer modifiers to each. All children after the first additionally + // get a synthetic chained_declaration modifier so the grouping can be recovered. + rule!( + (property_declaration + binding: (value_binding_pattern mutability: @binding_kind) + declarator: _* @decls + (modifiers)* @mods) + => + {..{ + let binding_text = __yeast_ctx.ast.source_text(binding_kind.into()); + let mod_ids: Vec = mods.iter().map(|&m| m.into()).collect(); + let decl_ids: Vec = decls.iter().map(|&d| d.into()).collect(); + for (i, &decl_id) in decl_ids.iter().enumerate() { + if i > 0 { + let chained = __yeast_ctx.literal("modifier", "chained_declaration"); + __yeast_ctx.prepend_field(decl_id, "modifier", chained); + } + for &mod_id in mod_ids.iter().rev() { + __yeast_ctx.prepend_field(decl_id, "modifier", mod_id); + } + let binding_mod = __yeast_ctx.literal("modifier", &binding_text); + __yeast_ctx.prepend_field(decl_id, "modifier", binding_mod); + } + decl_ids + }} + ), + // ---- Enums ---- + // enum_type_parameter → parameter (with optional name as pattern). + rule!( + (enum_type_parameter name: @name type: @ty) + => + (parameter + pattern: (name_pattern identifier: (identifier #{name})) + type: {ty}) + ), + rule!( + (enum_type_parameter type: @ty) + => + (parameter type: {ty}) + ), + // enum_case_entry with associated values → class_like_declaration containing + // a constructor whose parameters are the data parameters. + rule!( + (enum_case_entry + name: @name + data_contents: (enum_type_parameters parameter: _* @params)) + => + (class_like_declaration + modifier: (modifier "enum_case") + name: (identifier #{name}) + 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: (modifier "enum_case") + pattern: (name_pattern identifier: (identifier #{name})) + value: {val}) + ), + // enum_case_entry without associated values → variable_declaration tagged enum_case. + rule!( + (enum_case_entry name: @name) + => + (variable_declaration + modifier: (modifier "enum_case") + pattern: (name_pattern identifier: (identifier #{name}))) + ), + // enum_entry: flatten case entries; attach outer modifiers to each, and + // chained_declaration on every entry after the first. + rule!( + (enum_entry case: _+ @cases (modifiers)* @mods) + => + {..{ + let mod_ids: Vec = mods.iter().map(|&m| m.into()).collect(); + let case_ids: Vec = cases.iter().map(|&c| c.into()).collect(); + for (i, &case_id) in case_ids.iter().enumerate() { + if i > 0 { + let chained = __yeast_ctx.literal("modifier", "chained_declaration"); + __yeast_ctx.prepend_field(case_id, "modifier", chained); + } + for &mod_id in mod_ids.iter().rev() { + __yeast_ctx.prepend_field(case_id, "modifier", mod_id); + } + } + case_ids + }} + ), + // 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) => {inner}), + // `directly_assignable_expression` is just a wrapper; unwrap it + rule!((directly_assignable_expression expr: @inner) => {inner}), + // Pattern with bound_identifier → name_pattern + rule!((pattern bound_identifier: @name) => (name_pattern identifier: (identifier #{name}))), + // Tuple pattern (destructuring) + rule!((pattern (pattern)* @elems) => (tuple_pattern element: {..elems})), // ---- Fallbacks ---- rule!( (_) diff --git a/unified/extractor/tests/corpus/swift/closures.txt b/unified/extractor/tests/corpus/swift/closures.txt index 32004a0973d..3bfe46ff411 100644 --- a/unified/extractor/tests/corpus/swift/closures.txt +++ b/unified/extractor/tests/corpus/swift/closures.txt @@ -51,7 +51,13 @@ source_file top_level body: block - stmt: unsupported_node "let f = { (x: Int) -> Int in x * 2 }" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "f" + value: unsupported_node "{ (x: Int) -> Int in x * 2 }" === Closure with shorthand parameters @@ -85,7 +91,13 @@ source_file top_level body: block - stmt: unsupported_node "let f = { $0 + $1 }" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "f" + value: unsupported_node "{ $0 + $1 }" === Trailing closure @@ -170,7 +182,13 @@ source_file top_level body: block - stmt: unsupported_node "let f = { [weak self] in self?.doThing() }" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "f" + value: unsupported_node "{ [weak self] in self?.doThing() }" === Multi-statement closure @@ -245,4 +263,10 @@ source_file top_level body: block - stmt: unsupported_node "let f = { (x: Int) -> Int in\n let y = x + 1\n return y * 2\n}" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "f" + value: unsupported_node "{ (x: Int) -> Int in\n let y = x + 1\n return y * 2\n}" diff --git a/unified/extractor/tests/corpus/swift/collections.txt b/unified/extractor/tests/corpus/swift/collections.txt index 69437de0111..7a7544604d4 100644 --- a/unified/extractor/tests/corpus/swift/collections.txt +++ b/unified/extractor/tests/corpus/swift/collections.txt @@ -29,7 +29,13 @@ source_file top_level body: block - stmt: unsupported_node "let xs = [1, 2, 3]" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "xs" + value: unsupported_node "[1, 2, 3]" === Empty array literal with type @@ -71,7 +77,14 @@ source_file top_level body: block - stmt: unsupported_node "let xs: [Int] = []" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "xs" + type: unsupported_node ": [Int]" + value: unsupported_node "[]" === Dictionary literal @@ -111,7 +124,13 @@ source_file top_level body: block - stmt: unsupported_node "let d = [\"a\": 1, \"b\": 2]" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "d" + value: unsupported_node "[\"a\": 1, \"b\": 2]" === Set literal @@ -162,7 +181,14 @@ source_file top_level body: block - stmt: unsupported_node "let s: Set = [1, 2, 3]" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "s" + type: unsupported_node ": Set" + value: unsupported_node "[1, 2, 3]" === Tuple literal @@ -200,7 +226,13 @@ source_file top_level body: block - stmt: unsupported_node "let t = (1, \"two\", 3.0)" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "t" + value: tuple_expr "(1, \"two\", 3.0)" === Subscript access @@ -243,7 +275,13 @@ source_file top_level body: block - stmt: unsupported_node "let first = xs[0]" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "first" + value: unsupported_node "xs[0]" === Dictionary subscript @@ -286,7 +324,13 @@ source_file top_level body: block - stmt: unsupported_node "let v = d[\"key\"]" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "v" + value: unsupported_node "d[\"key\"]" === Tuple member access @@ -319,4 +363,10 @@ source_file top_level body: block - stmt: unsupported_node "let n = t.0" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "n" + value: unsupported_node "t.0" diff --git a/unified/extractor/tests/corpus/swift/control-flow.txt b/unified/extractor/tests/corpus/swift/control-flow.txt index f621a2ca665..5384df4abe4 100644 --- a/unified/extractor/tests/corpus/swift/control-flow.txt +++ b/unified/extractor/tests/corpus/swift/control-flow.txt @@ -288,7 +288,13 @@ source_file top_level body: block - stmt: unsupported_node "let y = x > 0 ? 1 : -1" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "y" + value: unsupported_node "x > 0 ? 1 : -1" === Switch statement diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors.txt b/unified/extractor/tests/corpus/swift/optionals-and-errors.txt index e4d0e30f688..2a921d9a302 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors.txt +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors.txt @@ -35,7 +35,14 @@ source_file top_level body: block - stmt: unsupported_node "let x: Int? = nil" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "x" + type: unsupported_node ": Int?" + value: builtin_expr "nil" === Optional chaining @@ -77,7 +84,13 @@ source_file top_level body: block - stmt: unsupported_node "let n = obj?.foo?.bar" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "n" + value: unsupported_node "obj?.foo?.bar" === Force unwrap @@ -108,7 +121,18 @@ source_file top_level body: block - stmt: unsupported_node "let n = opt!" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "n" + value: + unary_expr + operand: + name_expr + identifier: identifier "opt" + operator: postfix_operator "!" === Nil-coalescing @@ -139,7 +163,19 @@ source_file top_level body: block - stmt: unsupported_node "let n = opt ?? 0" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "n" + value: + binary_expr + operator: infix_operator "??" + left: + name_expr + identifier: identifier "opt" + right: int_literal "0" === Throwing function @@ -265,7 +301,13 @@ source_file top_level body: block - stmt: unsupported_node "let result = try? foo()" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "result" + value: unsupported_node "try? foo()" === Try! expression @@ -303,4 +345,10 @@ source_file top_level body: block - stmt: unsupported_node "let result = try! foo()" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "result" + value: unsupported_node "try! foo()" diff --git a/unified/extractor/tests/corpus/swift/variables.txt b/unified/extractor/tests/corpus/swift/variables.txt index ea3b898f98b..d7ff7a110a1 100644 --- a/unified/extractor/tests/corpus/swift/variables.txt +++ b/unified/extractor/tests/corpus/swift/variables.txt @@ -24,7 +24,13 @@ source_file top_level body: block - stmt: unsupported_node "let x = 1" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "x" + value: int_literal "1" === Var binding @@ -52,7 +58,13 @@ source_file top_level body: block - stmt: unsupported_node "var x = 1" + stmt: + variable_declaration + modifier: modifier "var" + pattern: + name_pattern + identifier: identifier "x" + value: int_literal "1" === Let with type annotation @@ -89,7 +101,14 @@ source_file top_level body: block - stmt: unsupported_node "let x: Int = 1" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "x" + type: unsupported_node ": Int" + value: int_literal "1" === Var without initialiser @@ -125,7 +144,13 @@ source_file top_level body: block - stmt: unsupported_node "var x: Int" + stmt: + variable_declaration + modifier: modifier "var" + pattern: + name_pattern + identifier: identifier "x" + type: unsupported_node ": Int" === Tuple destructuring binding @@ -163,7 +188,13 @@ source_file top_level body: block - stmt: unsupported_node "let (a, b) = pair" + stmt: + variable_declaration + modifier: modifier "let" + pattern: tuple_pattern "(a, b)" + value: + name_expr + identifier: identifier "pair" === Multiple bindings on one line @@ -196,7 +227,21 @@ source_file top_level body: block - stmt: unsupported_node "let x = 1, y = 2" + stmt: + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "x" + value: int_literal "1" + variable_declaration + modifier: + modifier "let" + modifier "chained_declaration" + pattern: + name_pattern + identifier: identifier "y" + value: int_literal "2" === Assignment @@ -220,7 +265,12 @@ source_file top_level body: block - stmt: unsupported_node "x = 1" + stmt: + assign_expr + target: + name_expr + identifier: identifier "x" + value: int_literal "1" === Compound assignment @@ -244,4 +294,10 @@ source_file top_level body: block - stmt: unsupported_node "x += 1" + stmt: + compound_assign_expr + operator: infix_operator "+=" + target: + name_expr + identifier: identifier "x" + value: int_literal "1"