From 7a573c4619374ef0328237fcfd4b6ed366d1f730 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 16 Jul 2026 15:57:35 +0000 Subject: [PATCH 01/27] yeast: Order AST dump fields by schema-declared order The AST dump previously emitted named fields in field-id order, which made it dependant on registration order and so it could differ between front-ends. We now emit them in the order declared in the node-types YAML instead, so that the order is kept stable. --- shared/yeast-schema/src/node_types_yaml.rs | 35 ++++++++++++ shared/yeast-schema/src/schema.rs | 17 ++++++ shared/yeast/src/dump.rs | 42 +++++++++++++-- .../closures/closure-with-capture-list.output | 12 ++--- .../closure-with-explicit-parameters.output | 24 ++++----- .../closure-with-shorthand-parameters.output | 2 +- .../closures/multi-statement-closure.output | 26 ++++----- .../swift/closures/trailing-closure.output | 14 ++--- .../collections/dictionary-subscript.output | 6 +-- .../swift/collections/subscript-access.output | 6 +-- ...g-modifier-does-not-leak-to-sibling.output | 22 ++++---- .../swift/control-flow/guard-let.output | 10 ++-- ...t-with-shadowing-in-condition-value.output | 8 +-- .../control-flow/if-else-if-chain.output | 40 +++++++------- .../corpus/swift/control-flow/if-else.output | 28 +++++----- .../if-let-optional-binding.output | 16 +++--- .../swift/control-flow/if-statement.output | 8 +-- .../control-flow/switch-statement.output | 38 ++++++------- .../switch-with-binding-pattern.output | 52 +++++++++--------- ...with-labeled-case-pattern-arguments.output | 54 +++++++++---------- .../control-flow/ternary-expression.output | 4 +- .../additive-expression-is-desugared.output | 2 +- ...er-additive-expression-is-desugared.output | 2 +- ...with-deeply-nested-path-three-parts.output | 2 +- .../import-with-dotted-path-two-parts.output | 2 +- .../scoped-import-uses-name-pattern.output | 6 +-- .../simple-import-with-single-name.output | 2 +- ...nction-call-with-labelled-arguments.output | 6 +-- .../swift/functions/function-call.output | 6 +-- ...nction-with-default-parameter-value.output | 20 +++---- .../function-with-named-parameters.output | 20 +++---- .../function-with-no-parameters.output | 8 +-- ...ion-with-parameters-and-return-type.output | 26 ++++----- .../swift/functions/generic-function.output | 14 ++--- .../leading-dot-expression-call.output | 6 +-- .../corpus/swift/functions/method-call.output | 6 +-- .../swift/functions/variadic-function.output | 32 +++++------ .../swift/loops/break-and-continue.output | 22 ++++---- .../loops/for-in-over-array-literal.output | 24 ++++----- .../swift/loops/for-in-over-range.output | 22 ++++---- .../loops/for-in-with-where-clause.output | 32 +++++------ .../swift/loops/repeat-while-loop.output | 4 +- .../corpus/swift/loops/while-loop.output | 16 +++--- .../corpus/swift/operators/addition.output | 2 +- .../corpus/swift/operators/comparison.output | 2 +- .../corpus/swift/operators/division.output | 2 +- .../corpus/swift/operators/equality.output | 2 +- .../corpus/swift/operators/logical-and.output | 2 +- .../corpus/swift/operators/logical-or.output | 2 +- .../swift/operators/multiplication.output | 2 +- ...cedence-addition-and-multiplication.output | 4 +- .../operators/parenthesised-expression.output | 2 +- .../swift/operators/range-operator.output | 2 +- .../corpus/swift/operators/subtraction.output | 2 +- .../optionals-and-errors/do-catch.output | 6 +-- .../nil-coalescing.output | 2 +- .../throwing-function.output | 8 +-- ...er-does-not-leak-into-accessor-body.output | 28 +++++----- .../swift/types/class-with-initializer.output | 4 +- .../swift/types/class-with-method.output | 8 +-- .../types/class-with-stored-properties.output | 4 +- .../swift/types/computed-property.output | 18 +++---- .../types/enum-with-associated-values.output | 28 +++++----- .../corpus/swift/types/enum-with-cases.output | 4 +- ...separated-cases-chained-declaration.output | 4 +- .../tests/corpus/swift/types/extension.output | 14 ++--- .../property-with-getter-and-setter.output | 26 ++++----- .../swift/types/protocol-declaration.output | 6 +-- ...nd-read-write-property-requirements.output | 10 ++-- .../tests/corpus/swift/types/struct.output | 4 +- ...fier-does-not-leak-into-initializer.output | 12 ++--- .../variables/compound-assignment.output | 2 +- ...y-with-willset-and-didset-observers.output | 30 +++++------ 73 files changed, 536 insertions(+), 450 deletions(-) diff --git a/shared/yeast-schema/src/node_types_yaml.rs b/shared/yeast-schema/src/node_types_yaml.rs index 5f6a3906f7cb..97052f9a835b 100644 --- a/shared/yeast-schema/src/node_types_yaml.rs +++ b/shared/yeast-schema/src/node_types_yaml.rs @@ -252,6 +252,41 @@ pub fn extend_schema_from_yaml( let yaml: YamlNodeTypes = serde_yaml::from_str(yaml_input).map_err(|e| format!("Failed to parse YAML: {e}"))?; apply_yaml_to_schema(&yaml, schema); + // The typed `YamlNodeTypes` stores each node's fields in a `BTreeMap` + // (alphabetical), losing the authored order. Re-parse as an ordered value to + // record the declared field order for presentation (see the AST dump). + record_field_order(schema, yaml_input)?; + Ok(()) +} + +/// Record each node kind's declared (named) field order from the source YAML, +/// which `serde`'s `BTreeMap`-based deserialization does not preserve. +/// `serde_yaml::Value` mappings keep insertion (source) order. +fn record_field_order(schema: &mut crate::schema::Schema, yaml_input: &str) -> Result<(), String> { + let value: serde_yaml::Value = serde_yaml::from_str(yaml_input) + .map_err(|e| format!("Failed to parse YAML for field order: {e}"))?; + let Some(named) = value.get("named").and_then(|v| v.as_mapping()) else { + return Ok(()); + }; + for (node_name, fields) in named { + let Some(node_name) = node_name.as_str() else { + continue; + }; + let Some(fields) = fields.as_mapping() else { + continue; // node with no fields (null) + }; + let mut order = Vec::new(); + for (raw_field_name, _) in fields { + let Some(raw) = raw_field_name.as_str() else { + continue; + }; + // Skip the unnamed/`child` slot; the dump handles it separately. + if let Some(name) = parse_field_name(raw).name { + order.push(schema.register_field(&name)); + } + } + schema.set_field_order(node_name, order); + } Ok(()) } diff --git a/shared/yeast-schema/src/schema.rs b/shared/yeast-schema/src/schema.rs index 9c438d7c22e8..0675d8913422 100644 --- a/shared/yeast-schema/src/schema.rs +++ b/shared/yeast-schema/src/schema.rs @@ -44,6 +44,11 @@ pub struct Schema { field_types: BTreeMap<(String, FieldId), Vec>, field_cardinalities: BTreeMap<(String, FieldId), FieldCardinality>, supertypes: BTreeMap>, + /// Per-node-kind declared field order (named fields only), as written in + /// the source node-types YAML. Field ids are not a stable ordering key + /// across front-ends, so this preserves the authored order for + /// presentation (see the AST dump). + field_order: BTreeMap>, } impl Default for Schema { @@ -65,6 +70,7 @@ impl Schema { field_types: BTreeMap::new(), field_cardinalities: BTreeMap::new(), supertypes: BTreeMap::new(), + field_order: BTreeMap::new(), } } @@ -269,6 +275,17 @@ impl Schema { .get(&(parent_kind.to_string(), field_id)) } + /// Record the declared (named) field order for a node kind, as authored in + /// the source node-types YAML. + pub fn set_field_order(&mut self, kind: &str, field_ids: Vec) { + self.field_order.insert(kind.to_string(), field_ids); + } + + /// The declared (named) field order for a node kind, if known. + pub fn field_order(&self, kind: &str) -> Option<&Vec> { + self.field_order.get(kind) + } + pub fn set_field_cardinality( &mut self, parent_kind: &str, diff --git a/shared/yeast/src/dump.rs b/shared/yeast/src/dump.rs index 34b614323600..f217f1798068 100644 --- a/shared/yeast/src/dump.rs +++ b/shared/yeast/src/dump.rs @@ -223,11 +223,45 @@ fn dump_node( writeln!(out).unwrap(); - // Named fields first - for (&field_id, children) in &node.fields { - if field_id == CHILD_FIELD { - continue; // Handle unnamed children last + // Named fields first, in the schema's declared order when available + // (front-end-independent), else in field-id order. Any present fields not + // covered by the declared order are appended in field-id order. + // + // The declared order lives in the validation schema, keyed by *its* field + // ids; the AST being dumped may key the same field names under different + // ids. So map the declared order through field NAMES into this AST's own id + // space, keeping the two schemas independent (they share names, not ids). + let named_field_ids: Vec = { + let present: Vec = node + .fields + .keys() + .copied() + .filter(|&f| f != CHILD_FIELD) + .collect(); + match type_check.and_then(|(schema, _, _)| { + schema + .field_order(node.kind_name()) + .map(|order| (schema, order)) + }) { + Some((schema, order)) => { + let mut result: Vec = order + .iter() + .filter_map(|&f| schema.field_name_for_id(f)) + .filter_map(|name| ast.field_id_for_name(name)) + .filter(|&f| f != CHILD_FIELD && node.fields.contains_key(&f)) + .collect(); + for &f in &present { + if !result.contains(&f) { + result.push(f); + } + } + result + } + None => present, } + }; + for field_id in named_field_ids { + let children = &node.fields[&field_id]; let field_name = ast.field_name_for_id(field_id).unwrap_or("?"); let child_type_check = type_check.map(|(schema, _, _)| { let expected = diff --git a/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output b/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output index 8f28322b4930..b4760e35591c 100644 --- a/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output +++ b/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output @@ -51,6 +51,12 @@ top_level identifier: identifier "f" value: function_expr + capture_declaration: + variable_declaration + modifier: modifier "weak" + pattern: + name_pattern + identifier: identifier "self" body: block stmt: @@ -61,9 +67,3 @@ top_level name_expr identifier: identifier "self" member: identifier "doThing" - capture_declaration: - variable_declaration - modifier: modifier "weak" - pattern: - name_pattern - identifier: identifier "self" diff --git a/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output b/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output index 95d638118d84..bb6b878b8bb9 100644 --- a/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output +++ b/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output @@ -55,23 +55,23 @@ top_level identifier: identifier "f" value: function_expr - body: - block - stmt: - binary_expr - operator: infix_operator "*" - left: - name_expr - identifier: identifier "x" - right: int_literal "2" parameter: parameter - pattern: - name_pattern - identifier: identifier "x" type: named_type_expr name: identifier "Int" + pattern: + name_pattern + identifier: identifier "x" return_type: named_type_expr name: identifier "Int" + body: + block + stmt: + binary_expr + left: + name_expr + identifier: identifier "x" + operator: infix_operator "*" + right: int_literal "2" diff --git a/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output b/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output index bd286c385799..67cdf3df63f2 100644 --- a/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output +++ b/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output @@ -38,10 +38,10 @@ top_level block stmt: binary_expr - operator: infix_operator "+" left: name_expr identifier: identifier "$0" + operator: infix_operator "+" right: name_expr identifier: identifier "$1" diff --git a/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output b/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output index 6c9a403f19c1..0d07ea6f7bbb 100644 --- a/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output +++ b/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output @@ -75,6 +75,17 @@ top_level identifier: identifier "f" value: function_expr + parameter: + parameter + type: + named_type_expr + name: identifier "Int" + pattern: + name_pattern + identifier: identifier "x" + return_type: + named_type_expr + name: identifier "Int" body: block stmt: @@ -85,27 +96,16 @@ top_level identifier: identifier "y" value: binary_expr - operator: infix_operator "+" left: name_expr identifier: identifier "x" + operator: infix_operator "+" right: int_literal "1" return_expr value: binary_expr - operator: infix_operator "*" left: name_expr identifier: identifier "y" + operator: infix_operator "*" right: int_literal "2" - parameter: - parameter - pattern: - name_pattern - identifier: identifier "x" - type: - named_type_expr - name: identifier "Int" - return_type: - named_type_expr - name: identifier "Int" diff --git a/unified/extractor/tests/corpus/swift/closures/trailing-closure.output b/unified/extractor/tests/corpus/swift/closures/trailing-closure.output index 56b8bf9a7c20..ef8d6bd21c6a 100644 --- a/unified/extractor/tests/corpus/swift/closures/trailing-closure.output +++ b/unified/extractor/tests/corpus/swift/closures/trailing-closure.output @@ -28,6 +28,12 @@ top_level block stmt: call_expr + callee: + member_access_expr + base: + name_expr + identifier: identifier "xs" + member: identifier "map" argument: argument value: @@ -36,14 +42,8 @@ top_level block stmt: binary_expr - operator: infix_operator "*" left: name_expr identifier: identifier "$0" + operator: infix_operator "*" right: int_literal "2" - callee: - member_access_expr - base: - name_expr - identifier: identifier "xs" - member: identifier "map" diff --git a/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output b/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output index 59afc51867a2..c30ab9326bb6 100644 --- a/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output +++ b/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output @@ -43,9 +43,9 @@ top_level identifier: identifier "v" value: call_expr - argument: - argument - value: string_literal "\"key\"" callee: name_expr identifier: identifier "d" + argument: + argument + value: string_literal "\"key\"" diff --git a/unified/extractor/tests/corpus/swift/collections/subscript-access.output b/unified/extractor/tests/corpus/swift/collections/subscript-access.output index 481a3e95f774..681afca891a7 100644 --- a/unified/extractor/tests/corpus/swift/collections/subscript-access.output +++ b/unified/extractor/tests/corpus/swift/collections/subscript-access.output @@ -43,9 +43,9 @@ top_level identifier: identifier "first" value: call_expr - argument: - argument - value: int_literal "0" callee: name_expr identifier: identifier "xs" + argument: + argument + value: int_literal "0" diff --git a/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output b/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output index 3919b875b96a..9c4088845f14 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output +++ b/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output @@ -60,27 +60,27 @@ top_level identifier: identifier "x" value: int_literal "1" switch_expr + value: + name_expr + identifier: identifier "y" case: switch_case + pattern: + expr_equality_pattern + expr: + name_expr + identifier: identifier "someConstant" body: block stmt: call_expr - argument: - argument - value: string_literal "\"matched\"" callee: name_expr identifier: identifier "print" - pattern: - expr_equality_pattern - expr: - name_expr - identifier: identifier "someConstant" + argument: + argument + value: string_literal "\"matched\"" switch_case body: block stmt: break_expr "break" - value: - name_expr - identifier: identifier "y" diff --git a/unified/extractor/tests/corpus/swift/control-flow/guard-let.output b/unified/extractor/tests/corpus/swift/control-flow/guard-let.output index a21962341217..2e90e9820640 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/guard-let.output +++ b/unified/extractor/tests/corpus/swift/control-flow/guard-let.output @@ -33,17 +33,17 @@ top_level pattern_guard_expr pattern: constructor_pattern - element: - pattern_element - pattern: - name_pattern - identifier: identifier "value" constructor: member_access_expr base: named_type_expr name: identifier "Optional" member: identifier "some" + element: + pattern_element + pattern: + name_pattern + identifier: identifier "value" value: name_expr identifier: identifier "optional" diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output b/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output index 6a53c87d21a2..61ca65811e97 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output @@ -53,20 +53,20 @@ top_level identifier: identifier "x" value: binary_expr - operator: infix_operator "+" left: name_expr identifier: identifier "x" + operator: infix_operator "+" right: int_literal "10" then: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "x" - callee: - name_expr - identifier: identifier "print" diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output b/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output index e8f413726466..268a2d1f97c9 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output @@ -73,47 +73,47 @@ top_level if_expr condition: binary_expr - operator: infix_operator ">" left: name_expr identifier: identifier "x" + operator: infix_operator ">" right: int_literal "0" + then: + block + stmt: + call_expr + callee: + name_expr + identifier: identifier "print" + argument: + argument + value: int_literal "1" else: if_expr condition: binary_expr - operator: infix_operator "<" left: name_expr identifier: identifier "x" + operator: infix_operator "<" right: int_literal "0" - else: + then: block stmt: call_expr - argument: - argument - value: int_literal "3" callee: name_expr identifier: identifier "print" - then: - block - stmt: - call_expr argument: argument value: int_literal "2" + else: + block + stmt: + call_expr callee: name_expr identifier: identifier "print" - then: - block - stmt: - call_expr - argument: - argument - value: int_literal "1" - callee: - name_expr - identifier: identifier "print" + argument: + argument + value: int_literal "3" diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-else.output b/unified/extractor/tests/corpus/swift/control-flow/if-else.output index 469861214676..e891cb4e2f6c 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-else.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-else.output @@ -53,35 +53,35 @@ top_level if_expr condition: binary_expr - operator: infix_operator ">" left: name_expr identifier: identifier "x" + operator: infix_operator ">" right: int_literal "0" - else: + then: block stmt: call_expr - argument: - argument - value: - unary_expr - operand: - name_expr - identifier: identifier "x" - operator: prefix_operator "-" callee: name_expr identifier: identifier "print" - then: - block - stmt: - call_expr argument: argument value: name_expr identifier: identifier "x" + else: + block + stmt: + call_expr callee: name_expr identifier: identifier "print" + argument: + argument + value: + unary_expr + operand: + name_expr + identifier: identifier "x" + operator: prefix_operator "-" diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output b/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output index f6b605a7461c..0436a559236b 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output @@ -42,17 +42,17 @@ top_level pattern_guard_expr pattern: constructor_pattern - element: - pattern_element - pattern: - name_pattern - identifier: identifier "value" constructor: member_access_expr base: named_type_expr name: identifier "Optional" member: identifier "some" + element: + pattern_element + pattern: + name_pattern + identifier: identifier "value" value: name_expr identifier: identifier "optional" @@ -60,11 +60,11 @@ top_level block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "value" - callee: - name_expr - identifier: identifier "print" diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-statement.output b/unified/extractor/tests/corpus/swift/control-flow/if-statement.output index 2c29ab1dc69b..6e89490b0128 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-statement.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-statement.output @@ -36,20 +36,20 @@ top_level if_expr condition: binary_expr - operator: infix_operator ">" left: name_expr identifier: identifier "x" + operator: infix_operator ">" right: int_literal "0" then: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "x" - callee: - name_expr - identifier: identifier "print" diff --git a/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output b/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output index bb90cb60fc5b..e016a650787f 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output +++ b/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output @@ -76,32 +76,25 @@ top_level block stmt: switch_expr + value: + name_expr + identifier: identifier "x" case: switch_case - body: - block - stmt: - call_expr - argument: - argument - value: string_literal "\"one\"" - callee: - name_expr - identifier: identifier "print" pattern: expr_equality_pattern expr: int_literal "1" - switch_case body: block stmt: call_expr - argument: - argument - value: string_literal "\"two or three\"" callee: name_expr identifier: identifier "print" + argument: + argument + value: string_literal "\"one\"" + switch_case pattern: or_pattern pattern: @@ -109,17 +102,24 @@ top_level expr: int_literal "2" expr_equality_pattern expr: int_literal "3" - switch_case body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument - value: string_literal "\"other\"" + value: string_literal "\"two or three\"" + switch_case + body: + block + stmt: + call_expr callee: name_expr identifier: identifier "print" - value: - name_expr - identifier: identifier "x" + argument: + argument + value: string_literal "\"other\"" diff --git a/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output b/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output index 4d98620fe8f4..b995854d5999 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output +++ b/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output @@ -86,55 +86,55 @@ top_level block stmt: switch_expr + value: + name_expr + identifier: identifier "shape" case: switch_case + pattern: + constructor_pattern + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "circle" + element: + pattern_element + pattern: + name_pattern + identifier: identifier "r" body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "r" - callee: - name_expr - identifier: identifier "print" + switch_case pattern: constructor_pattern + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "square" element: pattern_element pattern: name_pattern - identifier: identifier "r" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "circle" - switch_case + identifier: identifier "s" body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "s" - callee: - name_expr - identifier: identifier "print" - pattern: - constructor_pattern - element: - pattern_element - pattern: - name_pattern - identifier: identifier "s" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "square" - value: - name_expr - identifier: identifier "shape" diff --git a/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output b/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output index aef36c168558..f8f6f2a0fe89 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output +++ b/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output @@ -88,45 +88,40 @@ top_level block stmt: switch_expr + value: + name_expr + identifier: identifier "x" case: switch_case - body: - block - stmt: - call_expr - argument: - argument - value: string_literal "\"yes\"" - callee: - name_expr - identifier: identifier "print" pattern: constructor_pattern + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "implicit" element: pattern_element key: identifier "isAcknowledged" pattern: expr_equality_pattern expr: boolean_literal "false" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "implicit" - switch_case body: block stmt: call_expr - argument: - argument - value: - name_expr - identifier: identifier "rowId" callee: name_expr identifier: identifier "print" + argument: + argument + value: string_literal "\"yes\"" + switch_case pattern: constructor_pattern + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "thread" element: pattern_element key: identifier "threadRowId" @@ -135,10 +130,15 @@ top_level pattern: name_pattern identifier: identifier "rowId" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "thread" - value: - name_expr - identifier: identifier "x" + body: + block + stmt: + call_expr + callee: + name_expr + identifier: identifier "print" + argument: + argument + value: + name_expr + identifier: identifier "rowId" diff --git a/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output b/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output index 7f9da80d34ac..482bd2382a9e 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output +++ b/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output @@ -41,13 +41,13 @@ top_level if_expr condition: binary_expr - operator: infix_operator ">" left: name_expr identifier: identifier "x" + operator: infix_operator ">" right: int_literal "0" + then: int_literal "1" else: unary_expr operand: int_literal "1" operator: prefix_operator "-" - then: int_literal "1" diff --git a/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output b/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output index 07aa40618bd1..849fe74107b6 100644 --- a/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output +++ b/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output @@ -16,6 +16,6 @@ top_level block stmt: binary_expr - operator: infix_operator "+" left: int_literal "1" + operator: infix_operator "+" right: int_literal "2" diff --git a/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output b/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output index ff830fd4b894..88de56051393 100644 --- a/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output +++ b/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "+" left: name_expr identifier: identifier "foo" + operator: infix_operator "+" right: name_expr identifier: identifier "bar" diff --git a/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output b/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output index 4f312dabb151..3d31437a66ef 100644 --- a/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output +++ b/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output @@ -19,7 +19,6 @@ top_level block stmt: import_declaration - pattern: bulk_importing_pattern "import Foundation.Networking.URLSession" imported_expr: member_access_expr base: @@ -29,3 +28,4 @@ top_level identifier: identifier "Foundation" member: identifier "Networking" member: identifier "URLSession" + pattern: bulk_importing_pattern "import Foundation.Networking.URLSession" diff --git a/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output b/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output index efd2a6461243..f1c1dbcfb979 100644 --- a/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output +++ b/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output @@ -18,10 +18,10 @@ top_level block stmt: import_declaration - pattern: bulk_importing_pattern "import Foundation.Networking" imported_expr: member_access_expr base: name_expr identifier: identifier "Foundation" member: identifier "Networking" + pattern: bulk_importing_pattern "import Foundation.Networking" diff --git a/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output b/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output index fbf8e8100af7..79d1fa8bcb6d 100644 --- a/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output +++ b/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output @@ -20,12 +20,12 @@ top_level stmt: import_declaration modifier: modifier "struct" - pattern: - name_pattern - identifier: identifier "Date" imported_expr: member_access_expr base: name_expr identifier: identifier "Foundation" member: identifier "Date" + pattern: + name_pattern + identifier: identifier "Date" diff --git a/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output b/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output index 7a6be1c35e4c..8db7e15e3dc2 100644 --- a/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output +++ b/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output @@ -16,7 +16,7 @@ top_level block stmt: import_declaration - pattern: bulk_importing_pattern "import Foundation" imported_expr: name_expr identifier: identifier "Foundation" + pattern: bulk_importing_pattern "import Foundation" diff --git a/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output b/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output index ba0c002a4527..6c975e37be04 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output +++ b/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output @@ -26,10 +26,10 @@ top_level block stmt: call_expr + callee: + name_expr + identifier: identifier "greet" argument: argument name: identifier "person" value: string_literal "\"Bob\"" - callee: - name_expr - identifier: identifier "greet" diff --git a/unified/extractor/tests/corpus/swift/functions/function-call.output b/unified/extractor/tests/corpus/swift/functions/function-call.output index ed604730d33c..2e8e107b3a20 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-call.output +++ b/unified/extractor/tests/corpus/swift/functions/function-call.output @@ -23,11 +23,11 @@ top_level block stmt: call_expr + callee: + name_expr + identifier: identifier "foo" argument: argument value: int_literal "1" argument value: int_literal "2" - callee: - name_expr - identifier: identifier "foo" diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output b/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output index fdd737e1258d..2c55abad52d9 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output @@ -43,22 +43,22 @@ top_level block stmt: function_declaration + name: identifier "greet" + parameter: + parameter + pattern: + name_pattern + identifier: identifier "name" + default: string_literal "\"world\"" body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "name" - callee: - name_expr - identifier: identifier "print" - name: identifier "greet" - parameter: - parameter - default: string_literal "\"world\"" - pattern: - name_pattern - identifier: identifier "name" diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output b/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output index bfa68c645ea1..d9eb41963965 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output @@ -41,22 +41,22 @@ top_level block stmt: function_declaration + name: identifier "greet" + parameter: + parameter + external_name: identifier "person" + pattern: + name_pattern + identifier: identifier "name" body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "name" - callee: - name_expr - identifier: identifier "print" - name: identifier "greet" - parameter: - parameter - external_name: identifier "person" - pattern: - name_pattern - identifier: identifier "name" diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output b/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output index b5cdfd73d48f..b59ed702f296 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output @@ -30,14 +30,14 @@ top_level block stmt: function_declaration + name: identifier "greet" body: block stmt: call_expr - argument: - argument - value: string_literal "\"hello\"" callee: name_expr identifier: identifier "print" - name: identifier "greet" + argument: + argument + value: string_literal "\"hello\"" diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output b/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output index 6544f4313cd7..eca072fed022 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output @@ -58,19 +58,6 @@ top_level block stmt: function_declaration - body: - block - stmt: - return_expr - value: - binary_expr - operator: infix_operator "+" - left: - name_expr - identifier: identifier "a" - right: - name_expr - identifier: identifier "b" name: identifier "add" parameter: parameter @@ -86,3 +73,16 @@ top_level return_type: named_type_expr name: identifier "Int" + body: + block + stmt: + return_expr + value: + binary_expr + left: + name_expr + identifier: identifier "a" + operator: infix_operator "+" + right: + name_expr + identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/functions/generic-function.output b/unified/extractor/tests/corpus/swift/functions/generic-function.output index f42367a8fca2..5fb2d4b03899 100644 --- a/unified/extractor/tests/corpus/swift/functions/generic-function.output +++ b/unified/extractor/tests/corpus/swift/functions/generic-function.output @@ -47,13 +47,6 @@ top_level block stmt: function_declaration - body: - block - stmt: - return_expr - value: - name_expr - identifier: identifier "x" name: identifier "identity" parameter: parameter @@ -64,3 +57,10 @@ top_level return_type: named_type_expr name: identifier "T" + body: + block + stmt: + return_expr + value: + name_expr + identifier: identifier "x" diff --git a/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output b/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output index 8db16da8ab8b..75fa887f25ac 100644 --- a/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output +++ b/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output @@ -40,10 +40,10 @@ top_level identifier: identifier "y" value: call_expr - argument: - argument - value: int_literal "1" callee: member_access_expr base: inferred_type_expr ".some" member: identifier "some" + argument: + argument + value: int_literal "1" diff --git a/unified/extractor/tests/corpus/swift/functions/method-call.output b/unified/extractor/tests/corpus/swift/functions/method-call.output index 5a8a23f5658a..76d6d3206d19 100644 --- a/unified/extractor/tests/corpus/swift/functions/method-call.output +++ b/unified/extractor/tests/corpus/swift/functions/method-call.output @@ -26,12 +26,12 @@ top_level block stmt: call_expr - argument: - argument - value: int_literal "1" callee: member_access_expr base: name_expr identifier: identifier "list" member: identifier "append" + argument: + argument + value: int_literal "1" diff --git a/unified/extractor/tests/corpus/swift/functions/variadic-function.output b/unified/extractor/tests/corpus/swift/functions/variadic-function.output index 7ca1dfbad4eb..da8d4afd4d7c 100644 --- a/unified/extractor/tests/corpus/swift/functions/variadic-function.output +++ b/unified/extractor/tests/corpus/swift/functions/variadic-function.output @@ -60,12 +60,28 @@ top_level block stmt: function_declaration + name: identifier "sum" + parameter: + parameter + external_name: identifier "_" + pattern: + name_pattern + identifier: identifier "values" + return_type: + named_type_expr + name: identifier "Int" body: block stmt: return_expr value: call_expr + callee: + member_access_expr + base: + name_expr + identifier: identifier "values" + member: identifier "reduce" argument: argument value: int_literal "0" @@ -73,19 +89,3 @@ top_level value: name_expr identifier: identifier "+" - callee: - member_access_expr - base: - name_expr - identifier: identifier "values" - member: identifier "reduce" - name: identifier "sum" - parameter: - parameter - external_name: identifier "_" - pattern: - name_pattern - identifier: identifier "values" - return_type: - named_type_expr - name: identifier "Int" diff --git a/unified/extractor/tests/corpus/swift/loops/break-and-continue.output b/unified/extractor/tests/corpus/swift/loops/break-and-continue.output index 702cd0cbc68b..76b2c77b968d 100644 --- a/unified/extractor/tests/corpus/swift/loops/break-and-continue.output +++ b/unified/extractor/tests/corpus/swift/loops/break-and-continue.output @@ -59,16 +59,22 @@ top_level block stmt: for_each_stmt + pattern: + name_pattern + identifier: identifier "x" + iterable: + name_expr + identifier: identifier "xs" body: block stmt: if_expr condition: binary_expr - operator: infix_operator "<" left: name_expr identifier: identifier "x" + operator: infix_operator "<" right: int_literal "0" then: block @@ -76,26 +82,20 @@ top_level if_expr condition: binary_expr - operator: infix_operator ">" left: name_expr identifier: identifier "x" + operator: infix_operator ">" right: int_literal "100" then: block stmt: break_expr "break" call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "x" - callee: - name_expr - identifier: identifier "print" - pattern: - name_pattern - identifier: identifier "x" - iterable: - name_expr - identifier: identifier "xs" diff --git a/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output b/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output index bb1711a23412..cd1e6d8baab1 100644 --- a/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output +++ b/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output @@ -36,18 +36,6 @@ top_level block stmt: for_each_stmt - body: - block - stmt: - call_expr - argument: - argument - value: - name_expr - identifier: identifier "x" - callee: - name_expr - identifier: identifier "print" pattern: name_pattern identifier: identifier "x" @@ -57,3 +45,15 @@ top_level int_literal "1" int_literal "2" int_literal "3" + body: + block + stmt: + call_expr + callee: + name_expr + identifier: identifier "print" + argument: + argument + value: + name_expr + identifier: identifier "x" diff --git a/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output b/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output index 87a0baf328b0..eb65a677d45b 100644 --- a/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output +++ b/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output @@ -35,23 +35,23 @@ top_level block stmt: for_each_stmt + pattern: + name_pattern + identifier: identifier "i" + iterable: + binary_expr + left: int_literal "0" + operator: infix_operator "..<" + right: int_literal "10" body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "i" - callee: - name_expr - identifier: identifier "print" - pattern: - name_pattern - identifier: identifier "i" - iterable: - binary_expr - operator: infix_operator "..<" - left: int_literal "0" - right: int_literal "10" diff --git a/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output b/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output index 84e832c2be5d..be538bde4473 100644 --- a/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output +++ b/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output @@ -39,28 +39,28 @@ top_level block stmt: for_each_stmt - body: - block - stmt: - call_expr - argument: - argument - value: - name_expr - identifier: identifier "x" - callee: - name_expr - identifier: identifier "print" pattern: name_pattern identifier: identifier "x" + iterable: + name_expr + identifier: identifier "xs" guard: binary_expr - operator: infix_operator ">" left: name_expr identifier: identifier "x" + operator: infix_operator ">" right: int_literal "0" - iterable: - name_expr - identifier: identifier "xs" + body: + block + stmt: + call_expr + callee: + name_expr + identifier: identifier "print" + argument: + argument + value: + name_expr + identifier: identifier "x" diff --git a/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output b/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output index 15b673109f2f..547a50de735b 100644 --- a/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output +++ b/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output @@ -35,15 +35,15 @@ top_level block stmt: compound_assign_expr - operator: infix_operator "-=" target: name_expr identifier: identifier "x" + operator: infix_operator "-=" value: int_literal "1" condition: binary_expr - operator: infix_operator ">" left: name_expr identifier: identifier "x" + operator: infix_operator ">" right: int_literal "0" diff --git a/unified/extractor/tests/corpus/swift/loops/while-loop.output b/unified/extractor/tests/corpus/swift/loops/while-loop.output index 516ab43a2ee5..7a57bb42068f 100644 --- a/unified/extractor/tests/corpus/swift/loops/while-loop.output +++ b/unified/extractor/tests/corpus/swift/loops/while-loop.output @@ -31,19 +31,19 @@ top_level block stmt: while_stmt + condition: + binary_expr + left: + name_expr + identifier: identifier "x" + operator: infix_operator ">" + right: int_literal "0" body: block stmt: compound_assign_expr - operator: infix_operator "-=" target: name_expr identifier: identifier "x" + operator: infix_operator "-=" value: int_literal "1" - condition: - binary_expr - operator: infix_operator ">" - left: - name_expr - identifier: identifier "x" - right: int_literal "0" diff --git a/unified/extractor/tests/corpus/swift/operators/addition.output b/unified/extractor/tests/corpus/swift/operators/addition.output index 42c0ca9de617..072682f6f381 100644 --- a/unified/extractor/tests/corpus/swift/operators/addition.output +++ b/unified/extractor/tests/corpus/swift/operators/addition.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "+" left: name_expr identifier: identifier "a" + operator: infix_operator "+" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/operators/comparison.output b/unified/extractor/tests/corpus/swift/operators/comparison.output index f9428ad17589..aeee98ecd535 100644 --- a/unified/extractor/tests/corpus/swift/operators/comparison.output +++ b/unified/extractor/tests/corpus/swift/operators/comparison.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "<" left: name_expr identifier: identifier "a" + operator: infix_operator "<" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/operators/division.output b/unified/extractor/tests/corpus/swift/operators/division.output index 765549543023..c2a0a03a23f3 100644 --- a/unified/extractor/tests/corpus/swift/operators/division.output +++ b/unified/extractor/tests/corpus/swift/operators/division.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "/" left: name_expr identifier: identifier "a" + operator: infix_operator "/" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/operators/equality.output b/unified/extractor/tests/corpus/swift/operators/equality.output index cc891492c75f..64c2fb78b178 100644 --- a/unified/extractor/tests/corpus/swift/operators/equality.output +++ b/unified/extractor/tests/corpus/swift/operators/equality.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "==" left: name_expr identifier: identifier "a" + operator: infix_operator "==" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/operators/logical-and.output b/unified/extractor/tests/corpus/swift/operators/logical-and.output index bf852cd46146..fbdbd904eafe 100644 --- a/unified/extractor/tests/corpus/swift/operators/logical-and.output +++ b/unified/extractor/tests/corpus/swift/operators/logical-and.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "&&" left: name_expr identifier: identifier "a" + operator: infix_operator "&&" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/operators/logical-or.output b/unified/extractor/tests/corpus/swift/operators/logical-or.output index e246174844c1..5d15828065cf 100644 --- a/unified/extractor/tests/corpus/swift/operators/logical-or.output +++ b/unified/extractor/tests/corpus/swift/operators/logical-or.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "||" left: name_expr identifier: identifier "a" + operator: infix_operator "||" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/operators/multiplication.output b/unified/extractor/tests/corpus/swift/operators/multiplication.output index b4c33b132863..77ce11a659e4 100644 --- a/unified/extractor/tests/corpus/swift/operators/multiplication.output +++ b/unified/extractor/tests/corpus/swift/operators/multiplication.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "*" left: name_expr identifier: identifier "a" + operator: infix_operator "*" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output b/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output index b1467474e7c6..2c89c306a6d0 100644 --- a/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output +++ b/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output @@ -20,16 +20,16 @@ top_level block stmt: binary_expr - operator: infix_operator "+" left: name_expr identifier: identifier "a" + operator: infix_operator "+" right: binary_expr - operator: infix_operator "*" left: name_expr identifier: identifier "b" + operator: infix_operator "*" right: name_expr identifier: identifier "c" diff --git a/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output b/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output index dfc60e5b7f72..36216b4201f4 100644 --- a/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output +++ b/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output @@ -24,8 +24,8 @@ top_level block stmt: binary_expr - operator: infix_operator "*" left: tuple_expr "(a + b)" + operator: infix_operator "*" right: name_expr identifier: identifier "c" diff --git a/unified/extractor/tests/corpus/swift/operators/range-operator.output b/unified/extractor/tests/corpus/swift/operators/range-operator.output index 03d0290bb7c5..574eccd9795e 100644 --- a/unified/extractor/tests/corpus/swift/operators/range-operator.output +++ b/unified/extractor/tests/corpus/swift/operators/range-operator.output @@ -16,6 +16,6 @@ top_level block stmt: binary_expr - operator: infix_operator "..." left: int_literal "1" + operator: infix_operator "..." right: int_literal "10" diff --git a/unified/extractor/tests/corpus/swift/operators/subtraction.output b/unified/extractor/tests/corpus/swift/operators/subtraction.output index 69f75e720408..993a6c3b6838 100644 --- a/unified/extractor/tests/corpus/swift/operators/subtraction.output +++ b/unified/extractor/tests/corpus/swift/operators/subtraction.output @@ -16,10 +16,10 @@ top_level block stmt: binary_expr - operator: infix_operator "-" left: name_expr identifier: identifier "a" + operator: infix_operator "-" right: name_expr identifier: identifier "b" diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output index c807bd9b7b9c..1178570e5117 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output @@ -61,11 +61,11 @@ top_level block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "error" - callee: - name_expr - identifier: identifier "print" diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output index 81a9a9187c04..c0b3a3a9783a 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output @@ -31,8 +31,8 @@ top_level identifier: identifier "n" value: binary_expr - operator: infix_operator "??" left: name_expr identifier: identifier "opt" + operator: infix_operator "??" right: int_literal "0" diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output index f1240bd0b3ef..880128cd372f 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output @@ -31,12 +31,12 @@ top_level block stmt: function_declaration + name: identifier "read" + return_type: + named_type_expr + name: identifier "String" body: block stmt: return_expr value: string_literal "\"\"" - name: identifier "read" - return_type: - named_type_expr - name: identifier "String" diff --git a/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output b/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output index 4ee195672bb2..dc137a3e621f 100644 --- a/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output +++ b/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output @@ -67,34 +67,34 @@ top_level block stmt: accessor_declaration + modifier: modifier "var" + name: identifier "p" + accessor_kind: accessor_kind "get" + type: + named_type_expr + name: identifier "Int" body: block stmt: switch_expr + value: + name_expr + identifier: identifier "y" case: switch_case - body: - block - stmt: - return_expr - value: int_literal "1" pattern: expr_equality_pattern expr: name_expr identifier: identifier "someConstant" + body: + block + stmt: + return_expr + value: int_literal "1" switch_case body: block stmt: return_expr value: int_literal "2" - value: - name_expr - identifier: identifier "y" - modifier: modifier "var" - name: identifier "p" - type: - named_type_expr - name: identifier "Int" - accessor_kind: accessor_kind "get" diff --git a/unified/extractor/tests/corpus/swift/types/class-with-initializer.output b/unified/extractor/tests/corpus/swift/types/class-with-initializer.output index 13e0097172cc..77cfa70ac351 100644 --- a/unified/extractor/tests/corpus/swift/types/class-with-initializer.output +++ b/unified/extractor/tests/corpus/swift/types/class-with-initializer.output @@ -69,6 +69,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "class" + name: identifier "Point" member: variable_declaration modifier: modifier "var" @@ -92,5 +94,3 @@ top_level value: name_expr identifier: identifier "x" - modifier: modifier "class" - name: identifier "Point" diff --git a/unified/extractor/tests/corpus/swift/types/class-with-method.output b/unified/extractor/tests/corpus/swift/types/class-with-method.output index 770030d884a7..20152cd26c8e 100644 --- a/unified/extractor/tests/corpus/swift/types/class-with-method.output +++ b/unified/extractor/tests/corpus/swift/types/class-with-method.output @@ -44,6 +44,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "class" + name: identifier "Counter" member: variable_declaration modifier: modifier "var" @@ -52,15 +54,13 @@ top_level identifier: identifier "n" value: int_literal "0" function_declaration + name: identifier "bump" body: block stmt: compound_assign_expr - operator: infix_operator "+=" target: name_expr identifier: identifier "n" + operator: infix_operator "+=" value: int_literal "1" - name: identifier "bump" - modifier: modifier "class" - name: identifier "Counter" diff --git a/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output b/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output index 9d28afe6ae0e..c2ae82ea3dae 100644 --- a/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output +++ b/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output @@ -57,6 +57,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "class" + name: identifier "Point" member: variable_declaration modifier: modifier "var" @@ -74,5 +76,3 @@ top_level type: named_type_expr name: identifier "Int" - modifier: modifier "class" - name: identifier "Point" diff --git a/unified/extractor/tests/corpus/swift/types/computed-property.output b/unified/extractor/tests/corpus/swift/types/computed-property.output index 8803e652d316..287f75956330 100644 --- a/unified/extractor/tests/corpus/swift/types/computed-property.output +++ b/unified/extractor/tests/corpus/swift/types/computed-property.output @@ -88,6 +88,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "class" + name: identifier "Rect" member: variable_declaration modifier: modifier "var" @@ -106,24 +108,22 @@ top_level named_type_expr name: identifier "Double" accessor_declaration + modifier: modifier "var" + name: identifier "area" + accessor_kind: accessor_kind "get" + type: + named_type_expr + name: identifier "Double" body: block stmt: return_expr value: binary_expr - operator: infix_operator "*" left: name_expr identifier: identifier "w" + operator: infix_operator "*" right: name_expr identifier: identifier "h" - modifier: modifier "var" - name: identifier "area" - type: - named_type_expr - name: identifier "Double" - accessor_kind: accessor_kind "get" - modifier: modifier "class" - name: identifier "Rect" diff --git a/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output b/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output index 12b5191f69f9..f520b649095a 100644 --- a/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output +++ b/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output @@ -53,34 +53,34 @@ top_level block stmt: class_like_declaration + modifier: modifier "enum" + name: identifier "Shape" member: class_like_declaration + modifier: modifier "enum_case" + name: identifier "circle" member: constructor_declaration - body: block "circle(radius: Double)" parameter: parameter - pattern: - name_pattern - identifier: identifier "radius" type: named_type_expr name: identifier "Double" - modifier: modifier "enum_case" - name: identifier "circle" + pattern: + name_pattern + identifier: identifier "radius" + body: block "circle(radius: Double)" class_like_declaration + modifier: modifier "enum_case" + name: identifier "square" member: constructor_declaration - body: block "square(side: Double)" parameter: parameter - pattern: - name_pattern - identifier: identifier "side" type: named_type_expr name: identifier "Double" - modifier: modifier "enum_case" - name: identifier "square" - modifier: modifier "enum" - name: identifier "Shape" + pattern: + name_pattern + identifier: identifier "side" + body: block "square(side: Double)" diff --git a/unified/extractor/tests/corpus/swift/types/enum-with-cases.output b/unified/extractor/tests/corpus/swift/types/enum-with-cases.output index 72435fd2b1f7..f70f43bfb8ec 100644 --- a/unified/extractor/tests/corpus/swift/types/enum-with-cases.output +++ b/unified/extractor/tests/corpus/swift/types/enum-with-cases.output @@ -39,6 +39,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "enum" + name: identifier "Direction" member: variable_declaration modifier: modifier "enum_case" @@ -60,5 +62,3 @@ top_level pattern: name_pattern identifier: identifier "west" - modifier: modifier "enum" - name: identifier "Direction" diff --git a/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output b/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output index 6a4aac4b552d..56e793247bbf 100644 --- a/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output +++ b/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output @@ -30,6 +30,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "enum" + name: identifier "Suit" member: variable_declaration modifier: modifier "enum_case" @@ -57,5 +59,3 @@ top_level pattern: name_pattern identifier: identifier "spades" - modifier: modifier "enum" - name: identifier "Suit" diff --git a/unified/extractor/tests/corpus/swift/types/extension.output b/unified/extractor/tests/corpus/swift/types/extension.output index ad663cb86e16..894b8c530c81 100644 --- a/unified/extractor/tests/corpus/swift/types/extension.output +++ b/unified/extractor/tests/corpus/swift/types/extension.output @@ -45,24 +45,24 @@ top_level block stmt: class_like_declaration + modifier: modifier "extension" + name: identifier "Int" member: function_declaration + name: identifier "squared" + return_type: + named_type_expr + name: identifier "Int" body: block stmt: return_expr value: binary_expr - operator: infix_operator "*" left: name_expr identifier: identifier "self" + operator: infix_operator "*" right: name_expr identifier: identifier "self" - name: identifier "squared" - return_type: - named_type_expr - name: identifier "Int" - modifier: modifier "extension" - name: identifier "Int" diff --git a/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output b/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output index 0a8b2d8cc5d7..a44c6fc3c127 100644 --- a/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output +++ b/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output @@ -80,6 +80,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "class" + name: identifier "Box" member: variable_declaration modifier: modifier "var" @@ -88,6 +90,12 @@ top_level identifier: identifier "_v" value: int_literal "0" accessor_declaration + modifier: modifier "var" + name: identifier "v" + accessor_kind: accessor_kind "get" + type: + named_type_expr + name: identifier "Int" body: block stmt: @@ -95,13 +103,15 @@ top_level value: name_expr identifier: identifier "_v" - modifier: modifier "var" + accessor_declaration + modifier: + modifier "var" + modifier "chained_declaration" name: identifier "v" + accessor_kind: accessor_kind "set" type: named_type_expr name: identifier "Int" - accessor_kind: accessor_kind "get" - accessor_declaration body: block stmt: @@ -112,13 +122,3 @@ top_level value: name_expr identifier: identifier "newValue" - modifier: - modifier "var" - modifier "chained_declaration" - name: identifier "v" - type: - named_type_expr - name: identifier "Int" - accessor_kind: accessor_kind "set" - modifier: modifier "class" - name: identifier "Box" diff --git a/unified/extractor/tests/corpus/swift/types/protocol-declaration.output b/unified/extractor/tests/corpus/swift/types/protocol-declaration.output index 55a71218c18c..628b58e979f6 100644 --- a/unified/extractor/tests/corpus/swift/types/protocol-declaration.output +++ b/unified/extractor/tests/corpus/swift/types/protocol-declaration.output @@ -21,9 +21,9 @@ top_level block stmt: class_like_declaration + modifier: modifier "protocol" + name: identifier "Drawable" member: function_declaration - body: block "func draw()" name: identifier "draw" - modifier: modifier "protocol" - name: identifier "Drawable" + body: block "func draw()" diff --git a/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output b/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output index 0293534adf76..11296b02e005 100644 --- a/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output +++ b/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output @@ -61,25 +61,25 @@ top_level block stmt: class_like_declaration + modifier: modifier "protocol" + name: identifier "P" member: accessor_declaration name: identifier "foo" + accessor_kind: accessor_kind "get" type: named_type_expr name: identifier "Int" - accessor_kind: accessor_kind "get" accessor_declaration name: identifier "bar" + accessor_kind: accessor_kind "get" type: named_type_expr name: identifier "String" - accessor_kind: accessor_kind "get" accessor_declaration modifier: modifier "chained_declaration" name: identifier "bar" + accessor_kind: accessor_kind "set" type: named_type_expr name: identifier "String" - accessor_kind: accessor_kind "set" - modifier: modifier "protocol" - name: identifier "P" diff --git a/unified/extractor/tests/corpus/swift/types/struct.output b/unified/extractor/tests/corpus/swift/types/struct.output index e130ef9b8623..7de3a4f5fde5 100644 --- a/unified/extractor/tests/corpus/swift/types/struct.output +++ b/unified/extractor/tests/corpus/swift/types/struct.output @@ -57,6 +57,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "struct" + name: identifier "Point" member: variable_declaration modifier: modifier "let" @@ -74,5 +76,3 @@ top_level type: named_type_expr name: identifier "Int" - modifier: modifier "struct" - name: identifier "Point" diff --git a/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output b/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output index 65364c1ef918..a6d554ca8370 100644 --- a/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output +++ b/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output @@ -44,20 +44,20 @@ top_level identifier: identifier "x" value: switch_expr + value: + name_expr + identifier: identifier "y" case: switch_case - body: - block - stmt: int_literal "1" pattern: expr_equality_pattern expr: name_expr identifier: identifier "someConstant" + body: + block + stmt: int_literal "1" switch_case body: block stmt: int_literal "2" - value: - name_expr - identifier: identifier "y" diff --git a/unified/extractor/tests/corpus/swift/variables/compound-assignment.output b/unified/extractor/tests/corpus/swift/variables/compound-assignment.output index 95b7edb0a6ba..5385e0ae46be 100644 --- a/unified/extractor/tests/corpus/swift/variables/compound-assignment.output +++ b/unified/extractor/tests/corpus/swift/variables/compound-assignment.output @@ -18,8 +18,8 @@ top_level block stmt: compound_assign_expr - operator: infix_operator "+=" target: name_expr identifier: identifier "x" + operator: infix_operator "+=" value: int_literal "1" diff --git a/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output b/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output index 2d612e1d950e..5bf4b48efbcc 100644 --- a/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output +++ b/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output @@ -72,6 +72,8 @@ top_level block stmt: class_like_declaration + modifier: modifier "class" + name: identifier "C" member: variable_declaration modifier: modifier "var" @@ -83,40 +85,38 @@ top_level name: identifier "Int" value: int_literal "0" accessor_declaration + modifier: + modifier "var" + modifier "chained_declaration" + name: identifier "x" + accessor_kind: accessor_kind "willSet" body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "newValue" - callee: - name_expr - identifier: identifier "print" + accessor_declaration modifier: modifier "var" modifier "chained_declaration" name: identifier "x" - accessor_kind: accessor_kind "willSet" - accessor_declaration + accessor_kind: accessor_kind "didSet" body: block stmt: call_expr + callee: + name_expr + identifier: identifier "print" argument: argument value: name_expr identifier: identifier "oldValue" - callee: - name_expr - identifier: identifier "print" - modifier: - modifier "var" - modifier "chained_declaration" - name: identifier "x" - accessor_kind: accessor_kind "didSet" - modifier: modifier "class" - name: identifier "C" From 8206f6fa9785381b1a571074496b3a957bcf7675 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 16 Jul 2026 12:05:37 +0000 Subject: [PATCH 02/27] swift-syntax-rs: Fold local and stdlib operators In our swift-syntax wrapper, we now attempt to fold all operator sequences (i.e. `sequenceExpr` nodes) into appropriate `infixOperatorExpr` nodes, assuming the requisite operator definitions are present. Currently, we only consider operators that are defined in the standard library, and operators that are defined in the current file, leaving operators defined in separate modules as future work. The folding is done maximally -- if an argument of an unknown operator can be folded in isolation, then this is done. Each top-level sequence is folded independently, so a single unknown operator leaves only its own sequence flat rather than aborting folding elsewhere. --- unified/swift-syntax-rs/BUILD.bazel | 1 + unified/swift-syntax-rs/README.md | 31 +++++++ unified/swift-syntax-rs/src/lib.rs | 83 +++++++++++++++++++ unified/swift-syntax-rs/swift/Package.swift | 1 + .../SwiftSyntaxFFI/SwiftSyntaxFFI.swift | 69 ++++++++++++++- 5 files changed, 184 insertions(+), 1 deletion(-) diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 5c2360d4deba..11484736ca8f 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -27,6 +27,7 @@ xcode_transition_swift_library( module_name = "SwiftSyntaxFFI", target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, deps = [ + "@swift-syntax//:SwiftOperators", "@swift-syntax//:SwiftParser", "@swift-syntax//:SwiftSyntax", ], diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index d43969199f91..7d15c6f2c23e 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -91,6 +91,37 @@ arrays (their collection nodes are elided), layout children such as comment rides along as `trailingTrivia` on the token it follows. Tokens without trivia (most of them) simply omit the `leadingTrivia`/`trailingTrivia` keys. +### Operator folding + +Swift's grammar does not encode operator precedence, so the parser represents an +expression like `a + b * c` as a flat `sequenceExpr` (an alternating list of +operands and operators). Before serializing, we fold these sequences into +precedence-correct `infixOperatorExpr` (and `ternaryExpr`) trees — so `1 + 2 * 3` +becomes `1 + (2 * 3)`. + +Folding needs to know each operator's precedence group, which comes from +declarations rather than the grammar. We resolve operators from two sources: + +- the **Swift standard library** operators (a built-in approximation), and +- operator / precedence-group declarations **in the file being parsed**. + +Operators defined anywhere else (for example, imported from another module) are +unknown, so their precedence cannot be determined. Rather than guess — which +would silently produce a wrongly-structured tree — each top-level sequence is +folded independently, and any sequence that uses an unknown operator is left as +a flat `sequenceExpr`. So `a <+> b` (with an undeclared `<+>`) stays flat, while +a neighbouring `1 + 2` in the same file still folds. Supporting operators from +other modules is future work. + +Folding is bottom-up, so a *grouped* subexpression still folds even when the +sequence enclosing it uses an unknown operator: in `a *** (b + c)` (with an +unknown `***`) the parenthesised `b + c` is its own sequence and folds, while +the outer `a *** …` stays flat. This only applies when the subexpression is +syntactically isolated (parentheses, call arguments, collection elements, …); +an unparenthesised `a *** b + c` is a single flat sequence whose structure +cannot be determined without knowing `***`'s precedence, so it is left flat in +its entirety. + ## Prerequisites The build does not depend on any particular version manager. You need: diff --git a/unified/swift-syntax-rs/src/lib.rs b/unified/swift-syntax-rs/src/lib.rs index 2a8fe0411af8..5f558d942fc8 100644 --- a/unified/swift-syntax-rs/src/lib.rs +++ b/unified/swift-syntax-rs/src/lib.rs @@ -132,4 +132,87 @@ mod tests { "comment trivia not captured: {json}" ); } + + #[test] + fn folds_standard_library_operators() { + // Standard-library operators are folded into a precedence-correct tree: + // the flat `sequenceExpr` becomes nested `infixOperatorExpr` nodes. + let json = parse_to_json("let x = 1 + 2 * 3").expect("parsing should succeed"); + assert!( + json.contains("\"kind\":\"infixOperatorExpr\""), + "operators were not folded: {json}" + ); + assert!( + !json.contains("\"kind\":\"sequenceExpr\""), + "a foldable sequence was left flat: {json}" + ); + } + + #[test] + fn folds_file_defined_operators() { + // An operator declared in the same file (with a known precedence group) + // is folded, just like a standard-library one. + let json = parse_to_json("infix operator |>: AdditionPrecedence\nlet y = a |> b |> c") + .expect("parsing should succeed"); + assert!( + json.contains("\"kind\":\"infixOperatorExpr\""), + "file-defined operator was not folded: {json}" + ); + assert!( + !json.contains("\"kind\":\"sequenceExpr\""), + "a foldable sequence was left flat: {json}" + ); + } + + #[test] + fn leaves_unknown_operators_unfolded() { + // An operator that is neither in the standard library nor declared in + // this file (e.g. imported from another module) has unknown precedence, + // so its sequence is left flat rather than folded incorrectly. + let json = parse_to_json("let z = a <+> b").expect("parsing should succeed"); + assert!( + json.contains("\"kind\":\"sequenceExpr\""), + "unknown-operator sequence should stay flat: {json}" + ); + assert!( + !json.contains("\"kind\":\"infixOperatorExpr\""), + "unknown operator should not be folded: {json}" + ); + } + + #[test] + fn folds_each_sequence_independently() { + // Folding is isolated per sequence: a statement using a known operator + // folds even when another statement uses an unknown one. One unfoldable + // expression does not suppress folding elsewhere. + let json = parse_to_json("let x = 1 + 2\nlet y = a <+> b").expect("parsing should succeed"); + assert!( + json.contains("\"kind\":\"infixOperatorExpr\""), + "the known-operator statement should fold: {json}" + ); + assert!( + json.contains("\"kind\":\"sequenceExpr\""), + "the unknown-operator statement should stay flat: {json}" + ); + } + + #[test] + fn folds_grouped_subexpressions_under_unknown_operators() { + // A parenthesized (or otherwise bracketed) subexpression is its own + // sequence, so it folds independently even when the enclosing sequence + // uses an unknown operator. Here `***` is unknown (outer stays flat) but + // the grouped `b + c` still folds. Note this only works because the + // parentheses isolate `b + c`: in an unparenthesized `a *** b + c` the + // whole thing is one flat sequence whose structure can't be determined + // without `***`'s precedence, so it is left flat entirely. + let json = parse_to_json("let r = a *** (b + c)").expect("parsing should succeed"); + assert!( + json.contains("\"kind\":\"infixOperatorExpr\""), + "the grouped known-operator subexpression should fold: {json}" + ); + assert!( + json.contains("\"kind\":\"sequenceExpr\""), + "the enclosing unknown-operator sequence should stay flat: {json}" + ); + } } diff --git a/unified/swift-syntax-rs/swift/Package.swift b/unified/swift-syntax-rs/swift/Package.swift index a58b7c1a479b..37fa5a1eed7b 100644 --- a/unified/swift-syntax-rs/swift/Package.swift +++ b/unified/swift-syntax-rs/swift/Package.swift @@ -29,6 +29,7 @@ let package = Package( dependencies: [ .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftOperators", package: "swift-syntax"), ] ) ] diff --git a/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift index 6305b1610d06..9471c2ea143d 100644 --- a/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift +++ b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift @@ -1,4 +1,5 @@ import Foundation +import SwiftOperators import SwiftParser // `@_spi(RawSyntax)` exposes the `childName(_:)` helper that maps a child's @@ -151,6 +152,68 @@ private func serialize( return result } +/// Fold the flat operator sequences in `tree` into structured binary/ternary +/// expressions, using operator precedence. +/// +/// Swift's grammar does not encode operator precedence: an expression like +/// `a + b * c` is parsed as a flat `SequenceExpr` (a list of operands and +/// operators). Resolving it into a precedence-correct tree requires knowing the +/// operators' precedence groups, which live in declarations rather than the +/// grammar. We fold using two sources of operator definitions: +/// +/// * `OperatorTable.standardOperators` — the Swift standard library's +/// operators (a built-in approximation), and +/// * the operator / precedence-group declarations in *this* file +/// (via `addSourceFile`). +/// +/// Operators from anywhere else (e.g. imported from another module) are +/// unknown to us. Rather than guess their precedence — which would silently +/// produce an incorrectly-structured tree — we fold each top-level sequence +/// *independently* and leave any sequence containing an unknown operator as a +/// flat `SequenceExpr`. Downstream can treat such a sequence as unsupported. +/// This keeps folding correct for the operators we do know while isolating the +/// rest per sequence, so one exotic operator doesn't block folding elsewhere. +private func foldOperators(in tree: SourceFileSyntax) -> Syntax { + var operators = OperatorTable.standardOperators + // Register operators and precedence groups declared in this file. Swallow + // errors (e.g. a redeclaration of a standard operator): keep whatever we + // can and carry on. + operators.addSourceFile(tree, errorHandler: { _ in }) + return PerSequenceFolder(operators: operators).rewrite(tree) +} + +/// A `SyntaxRewriter` that folds each `SequenceExpr` on its own, leaving +/// sequences it cannot fold (because they use an operator we don't know) flat. +/// +/// This is deliberately more conservative than `OperatorTable.foldAll`, which +/// is all-or-nothing: with a throwing error handler a single unknown operator +/// aborts folding for the *entire* tree, while with a non-throwing handler +/// unknown operators are folded *arbitrarily* (left-associatively), silently +/// producing a wrong tree. Folding each sequence with a throwing handler and +/// catching the error gives us per-sequence isolation instead. +private final class PerSequenceFolder: SyntaxRewriter { + private let operators: OperatorTable + + init(operators: OperatorTable) { + self.operators = operators + super.init() + } + + override func visit(_ node: SequenceExprSyntax) -> ExprSyntax { + // Fold any nested sequences first (bottom-up), so that an unfoldable + // outer sequence still keeps its foldable inner sequences folded. + let inner = super.visit(node).as(SequenceExprSyntax.self)! + do { + // The default error handler throws on the first unknown operator or + // incomparable-precedence pair. + return try operators.foldSingle(inner) + } catch { + // Leave this sequence flat; it uses an operator we don't know. + return ExprSyntax(inner) + } + } +} + /// Parse the given NUL-terminated Swift source string and return a /// heap-allocated, NUL-terminated JSON representation of the syntax tree. /// @@ -161,8 +224,12 @@ public func ssr_parse_json(_ source: UnsafePointer?) -> UnsafeMutablePoin guard let source = source else { return nil } let code = String(cString: source) let tree = Parser.parse(source: code) + // Fold operator sequences before serializing. Source positions are + // preserved by folding (the same tokens, in the same places), so a + // converter built from the original tree maps the folded tree correctly. + let folded = foldOperators(in: tree) let converter = SourceLocationConverter(fileName: "", tree: tree) - let json = serialize(Syntax(tree), converter) + let json = serialize(folded, converter) guard let data = try? JSONSerialization.data( From 2e74c1d83f791776b2f109f6ff0007a4dbc9ee5c Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 16 Jul 2026 20:47:45 +0000 Subject: [PATCH 03/27] yeast: Desugar an externally-built AST, and validate by field name Prepare yeast for front-ends that do not parse with tree-sitter (e.g. the swift-syntax front-end, whose parser hands us a ready-built `yeast::Ast`): - `Runner`/`ConcreteDesugarer` now hold `Option`. New constructors `Runner::with_schema_no_language`, `ConcreteDesugarer::without_language`, and `DesugaringConfig::build_schema_no_language` build the schema from the output node-types YAML alone. The parsing entry points (`run`/`run_from_tree`) error when no language is present; `run_from_ast` needs none. - `BuildCtx::source_text` is a small convenience for Rust-block rules that read a captured token's source text. - AST-dump type validation now resolves field constraints and required fields by field NAME rather than by field id. A field id is local to the schema that assigned it, so an AST built by one schema (e.g. an external parser's adapter) could not be validated against another (the output node-types schema) without re-keying. Looking up by name keeps the two schemas full independent: they share field names, not ids. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- shared/yeast/src/build.rs | 11 ++++++ shared/yeast/src/dump.rs | 24 +++++++++--- shared/yeast/src/lib.rs | 77 +++++++++++++++++++++++++++++++++------ 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/shared/yeast/src/build.rs b/shared/yeast/src/build.rs index 508a1e731346..f4f5ae5d18f3 100644 --- a/shared/yeast/src/build.rs +++ b/shared/yeast/src/build.rs @@ -108,6 +108,17 @@ impl<'a, C> BuildCtx<'a, C> { self.captures.get_all(name) } + /// Read the source text of a captured (or any other) node. + /// + /// Convenience for the common `ctx.ast.source_text(id)` reach-through used + /// by Rust-block rules that branch on, or embed, a raw capture's spelling + /// (e.g. an operator or keyword token). Resolves against the stored source + /// bytes, so it works for both parsed nodes and synthesized ones (via their + /// inherited source range). + pub fn source_text(&self, id: Id) -> String { + self.ast.source_text(id) + } + /// Create a named AST node with the given kind and fields. pub fn node(&mut self, kind: &str, fields: Vec<(&str, Vec)>) -> Id { let kind_id = self diff --git a/shared/yeast/src/dump.rs b/shared/yeast/src/dump.rs index f217f1798068..dc348f8789b6 100644 --- a/shared/yeast/src/dump.rs +++ b/shared/yeast/src/dump.rs @@ -162,8 +162,12 @@ fn type_error_for_node( fn expected_for_field<'a>( schema: &'a Schema, parent_kind: &str, - field_id: u16, + field_name: &str, ) -> Option<&'a [crate::schema::NodeType]> { + // Resolve the field NAME in the validation schema's own id space, so the + // AST being dumped and the validation schema stay completely independent: + // they need not share field ids, only field names. + let field_id = schema.field_id_for_name(field_name)?; schema .field_types(parent_kind, field_id) .map(|v| v.as_slice()) @@ -264,8 +268,8 @@ fn dump_node( let children = &node.fields[&field_id]; let field_name = ast.field_name_for_id(field_id).unwrap_or("?"); let child_type_check = type_check.map(|(schema, _, _)| { - let expected = - expected_for_field(schema, node.kind_name(), field_id).or(Some(EMPTY_NODE_TYPES)); + let expected = expected_for_field(schema, node.kind_name(), field_name) + .or(Some(EMPTY_NODE_TYPES)); let parent_field = Some((node.kind_name(), field_name)); (schema, expected, parent_field) }); @@ -307,8 +311,14 @@ fn dump_node( // Check for required fields that are absent if let Some((schema, _, _)) = type_check { - for (field_id, field_name) in schema.required_fields_for_kind(node.kind_name()) { - if !node.fields.contains_key(&field_id) { + for (_field_id, field_name) in schema.required_fields_for_kind(node.kind_name()) { + let present = match field_name { + Some(n) => ast + .field_id_for_name(n) + .is_some_and(|fid| node.fields.contains_key(&fid)), + None => node.fields.contains_key(&CHILD_FIELD), + }; + if !present { let name = field_name.unwrap_or("child"); writeln!(out, "{prefix} <-- ERROR: missing required field '{name}'").unwrap(); } @@ -318,7 +328,9 @@ fn dump_node( // Unnamed children — skip unnamed tokens (keywords, punctuation) if let Some(children) = node.fields.get(&CHILD_FIELD) { let child_type_check = type_check.map(|(schema, _, _)| { - let expected = expected_for_field(schema, node.kind_name(), CHILD_FIELD) + let expected = schema + .field_types(node.kind_name(), CHILD_FIELD) + .map(|v| v.as_slice()) .or(Some(EMPTY_NODE_TYPES)); let parent_field = Some((node.kind_name(), "children")); (schema, expected, parent_field) diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 6719a5498e2b..14a0ab055761 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -1329,10 +1329,27 @@ impl DesugaringConfig { None => Ok(schema::from_language(language)), } } + + /// Build the yeast `Schema` from the output YAML alone, with no input + /// tree-sitter grammar. Requires `output_node_types_yaml` to be set (there + /// is no grammar to fall back to). Used by custom (non-tree-sitter) + /// front-ends whose input schema is supplied by their own parser/adapter. + pub fn build_schema_no_language(&self) -> Result { + match self.output_node_types_yaml { + Some(yaml) => node_types_yaml::schema_from_yaml(yaml), + None => Err( + "a language-free desugarer requires output_node_types_yaml to be set".to_string(), + ), + } + } } pub struct Runner<'a, C = ()> { - language: tree_sitter::Language, + /// The input tree-sitter language, used only when parsing (`run_from_tree` + /// / `run`). `None` for pipelines that only ever run over an + /// externally-built AST (`run_from_ast`), so no tree-sitter grammar is + /// required. + language: Option, schema: schema::Schema, phases: &'a [Phase], } @@ -1342,7 +1359,7 @@ impl<'a, C> Runner<'a, C> { pub fn new(language: tree_sitter::Language, phases: &'a [Phase]) -> Self { let schema = schema::from_language(&language); Self { - language, + language: Some(language), schema, phases, } @@ -1355,7 +1372,18 @@ impl<'a, C> Runner<'a, C> { phases: &'a [Phase], ) -> Self { Self { - language, + language: Some(language), + schema: schema.clone(), + phases, + } + } + + /// Create a runner with no input tree-sitter language, for pipelines that + /// only run over an externally-built AST (`run_from_ast`). The parsing + /// entry points (`run_from_tree` / `run`) will error if called. + pub fn with_schema_no_language(schema: &schema::Schema, phases: &'a [Phase]) -> Self { + Self { + language: None, schema: schema.clone(), phases, } @@ -1368,7 +1396,7 @@ impl<'a, C> Runner<'a, C> { ) -> Result { let schema = config.build_schema(&language)?; Ok(Self { - language, + language: Some(language), schema, phases: &config.phases, }) @@ -1388,7 +1416,9 @@ impl<'a, C: Clone> Runner<'a, C> { let mut ast = Ast::from_tree_with_schema_and_source( self.schema.clone(), tree, - &self.language, + self.language + .as_ref() + .ok_or("run_from_tree requires a tree-sitter language")?, source.to_vec(), ); self.run_phases(&mut ast, user_ctx)?; @@ -1398,9 +1428,13 @@ impl<'a, C: Clone> Runner<'a, C> { /// Parse `input` and run all phases, threading `user_ctx` through /// every rule transform. The caller owns the initial context state. pub fn run_with_ctx(&self, input: &str, user_ctx: &mut C) -> Result { + let language = self + .language + .as_ref() + .ok_or("run requires a tree-sitter language")?; let mut parser = tree_sitter::Parser::new(); parser - .set_language(&self.language) + .set_language(language) .map_err(|e| format!("Failed to set language: {e}"))?; let tree = parser .parse(input, None) @@ -1408,7 +1442,7 @@ impl<'a, C: Clone> Runner<'a, C> { let mut ast = Ast::from_tree_with_schema_and_source( self.schema.clone(), &tree, - &self.language, + language, input.as_bytes().to_vec(), ); self.run_phases(&mut ast, user_ctx)?; @@ -1506,7 +1540,10 @@ pub trait Desugarer: Send + Sync { /// schema so that per-call cost is bounded to constructing a transient /// [`Runner`] and cloning the schema (no YAML re-parsing). pub struct ConcreteDesugarer { - language: tree_sitter::Language, + /// The input tree-sitter language, or `None` for a custom (non-tree-sitter) + /// front-end that only ever desugars an externally-built AST + /// (`run_from_ast`). + language: Option, schema: schema::Schema, config: DesugaringConfig, } @@ -1520,7 +1557,20 @@ impl ConcreteDesugarer { ) -> Result { let schema = config.build_schema(&language)?; Ok(Self { - language, + language: Some(language), + schema, + config, + }) + } + + /// Build a desugarer with no input tree-sitter language, for a custom + /// front-end whose parser supplies the input AST directly. Only + /// [`run_from_ast`](Desugarer::run_from_ast) is supported; the schema comes + /// from the config's `output_node_types_yaml` (which must be set). + pub fn without_language(config: DesugaringConfig) -> Result { + let schema = config.build_schema_no_language()?; + Ok(Self { + language: None, schema, config, }) @@ -1533,7 +1583,11 @@ impl Desugarer for ConcreteDesugarer } fn run_from_tree(&self, tree: &tree_sitter::Tree, source: &[u8]) -> Result { - let runner = Runner::with_schema(self.language.clone(), &self.schema, &self.config.phases); + let language = self + .language + .clone() + .ok_or("run_from_tree requires a tree-sitter language")?; + let runner = Runner::with_schema(language, &self.schema, &self.config.phases); runner.run_from_tree(tree, source) } @@ -1541,7 +1595,8 @@ impl Desugarer for ConcreteDesugarer // The AST was built against its own (external) schema; make sure the // output kind/field names the rules build are resolvable in it. ast.register_names_from_schema(&self.schema); - let runner = Runner::with_schema(self.language.clone(), &self.schema, &self.config.phases); + // `run_from_ast` never parses, so no tree-sitter language is needed. + let runner = Runner::with_schema_no_language(&self.schema, &self.config.phases); runner.run_from_ast(ast) } } From bbf52ce7a76bb8b620801863e9d2598210a6e285 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 17 Jul 2026 12:26:55 +0000 Subject: [PATCH 04/27] tree-sitter-extractor: Split direct and desugaring extractors Previously the `simple` multi-language extractor carried an optional desugarer, so every language (including plain tree-sitter ones such as ql, dbscheme, json and blame) went through the same desugaring-aware extraction path. This commit splits it into two front-ends that share a private driver: - `simple`: pure tree-sitter extraction with no desugaring. Comments and other `extra` nodes are emitted inline as tokens. (The extractor then extracts these as usual.) - `desugaring`: parses source into a `ParsedTree` (a yeast AST plus side-channel `extra` tokens) and rewrites the AST through a `yeast::Desugarer` before extraction. The parser is a closure, so both tree-sitter grammars (via `tree_sitter_parser`) and custom parsers plug in the same way. The shared multi-file plumbing (threading, glob matching, source-archive copying, TRAP writing) lives in a new private `driver` module behind a `LanguageExtractor` trait, so neither front-end duplicates it. `extract` no longer takes an optional desugarer (it always walks the parse tree directly); `extract_parsed` takes a required desugarer. ql and ruby use the direct path; the unified Swift extractor uses the desugaring path. Also rename the new side-channel identifiers from "trivia" to "extra" (ExtraToken, ParsedTree.extras, emit_extra, ...) to match tree-sitter's own `is_extra()` terminology. The pre-existing `*_trivia_tokeninfo` relation is left unchanged for a separate change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ql/Cargo.lock | 10 + ql/extractor/src/extractor.rs | 4 - ruby/extractor/src/extractor.rs | 6 +- .../src/extractor/desugaring.rs | 104 ++++++++ .../src/extractor/driver.rs | 210 +++++++++++++++ .../src/extractor/mod.rs | 219 +++++++++++++--- .../src/extractor/simple.rs | 240 ++++-------------- .../tests/integration_test.rs | 1 - .../tests/multiple_languages.rs | 2 - unified/extractor/src/extractor.rs | 4 +- unified/extractor/src/languages/mod.rs | 4 +- .../extractor/src/languages/swift/swift.rs | 10 +- unified/extractor/tests/corpus_tests.rs | 59 ++--- .../extractor/tests/swift_syntax_pipeline.rs | 2 +- 14 files changed, 586 insertions(+), 289 deletions(-) create mode 100644 shared/tree-sitter-extractor/src/extractor/desugaring.rs create mode 100644 shared/tree-sitter-extractor/src/extractor/driver.rs diff --git a/ql/Cargo.lock b/ql/Cargo.lock index 5c65d88de6a7..d6ea9ea343ef 100644 --- a/ql/Cargo.lock +++ b/ql/Cargo.lock @@ -1017,6 +1017,7 @@ dependencies = [ "tree-sitter-python", "tree-sitter-ruby", "yeast-macros", + "yeast-schema", ] [[package]] @@ -1028,6 +1029,15 @@ dependencies = [ "syn", ] +[[package]] +name = "yeast-schema" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "serde_yaml", +] + [[package]] name = "zstd" version = "0.13.3" diff --git a/ql/extractor/src/extractor.rs b/ql/extractor/src/extractor.rs index 66096442e85f..8383d8424eee 100644 --- a/ql/extractor/src/extractor.rs +++ b/ql/extractor/src/extractor.rs @@ -29,28 +29,24 @@ pub fn run(options: Options) -> std::io::Result<()> { prefix: "ql", ts_language: tree_sitter_ql::LANGUAGE.into(), node_types: tree_sitter_ql::NODE_TYPES, - desugar: None, file_globs: vec!["*.ql".into(), "*.qll".into()], }, simple::LanguageSpec { prefix: "dbscheme", ts_language: tree_sitter_ql_dbscheme::LANGUAGE.into(), node_types: tree_sitter_ql_dbscheme::NODE_TYPES, - desugar: None, file_globs: vec!["*.dbscheme".into()], }, simple::LanguageSpec { prefix: "json", ts_language: tree_sitter_json::LANGUAGE.into(), node_types: tree_sitter_json::NODE_TYPES, - desugar: None, file_globs: vec!["*.json".into(), "*.jsonl".into(), "*.jsonc".into()], }, simple::LanguageSpec { prefix: "blame", ts_language: tree_sitter_blame::LANGUAGE.into(), node_types: tree_sitter_blame::NODE_TYPES, - desugar: None, file_globs: vec!["*.blame".into()], }, ], diff --git a/ruby/extractor/src/extractor.rs b/ruby/extractor/src/extractor.rs index d418c144bfc9..3749609e7885 100644 --- a/ruby/extractor/src/extractor.rs +++ b/ruby/extractor/src/extractor.rs @@ -94,7 +94,9 @@ pub fn run(options: Options) -> std::io::Result<()> { node_types::read_node_types_str("erb", tree_sitter_embedded_template::NODE_TYPES)?; let lines: std::io::Result> = std::io::BufReader::new(file_list).lines().collect(); let lines = lines?; - let source_root = std::env::current_dir().ok().and_then(|d| d.canonicalize().ok()); + let source_root = std::env::current_dir() + .ok() + .and_then(|d| d.canonicalize().ok()); lines .par_iter() .try_for_each(|line| { @@ -126,7 +128,6 @@ pub fn run(options: Options) -> std::io::Result<()> { &path, &source, &[], - None, ); let (ranges, line_breaks) = scan_erb( @@ -215,7 +216,6 @@ pub fn run(options: Options) -> std::io::Result<()> { &path, &source, &code_ranges, - None, ); std::fs::create_dir_all(src_archive_file.parent().unwrap())?; if needs_conversion { diff --git a/shared/tree-sitter-extractor/src/extractor/desugaring.rs b/shared/tree-sitter-extractor/src/extractor/desugaring.rs new file mode 100644 index 000000000000..c52659750c77 --- /dev/null +++ b/shared/tree-sitter-extractor/src/extractor/desugaring.rs @@ -0,0 +1,104 @@ +//! Extraction for languages that rewrite their syntax tree before extraction. +//! +//! Unlike [`crate::extractor::simple`] (direct tree-sitter extraction), a +//! desugaring language parses source into a [`ParsedTree`] — a `yeast::Ast` +//! plus side-channel `extra` tokens (comments and similar) — and rewrites the +//! AST through a [`yeast::Desugarer`] before emitting TRAP. The parser is a +//! closure, so both tree-sitter grammars (via +//! [`crate::extractor::tree_sitter_parser`]) and fully custom parsers plug in +//! the same way. + +use crate::trap; +use std::path::PathBuf; + +use crate::diagnostics; +use crate::extractor::ParsedTree; +use crate::extractor::driver::{self, LanguageExtractor}; +use crate::node_types::{self, NodeTypeMap}; + +/// A parser turns source bytes into a [`ParsedTree`]. Tree-sitter grammars plug +/// in via [`crate::extractor::tree_sitter_parser`]; custom (non-tree-sitter) +/// parsers supply their own closure. +pub type Parser = Box Result + Send + Sync>; + +pub struct LanguageSpec { + pub prefix: &'static str, + /// The parser: source -> `yeast::Ast` + `extra` tokens (see [`Parser`]). + pub parser: Parser, + /// Fallback TRAP schema, used only when `desugarer` does not supply its own + /// output schema (via [`yeast::Desugarer::output_node_types_yaml`]). May be + /// empty for a custom parser whose desugarer always provides the schema. + pub node_types: &'static str, + /// The desugarer applied to the parsed AST before extraction. Its + /// `output_node_types_yaml()` (when set) provides the TRAP schema. + /// + /// `Box` so the shared extractor is agnostic to the + /// user-defined context type the desugarer uses internally. + pub desugarer: Box, + pub file_globs: Vec, +} + +impl LanguageExtractor for LanguageSpec { + fn file_globs(&self) -> &[String] { + &self.file_globs + } + + fn build_schema(&self) -> std::io::Result { + let effective_node_types: String = match self.desugarer.output_node_types_yaml() { + Some(yaml) => yeast::node_types_yaml::convert(yaml).map_err(|e| { + std::io::Error::other(format!( + "Failed to convert YAML node-types to JSON for {}: {e}", + self.prefix + )) + })?, + None => self.node_types.to_string(), + }; + node_types::read_node_types_str(self.prefix, &effective_node_types) + } + + fn extract_file( + &self, + schema: &NodeTypeMap, + diagnostics_writer: &mut diagnostics::LogWriter, + trap_writer: &mut trap::Writer, + path: &std::path::Path, + source: &[u8], + ) { + crate::extractor::extract_parsed( + self.parser.as_ref(), + self.prefix, + schema, + diagnostics_writer, + trap_writer, + None, + path, + source, + self.desugarer.as_ref(), + ); + } +} + +pub struct Extractor { + pub prefix: String, + pub languages: Vec, + pub trap_dir: PathBuf, + pub source_archive_dir: PathBuf, + pub file_lists: Vec, + // Typically constructed via `trap::Compression::from_env`. + // This allow us to report the error using our diagnostics system + // without exposing it to consumers. + pub trap_compression: Result, +} + +impl Extractor { + pub fn run(&self) -> std::io::Result<()> { + driver::run_extractor( + &self.prefix, + &self.languages, + &self.trap_dir, + &self.source_archive_dir, + &self.file_lists, + &self.trap_compression, + ) + } +} diff --git a/shared/tree-sitter-extractor/src/extractor/driver.rs b/shared/tree-sitter-extractor/src/extractor/driver.rs new file mode 100644 index 000000000000..d97d8f4c75c2 --- /dev/null +++ b/shared/tree-sitter-extractor/src/extractor/driver.rs @@ -0,0 +1,210 @@ +//! Shared multi-file extraction driver. +//! +//! The `simple` (direct tree-sitter) and `desugaring` (parse + desugar) +//! extractors differ only in how a language's schema is built and how a single +//! file is extracted. Everything else — threading, matching files to languages +//! by glob, writing TRAP, and copying into the source archive — is identical +//! and lives here, parameterised over the [`LanguageExtractor`] trait. + +use globset::{GlobBuilder, GlobSetBuilder}; +use rayon::prelude::*; +use std::fs::File; +use std::io::BufRead; +use std::path::{Path, PathBuf}; + +use crate::diagnostics; +use crate::file_paths; +use crate::node_types::NodeTypeMap; +use crate::trap; + +/// A language that [`run_extractor`] can process: it knows its file globs, its +/// TRAP schema, and how to extract a single file. Implemented by +/// [`super::simple::LanguageSpec`] (direct tree-sitter extraction) and +/// [`super::desugaring::LanguageSpec`] (parse into an AST and desugar it). +pub(crate) trait LanguageExtractor: Sync { + /// The file-name globs that select files for this language. + fn file_globs(&self) -> &[String]; + /// Build the TRAP node-type schema used to validate emitted tuples. + fn build_schema(&self) -> std::io::Result; + /// Extract a single file's `source` into `trap_writer`. + fn extract_file( + &self, + schema: &NodeTypeMap, + diagnostics_writer: &mut diagnostics::LogWriter, + trap_writer: &mut trap::Writer, + path: &Path, + source: &[u8], + ); +} + +/// Drive extraction over `languages` for every file listed in `file_lists`. +/// +/// Sets up the thread pool, builds a combined glob set, and for each input file +/// dispatches to the matching language's [`LanguageExtractor::extract_file`], +/// writing the resulting TRAP and a source-archive copy. +pub(crate) fn run_extractor( + prefix: &str, + languages: &[L], + trap_dir: &Path, + source_archive_dir: &Path, + file_lists: &[PathBuf], + trap_compression: &Result, +) -> std::io::Result<()> { + tracing::info!("Extraction started"); + let diagnostics = diagnostics::DiagnosticLoggers::new(prefix); + let mut main_thread_logger = diagnostics.logger(); + let num_threads = match crate::options::num_threads() { + Ok(num) => num, + Err(e) => { + main_thread_logger.write( + main_thread_logger + .new_entry("configuration-error", "Configuration error") + .message( + "{}; defaulting to 1 thread.", + &[diagnostics::MessageArg::Code(&e)], + ) + .severity(diagnostics::Severity::Warning), + ); + 1 + } + }; + tracing::info!( + "Using {} {}", + num_threads, + if num_threads == 1 { + "thread" + } else { + "threads" + } + ); + let trap_compression = match trap_compression { + Ok(x) => *x, + Err(e) => { + main_thread_logger.write( + main_thread_logger + .new_entry("configuration-error", "Configuration error") + .message("{}; using gzip.", &[diagnostics::MessageArg::Code(e)]) + .severity(diagnostics::Severity::Warning), + ); + trap::Compression::Gzip + } + }; + drop(main_thread_logger); + + rayon::ThreadPoolBuilder::new() + .num_threads(num_threads) + .build_global() + .unwrap(); + + let file_lists: Vec = file_lists + .iter() + .map(|file_list| { + File::open(file_list) + .unwrap_or_else(|_| panic!("Unable to open file list at {file_list:?}")) + }) + .collect(); + + let mut schemas = vec![]; + for lang in languages { + schemas.push(lang.build_schema()?); + } + + // Construct a single globset containing all language globs, + // and a mapping from glob index to language index. + let (globset, glob_language_mapping) = { + let mut builder = GlobSetBuilder::new(); + let mut glob_lang_mapping = vec![]; + for (i, lang) in languages.iter().enumerate() { + for glob_str in lang.file_globs() { + let glob = GlobBuilder::new(glob_str) + .literal_separator(true) + .build() + .expect("invalid glob"); + builder.add(glob); + glob_lang_mapping.push(i); + } + } + ( + builder.build().expect("failed to build globset"), + glob_lang_mapping, + ) + }; + + let path_transformer = file_paths::load_path_transformer()?; + + let lines: std::io::Result> = file_lists + .iter() + .flat_map(|file_list| std::io::BufReader::new(file_list).lines()) + .collect(); + let lines = lines?; + + lines + .par_iter() + .try_for_each(|line| { + let mut diagnostics_writer = diagnostics.logger(); + let path = PathBuf::from(line).canonicalize()?; + let src_archive_file = crate::file_paths::path_for( + source_archive_dir, + &path, + "", + path_transformer.as_ref(), + ); + let source = std::fs::read(&path)?; + let mut trap_writer = trap::Writer::new(); + + match path.file_name() { + None => { + tracing::error!(?path, "No file name found, skipping file."); + } + Some(filename) => { + let matches = globset.matches(filename); + if matches.is_empty() { + tracing::error!(?path, "No matching language found, skipping file."); + } else { + let mut languages_processed = vec![false; languages.len()]; + + for m in matches { + let i = glob_language_mapping[m]; + if languages_processed[i] { + continue; + } + languages_processed[i] = true; + let lang = &languages[i]; + + lang.extract_file( + &schemas[i], + &mut diagnostics_writer, + &mut trap_writer, + &path, + &source, + ); + std::fs::create_dir_all(src_archive_file.parent().unwrap())?; + std::fs::copy(&path, &src_archive_file)?; + write_trap(trap_dir, &path, &trap_writer, trap_compression)?; + } + } + } + } + Ok(()) as std::io::Result<()> + }) + .expect("failed to extract files"); + + let path = PathBuf::from("extras"); + let mut trap_writer = trap::Writer::new(); + crate::extractor::populate_empty_location(&mut trap_writer); + + let res = write_trap(trap_dir, &path, &trap_writer, trap_compression); + tracing::info!("Extraction complete"); + res +} + +fn write_trap( + trap_dir: &Path, + path: &Path, + trap_writer: &trap::Writer, + trap_compression: trap::Compression, +) -> std::io::Result<()> { + let trap_file = crate::file_paths::path_for(trap_dir, path, trap_compression.extension(), None); + std::fs::create_dir_all(trap_file.parent().unwrap())?; + trap_writer.write_to_file(&trap_file, trap_compression) +} diff --git a/shared/tree-sitter-extractor/src/extractor/mod.rs b/shared/tree-sitter-extractor/src/extractor/mod.rs index 474dc096a2a6..1a8b9e820164 100644 --- a/shared/tree-sitter-extractor/src/extractor/mod.rs +++ b/shared/tree-sitter-extractor/src/extractor/mod.rs @@ -16,6 +16,8 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tree_sitter::{Language, Node, Parser, Range, Tree}; +pub mod desugaring; +mod driver; pub mod simple; /// Trait abstracting over tree-sitter and yeast node types for extraction. @@ -294,6 +296,10 @@ pub fn location_label(writer: &mut trap::Writer, location: trap::Location) -> tr /// caller's responsibility, allowing it to be done once and shared across /// files. #[allow(clippy::too_many_arguments)] +/// Extract a file with a tree-sitter grammar, walking the parse tree directly. +/// Comments and other `extra` nodes are emitted inline as tokens. This is the +/// path for languages that don't desugar their syntax tree (and hence have no +/// `extra` side channel); desugaring languages use [`extract_parsed`] instead. pub fn extract( language: &Language, language_prefix: &str, @@ -304,7 +310,6 @@ pub fn extract( path: &Path, source: &[u8], ranges: &[Range], - desugarer: Option<&dyn yeast::Desugarer>, ) { let path_str = file_paths::normalize_and_transform_path(path, transformer); let source_root = std::env::current_dir() @@ -337,21 +342,173 @@ pub fn extract( schema, ); - if let Some(desugarer) = desugarer { - let ast = desugarer - .run_from_tree(&tree, source) - .unwrap_or_else(|e| panic!("Desugaring failed for {path_str}: {e}")); - traverse_yeast(&ast, &mut visitor); - // Comments and other `extra` nodes are not represented in the desugared - // AST, so recover them directly from the original parse tree. - traverse_extras(&tree, &mut visitor); - } else { - traverse(&tree, &mut visitor); - } + traverse(&tree, &mut visitor); parser.reset(); } +/// A source tree produced by a parser: the raw `yeast::Ast` (pre-desugaring) +/// plus side-channel `extra` tokens (comments and similar) that are not +/// attached to the AST proper. +pub struct ParsedTree { + pub ast: yeast::Ast, + pub extras: Vec, +} + +/// A piece of side-channel `extra` content (a comment or unexpected text) +/// recovered by a parser. `kind` is a language-defined id written verbatim into +/// the `_trivia_tokeninfo` table (the tree-sitter parser uses the +/// grammar's node kind id here; a custom parser supplies its own stable +/// enumeration). +pub struct ExtraToken { + pub kind: usize, + pub range: yeast::Range, + pub text: String, +} + +/// Build a parser closure that parses `source` with a tree-sitter `language`, +/// producing a [`ParsedTree`]: a `yeast::Ast` (via [`yeast::Ast::from_tree`]) +/// plus the `extra` nodes (comments and similar) recovered as side-channel +/// tokens. This lets a tree-sitter language plug into the same +/// `ParsedTree`-producing interface as a custom parser. +pub fn tree_sitter_parser( + language: tree_sitter::Language, +) -> impl Fn(&[u8]) -> Result + Send + Sync { + move |source: &[u8]| { + let mut parser = Parser::new(); + parser + .set_language(&language) + .map_err(|e| format!("failed to set tree-sitter language: {e}"))?; + let tree = parser + .parse(source, None) + .ok_or_else(|| "tree-sitter failed to parse".to_string())?; + let ast = yeast::Ast::from_tree_with_schema_and_source( + yeast::schema::from_language(&language), + &tree, + &language, + source.to_vec(), + ); + let mut extras = Vec::new(); + collect_extras(tree.root_node(), source, &mut extras); + Ok(ParsedTree { ast, extras }) + } +} + +/// Collect `extra` nodes (comments and similar) under `node` into `out` as +/// [`ExtraToken`]s, keyed by the grammar's node kind id, for a desugaring +/// language whose rewritten AST won't retain them. +fn collect_extras(node: Node<'_>, source: &[u8], out: &mut Vec) { + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + if child.is_extra() { + let text = String::from_utf8_lossy(&source[child.byte_range()]).into_owned(); + out.push(ExtraToken { + kind: child.kind_id() as usize, + range: child.range().into(), + text, + }); + } else { + collect_extras(child, source, out); + } + } +} + +/// Extract a file from a [`ParsedTree`]-producing parser that desugars. The +/// parser yields a `yeast::Ast` plus side-channel `extra` tokens; the AST is +/// rewritten through `desugarer` ([`yeast::Desugarer::run_from_ast`]) before +/// TRAP extraction, and the `extra` tokens (comments and similar, which the +/// desugared AST does not carry) are emitted from the side channel. Both +/// tree-sitter grammars (via [`tree_sitter_parser`]) and custom parsers plug in +/// here; languages that don't desugar use [`extract`] instead. +#[allow(clippy::too_many_arguments)] +pub fn extract_parsed( + parse: &(dyn Fn(&[u8]) -> Result + Send + Sync), + language_prefix: &str, + schema: &NodeTypeMap, + diagnostics_writer: &mut diagnostics::LogWriter, + trap_writer: &mut trap::Writer, + transformer: Option<&file_paths::PathTransformer>, + path: &Path, + source: &[u8], + desugarer: &dyn yeast::Desugarer, +) { + let path_str = file_paths::normalize_and_transform_path(path, transformer); + let source_root = std::env::current_dir() + .ok() + .and_then(|d| d.canonicalize().ok()); + let diagnostics_path = file_paths::relativize_for_diagnostic(path, source_root.as_deref()); + let span = tracing::span!(tracing::Level::TRACE, "extract", file = %path_str); + let _enter = span.enter(); + tracing::debug!("extracting: {}", path_str); + + trap_writer.comment(format!("Auto-generated TRAP file for {path_str}")); + let file_label = populate_file(trap_writer, path, transformer); + let mut visitor = Visitor::new( + source, + diagnostics_writer, + trap_writer, + &diagnostics_path, + file_label, + language_prefix, + schema, + ); + + let parsed = parse(source).unwrap_or_else(|e| panic!("Parsing failed for {path_str}: {e}")); + let ast = desugarer + .run_from_ast(parsed.ast) + .unwrap_or_else(|e| panic!("Desugaring failed for {path_str}: {e}")); + traverse_yeast(&ast, &mut visitor); + // Comments and other `extra` tokens are not part of the desugared AST; emit + // them directly from the parser's side channel. + for extra in &parsed.extras { + visitor.emit_extra(extra); + } +} + +/// A lightweight [`AstNode`] over a piece of side-channel `extra` content +/// recovered by a custom parser, so it can reuse the tree-sitter location +/// machinery. +struct ExtraNode { + range: yeast::Range, + text: String, +} + +impl AstNode for ExtraNode { + fn kind(&self) -> &str { + "extra" + } + fn is_named(&self) -> bool { + false + } + fn is_missing(&self) -> bool { + false + } + fn is_error(&self) -> bool { + false + } + fn is_extra(&self) -> bool { + true + } + fn start_position(&self) -> tree_sitter::Point { + tree_sitter::Point { + row: self.range.start_point.row, + column: self.range.start_point.column, + } + } + fn end_position(&self) -> tree_sitter::Point { + tree_sitter::Point { + row: self.range.end_point.row, + column: self.range.end_point.column, + } + } + fn byte_range(&self) -> std::ops::Range { + self.range.start_byte..self.range.end_byte + } + fn opt_string_content(&self) -> Option { + Some(self.text.clone()) + } +} + struct ChildNode { field_name: Option<&'static str>, label: trap::Label, @@ -415,10 +572,11 @@ impl<'a> Visitor<'a> { } } - /// Emits a `TriviaToken` for the given `extra` node (e.g. a comment) from - /// the original parse tree. Trivia tokens carry a location and their source - /// text, but are not attached to a parent in the (possibly desugared) AST. - fn emit_trivia_token(&mut self, node: &Node) { + /// Emit an `extra` token (a location row plus a `trivia_tokeninfo` row) for + /// any [`AstNode`], with an explicit `kind` id. Shared by the tree-sitter + /// path (kind = the grammar's node kind id) and the custom-parser path + /// (kind = a language-defined `extra` kind id). + fn emit_extra_from(&mut self, node: &N, kind: usize) { let id = self.trap_writer.fresh_id(); let loc = location_for(self, self.file_label, node); let loc_label = location_label(self.trap_writer, loc); @@ -430,12 +588,21 @@ impl<'a> Visitor<'a> { &self.trivia_tokeninfo_table_name, vec![ trap::Arg::Label(id), - trap::Arg::Int(node.kind_id() as usize), + trap::Arg::Int(kind), sliced_source_arg(self.source, node), ], ); } + /// Emit an `extra` token recovered from a parser's side channel. + fn emit_extra(&mut self, extra: &ExtraToken) { + let node = ExtraNode { + range: extra.range, + text: extra.text.clone(), + }; + self.emit_extra_from(&node, extra.kind); + } + fn record_parse_error(&mut self, loc: trap::Label, mesg: &diagnostics::DiagnosticMessage) { self.diagnostics_writer.write(mesg); let id = self.trap_writer.fresh_id(); @@ -871,24 +1038,6 @@ fn traverse(tree: &Tree, visitor: &mut Visitor) { } } -/// Walks the original tree-sitter tree and emits a `TriviaToken` for every -/// `extra` node (e.g. a comment). Used to preserve comments that would -/// otherwise be lost after a desugaring pass rewrites the tree. -fn traverse_extras(tree: &Tree, visitor: &mut Visitor) { - emit_extras_in(visitor, tree.root_node()); -} - -fn emit_extras_in(visitor: &mut Visitor, node: Node<'_>) { - let mut cursor = node.walk(); - for child in node.children(&mut cursor) { - if child.is_extra() { - visitor.emit_trivia_token(&child); - } else { - emit_extras_in(visitor, child); - } - } -} - fn traverse_yeast(tree: &yeast::Ast, visitor: &mut Visitor) { let mut cursor = tree.walk(); visitor.enter_node(cursor.node()); diff --git a/shared/tree-sitter-extractor/src/extractor/simple.rs b/shared/tree-sitter-extractor/src/extractor/simple.rs index 9ba6f21778cf..1c6691fa8cf3 100644 --- a/shared/tree-sitter-extractor/src/extractor/simple.rs +++ b/shared/tree-sitter-extractor/src/extractor/simple.rs @@ -1,29 +1,52 @@ -use crate::{file_paths, trap}; -use globset::{GlobBuilder, GlobSetBuilder}; -use rayon::prelude::*; -use std::fs::File; -use std::io::BufRead; -use std::path::{Path, PathBuf}; +use crate::trap; +use std::path::PathBuf; use crate::diagnostics; -use crate::node_types; -use yeast; +use crate::extractor::driver::{self, LanguageExtractor}; +use crate::node_types::{self, NodeTypeMap}; +/// A tree-sitter language extracted directly from its parse tree, with no +/// desugaring. Comments and other `extra` nodes are emitted inline as tokens. +/// Languages that rewrite their syntax tree use +/// [`crate::extractor::desugaring`] instead. pub struct LanguageSpec { pub prefix: &'static str, pub ts_language: tree_sitter::Language, pub node_types: &'static str, - /// Optional desugarer. When set, the parsed tree is rewritten through - /// the desugarer before TRAP extraction. The desugarer's - /// `output_node_types_yaml()` (if set) provides the schema used both - /// at runtime (for the rewriter) and for TRAP validation. - /// - /// `Box` so the shared extractor is agnostic to - /// the user-defined context type the desugarer uses internally. - pub desugar: Option>, pub file_globs: Vec, } +impl LanguageExtractor for LanguageSpec { + fn file_globs(&self) -> &[String] { + &self.file_globs + } + + fn build_schema(&self) -> std::io::Result { + node_types::read_node_types_str(self.prefix, self.node_types) + } + + fn extract_file( + &self, + schema: &NodeTypeMap, + diagnostics_writer: &mut diagnostics::LogWriter, + trap_writer: &mut trap::Writer, + path: &std::path::Path, + source: &[u8], + ) { + crate::extractor::extract( + &self.ts_language, + self.prefix, + schema, + diagnostics_writer, + trap_writer, + None, + path, + source, + &[], + ); + } +} + pub struct Extractor { pub prefix: String, pub languages: Vec, @@ -38,182 +61,13 @@ pub struct Extractor { impl Extractor { pub fn run(&self) -> std::io::Result<()> { - tracing::info!("Extraction started"); - let diagnostics = diagnostics::DiagnosticLoggers::new(&self.prefix); - let mut main_thread_logger = diagnostics.logger(); - let num_threads = match crate::options::num_threads() { - Ok(num) => num, - Err(e) => { - main_thread_logger.write( - main_thread_logger - .new_entry("configuration-error", "Configuration error") - .message( - "{}; defaulting to 1 thread.", - &[diagnostics::MessageArg::Code(&e)], - ) - .severity(diagnostics::Severity::Warning), - ); - 1 - } - }; - tracing::info!( - "Using {} {}", - num_threads, - if num_threads == 1 { - "thread" - } else { - "threads" - } - ); - let trap_compression = match &self.trap_compression { - Ok(x) => *x, - Err(e) => { - main_thread_logger.write( - main_thread_logger - .new_entry("configuration-error", "Configuration error") - .message("{}; using gzip.", &[diagnostics::MessageArg::Code(e)]) - .severity(diagnostics::Severity::Warning), - ); - trap::Compression::Gzip - } - }; - drop(main_thread_logger); - - rayon::ThreadPoolBuilder::new() - .num_threads(num_threads) - .build_global() - .unwrap(); - - let file_lists: Vec = self - .file_lists - .iter() - .map(|file_list| { - File::open(file_list) - .unwrap_or_else(|_| panic!("Unable to open file list at {file_list:?}")) - }) - .collect(); - - let mut schemas = vec![]; - for lang in &self.languages { - let effective_node_types: String = match lang - .desugar - .as_ref() - .and_then(|d| d.output_node_types_yaml()) - { - Some(yaml) => yeast::node_types_yaml::convert(yaml).map_err(|e| { - std::io::Error::other(format!( - "Failed to convert YAML node-types to JSON for {}: {e}", - lang.prefix - )) - })?, - None => lang.node_types.to_string(), - }; - let schema = node_types::read_node_types_str(lang.prefix, &effective_node_types)?; - schemas.push(schema); - } - - // Construct a single globset containing all language globs, - // and a mapping from glob index to language index. - let (globset, glob_language_mapping) = { - let mut builder = GlobSetBuilder::new(); - let mut glob_lang_mapping = vec![]; - for (i, lang) in self.languages.iter().enumerate() { - for glob_str in &lang.file_globs { - let glob = GlobBuilder::new(glob_str) - .literal_separator(true) - .build() - .expect("invalid glob"); - builder.add(glob); - glob_lang_mapping.push(i); - } - } - ( - builder.build().expect("failed to build globset"), - glob_lang_mapping, - ) - }; - - let path_transformer = file_paths::load_path_transformer()?; - - let lines: std::io::Result> = file_lists - .iter() - .flat_map(|file_list| std::io::BufReader::new(file_list).lines()) - .collect(); - let lines = lines?; - - lines - .par_iter() - .try_for_each(|line| { - let mut diagnostics_writer = diagnostics.logger(); - let path = PathBuf::from(line).canonicalize()?; - let src_archive_file = crate::file_paths::path_for( - &self.source_archive_dir, - &path, - "", - path_transformer.as_ref(), - ); - let source = std::fs::read(&path)?; - let mut trap_writer = trap::Writer::new(); - - match path.file_name() { - None => { - tracing::error!(?path, "No file name found, skipping file."); - } - Some(filename) => { - let matches = globset.matches(filename); - if matches.is_empty() { - tracing::error!(?path, "No matching language found, skipping file."); - } else { - let mut languages_processed = vec![false; self.languages.len()]; - - for m in matches { - let i = glob_language_mapping[m]; - if languages_processed[i] { - continue; - } - languages_processed[i] = true; - let lang = &self.languages[i]; - - crate::extractor::extract( - &lang.ts_language, - lang.prefix, - &schemas[i], - &mut diagnostics_writer, - &mut trap_writer, - None, - &path, - &source, - &[], - lang.desugar.as_deref(), - ); - std::fs::create_dir_all(src_archive_file.parent().unwrap())?; - std::fs::copy(&path, &src_archive_file)?; - write_trap(&self.trap_dir, &path, &trap_writer, trap_compression)?; - } - } - } - } - Ok(()) as std::io::Result<()> - }) - .expect("failed to extract files"); - - let path = PathBuf::from("extras"); - let mut trap_writer = trap::Writer::new(); - crate::extractor::populate_empty_location(&mut trap_writer); - - let res = write_trap(&self.trap_dir, &path, &trap_writer, trap_compression); - tracing::info!("Extraction complete"); - res + driver::run_extractor( + &self.prefix, + &self.languages, + &self.trap_dir, + &self.source_archive_dir, + &self.file_lists, + &self.trap_compression, + ) } } - -fn write_trap( - trap_dir: &Path, - path: &Path, - trap_writer: &trap::Writer, - trap_compression: trap::Compression, -) -> std::io::Result<()> { - let trap_file = crate::file_paths::path_for(trap_dir, path, trap_compression.extension(), None); - std::fs::create_dir_all(trap_file.parent().unwrap())?; - trap_writer.write_to_file(&trap_file, trap_compression) -} diff --git a/shared/tree-sitter-extractor/tests/integration_test.rs b/shared/tree-sitter-extractor/tests/integration_test.rs index 694eb526f394..2b243ff7945b 100644 --- a/shared/tree-sitter-extractor/tests/integration_test.rs +++ b/shared/tree-sitter-extractor/tests/integration_test.rs @@ -13,7 +13,6 @@ fn simple_extractor() { prefix: "ql", ts_language: tree_sitter_ql::LANGUAGE.into(), node_types: tree_sitter_ql::NODE_TYPES, - desugar: None, file_globs: vec!["*.qll".into()], }; diff --git a/shared/tree-sitter-extractor/tests/multiple_languages.rs b/shared/tree-sitter-extractor/tests/multiple_languages.rs index e345eec58280..2e45e56754a3 100644 --- a/shared/tree-sitter-extractor/tests/multiple_languages.rs +++ b/shared/tree-sitter-extractor/tests/multiple_languages.rs @@ -13,14 +13,12 @@ fn multiple_language_extractor() { prefix: "ql", ts_language: tree_sitter_ql::LANGUAGE.into(), node_types: tree_sitter_ql::NODE_TYPES, - desugar: None, file_globs: vec!["*.qll".into()], }; let lang_json = simple::LanguageSpec { prefix: "json", ts_language: tree_sitter_json::LANGUAGE.into(), node_types: tree_sitter_json::NODE_TYPES, - desugar: None, file_globs: vec!["*.json".into(), "*Jsonfile".into()], }; diff --git a/unified/extractor/src/extractor.rs b/unified/extractor/src/extractor.rs index 301c6cf533f4..82bfe81219b5 100644 --- a/unified/extractor/src/extractor.rs +++ b/unified/extractor/src/extractor.rs @@ -2,7 +2,7 @@ use clap::Args; use std::path::PathBuf; use crate::languages; -use codeql_extractor::extractor::simple; +use codeql_extractor::extractor::desugaring; use codeql_extractor::trap; #[derive(Args)] @@ -31,7 +31,7 @@ pub fn run(options: Options) -> std::io::Result<()> { lang.prefix = "unified"; } - let extractor = simple::Extractor { + let extractor = desugaring::Extractor { prefix: "unified".to_string(), languages, trap_dir: options.output_dir, diff --git a/unified/extractor/src/languages/mod.rs b/unified/extractor/src/languages/mod.rs index 52a1bd40ffc7..a1f5c88cf3aa 100644 --- a/unified/extractor/src/languages/mod.rs +++ b/unified/extractor/src/languages/mod.rs @@ -1,4 +1,4 @@ -use codeql_extractor::extractor::simple; +use codeql_extractor::extractor::desugaring; #[path = "swift/swift.rs"] mod swift; @@ -15,6 +15,6 @@ pub mod swift_adapter; /// Shared YEAST output AST schema for all languages. pub(crate) const OUTPUT_AST_SCHEMA: &str = include_str!("../../ast_types.yml"); -pub fn all_language_specs() -> Vec { +pub fn all_language_specs() -> Vec { vec![swift::language_spec(OUTPUT_AST_SCHEMA)] } diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 13f1a6beadf0..991e95aaa6d7 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -1,4 +1,4 @@ -use codeql_extractor::extractor::simple; +use codeql_extractor::extractor::desugaring; use yeast::{ConcreteDesugarer, DesugaringConfig, PhaseKind, Rule, rule, tree}; /// User context propagated from outer rules down to the inner rules that @@ -1249,18 +1249,18 @@ fn translation_rules() -> Vec> { ] } -pub fn language_spec(desugared_ast_schema: &'static str) -> simple::LanguageSpec { +pub fn language_spec(desugared_ast_schema: &'static str) -> desugaring::LanguageSpec { let ts_language: tree_sitter::Language = tree_sitter_swift::LANGUAGE.into(); let config = DesugaringConfig::::new() .add_phase("translate", PhaseKind::OneShot, translation_rules()) .with_output_node_types_yaml(desugared_ast_schema); let desugarer = ConcreteDesugarer::new(ts_language.clone(), config) .expect("failed to build Swift desugarer"); - simple::LanguageSpec { + desugaring::LanguageSpec { prefix: "swift", - ts_language, + parser: Box::new(codeql_extractor::extractor::tree_sitter_parser(ts_language)), node_types: tree_sitter_swift::NODE_TYPES, file_globs: vec!["*.swift".into(), "*.swiftinterface".into()], - desugar: Some(Box::new(desugarer)), + desugarer: Box::new(desugarer), } } diff --git a/unified/extractor/tests/corpus_tests.rs b/unified/extractor/tests/corpus_tests.rs index ac93f06622cf..d192b19485e7 100644 --- a/unified/extractor/tests/corpus_tests.rs +++ b/unified/extractor/tests/corpus_tests.rs @@ -1,8 +1,8 @@ use std::fs; use std::path::Path; -use codeql_extractor::extractor::simple; -use yeast::{Runner, dump::dump_ast, dump::dump_ast_with_type_errors}; +use codeql_extractor::extractor::desugaring; +use yeast::{dump::dump_ast, dump::dump_ast_with_type_errors}; #[path = "../src/languages/mod.rs"] mod languages; @@ -59,40 +59,21 @@ fn render_corpus(case: &CorpusCase) -> String { ) } -fn run_desugaring(lang: &simple::LanguageSpec, input: &str) -> Result { - match lang.desugar.as_deref() { - Some(desugarer) => { - // Parse the input ourselves so we don't depend on the desugarer - // knowing about the language. - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(&lang.ts_language) - .map_err(|e| format!("Failed to set language: {e}"))?; - let tree = parser - .parse(input, None) - .ok_or_else(|| "Failed to parse input".to_string())?; - desugarer - .run_from_tree(&tree, input.as_bytes()) - .map_err(|e| format!("Desugaring failed: {e}")) - } - None => { - let runner: Runner = Runner::new(lang.ts_language.clone(), &[]); - runner - .run(input) - .map_err(|e| format!("Failed to parse input: {e}")) - } - } +/// Parse `input` through the language's parser and desugar it, returning the +/// mapped AST. +fn run_desugaring(lang: &desugaring::LanguageSpec, input: &str) -> Result { + let parsed = (lang.parser)(input.as_bytes())?; + lang.desugarer + .run_from_ast(parsed.ast) + .map_err(|e| format!("Desugaring failed: {e}")) } -/// Produce the raw tree-sitter parse tree dump for `input`, with no -/// desugaring rules applied. Uses a `Runner` with an empty phase list and -/// the input grammar's own schema. -fn dump_raw_parse(lang: &simple::LanguageSpec, input: &str) -> Result { - let runner: Runner = Runner::new(lang.ts_language.clone(), &[]); - let ast = runner - .run(input) - .map_err(|e| format!("Failed to parse input: {e}"))?; - Ok(dump_ast(&ast, ast.get_root(), input)) +/// Produce the raw (pre-desugar) parse tree dump for `input` — the `yeast::Ast` +/// the language's parser builds, before any desugaring rules. Useful for seeing +/// what the mapping rules operate on. +fn dump_raw_parse(lang: &desugaring::LanguageSpec, input: &str) -> Result { + let parsed = (lang.parser)(input.as_bytes())?; + Ok(dump_ast(&parsed.ast, parsed.ast.get_root(), input)) } /// Collect the set of corpus test "stems" (paths without an extension) under @@ -122,11 +103,8 @@ fn test_corpus() { let corpus_dir = Path::new("tests/corpus"); for lang in all_languages { - let output_schema = yeast::node_types_yaml::schema_from_yaml_with_language( - languages::OUTPUT_AST_SCHEMA, - &lang.ts_language, - ) - .expect("Failed to parse OUTPUT_AST_SCHEMA YAML"); + let output_schema = yeast::node_types_yaml::schema_from_yaml(languages::OUTPUT_AST_SCHEMA) + .expect("Failed to parse OUTPUT_AST_SCHEMA YAML"); let lang_corpus_dir = corpus_dir.join(&lang.prefix); if !lang_corpus_dir.exists() { @@ -236,8 +214,7 @@ fn test_corpus() { ); if update_mode { case.expected = actual_dump.trim().to_string(); - } else if output_path.exists() - && case.expected.trim() != actual_dump.trim() + } else if output_path.exists() && case.expected.trim() != actual_dump.trim() { failures.push(format!( "Test failed in {}:\nEXPECTED:\n\n{}\n\nACTUAL:\n\n{}", diff --git a/unified/extractor/tests/swift_syntax_pipeline.rs b/unified/extractor/tests/swift_syntax_pipeline.rs index cdaae1f47c6e..fde060f98203 100644 --- a/unified/extractor/tests/swift_syntax_pipeline.rs +++ b/unified/extractor/tests/swift_syntax_pipeline.rs @@ -22,7 +22,7 @@ fn swift_syntax_json_runs_through_the_desugarer() { .into_iter() .find(|l| l.file_globs.iter().any(|g| g.contains("swift"))) .expect("swift language spec"); - let desugarer = lang.desugar.as_deref().expect("swift desugarer"); + let desugarer = lang.desugarer.as_ref(); // Adapt the swift-syntax JSON into a yeast AST (pure Rust, no Swift FFI). let adapted = From 79954ce19ee7d25d76e780f14f5d445c90c9dfa5 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 17 Jul 2026 13:37:49 +0000 Subject: [PATCH 05/27] unified: Add the swift-syntax parser and unresolved operator sequence (dormant) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the plumbing for the swift-syntax front-end, without yet switching the runtime over to it (the Swift front-end still parses with tree-sitter): - `languages/swift/parse.rs` shells out to the separate `swift-syntax-parse` binary and adapts its JSON into a `yeast::Ast` (plus side-channel `extra` tokens) via `swift_adapter`. Running the parser out-of-process keeps the Swift toolchain out of the extractor's own build. It is wired in as a module but left `allow(dead_code)` until the runtime uses it. - `swift_node_types.yml` is the authoritative swift-syntax input schema (generated from swift-syntax by a one-off tool). The adapter seeds every parse with it, pre-registering every input kind and field so that rule matching never references a name absent from a given file's tree. The adapter now emits `ExtraToken`s directly during its single traversal, so `parse.rs` hands the parsed tree straight through with no second pass. - `ast_types.yml` gains an `unresolved_operator_sequence` type (with an `expr_or_operator` union) for flat operator chains the parser can't resolve — e.g. a chain using an operator imported from another module, whose precedence is unknown. Nothing produces it yet; the mapping rules that do are added when the rules are ported. The dbscheme and QL library are regenerated to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/extractor/ast_types.yml | 19 + unified/extractor/src/languages/mod.rs | 11 + .../extractor/src/languages/swift/adapter.rs | 95 +- .../extractor/src/languages/swift/parse.rs | 73 + unified/extractor/swift_node_types.yml | 1281 +++++++++++++++++ unified/ql/lib/codeql/unified/Ast.qll | 20 + unified/ql/lib/unified.dbscheme | 17 +- 7 files changed, 1473 insertions(+), 43 deletions(-) create mode 100644 unified/extractor/src/languages/swift/parse.rs create mode 100644 unified/extractor/swift_node_types.yml diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index 418772aa2680..4fa1ff169428 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -31,6 +31,7 @@ supertypes: - throw_expr - try_expr - switch_expr + - unresolved_operator_sequence - unsupported_node expr_or_pattern: - expr @@ -38,6 +39,11 @@ supertypes: expr_or_type: - expr - type_expr + # An element of an `unresolved_operator_sequence`: either an operand (`expr`) + # or one of the infix operators separating the operands. + expr_or_operator: + - expr + - infix_operator pattern: - name_pattern - tuple_pattern @@ -137,6 +143,19 @@ named: operand: expr operator: operator + # A flat, unresolved operator sequence such as `a <+> b <+> c`. + # + # Swift's grammar doesn't encode operator precedence, so an operator chain is + # first parsed as a flat list of operands and operators. The parser front-end + # resolves this into structured `binary_expr` trees when it knows the + # operators' precedence (standard-library operators, and operators declared in + # the same file). When it encounters an operator whose precedence it can't + # determine (e.g. one imported from another module), it leaves that chain + # unresolved and emits it here rather than guessing a (possibly wrong) + # structure. The `element`s alternate operands (`expr`) and infix operators. + unresolved_operator_sequence: + element*: expr_or_operator + # Plain assignment assign_expr: target: expr_or_pattern diff --git a/unified/extractor/src/languages/mod.rs b/unified/extractor/src/languages/mod.rs index a1f5c88cf3aa..032bc884ca34 100644 --- a/unified/extractor/src/languages/mod.rs +++ b/unified/extractor/src/languages/mod.rs @@ -12,6 +12,17 @@ mod swift; #[allow(dead_code)] pub mod swift_adapter; +/// Swift front-end parser: shells out to `swift-syntax-parse` and adapts its +/// JSON output via [`swift_adapter`]. +/// +/// Dormant for now: the runtime Swift front-end is still tree-sitter, so +/// nothing in the binary calls this yet. `allow(dead_code)` for the same +/// binary-crate reason as [`swift_adapter`]; both allows are removed once the +/// runtime switches the Swift front-end to swift-syntax. +#[path = "swift/parse.rs"] +#[allow(dead_code)] +pub mod swift_parse; + /// Shared YEAST output AST schema for all languages. pub(crate) const OUTPUT_AST_SCHEMA: &str = include_str!("../../ast_types.yml"); diff --git a/unified/extractor/src/languages/swift/adapter.rs b/unified/extractor/src/languages/swift/adapter.rs index 37d5aac00d2d..696fce889708 100644 --- a/unified/extractor/src/languages/swift/adapter.rs +++ b/unified/extractor/src/languages/swift/adapter.rs @@ -2,9 +2,8 @@ //! in-memory format the CodeQL desugaring rules operate on. //! //! The JSON tree is produced by the `swift-syntax-rs` crate's Swift FFI shim -//! (`parse_to_json`). This module is pure Rust (only `yeast` + `serde_json`), -//! so the extractor consumes swift-syntax output without pulling in the Swift -//! toolchain (the JSON is produced out-of-process). +//! (`parse_to_json`). This module needs no Swift toolchain, so the extractor +//! consumes swift-syntax output out-of-process. //! //! The mapping mirrors tree-sitter's node model, which is what yeast (and the //! extractor's rewrite rules) expect: @@ -24,31 +23,19 @@ use std::collections::BTreeMap; +use codeql_extractor::extractor::ExtraToken; use serde_json::Value; -use yeast::schema::Schema; use yeast::{Ast, Id, NodeContent, Point, Range}; -/// A comment (or `unexpectedText`) recovered from the syntax tree's trivia. +/// The result of adapting a swift-syntax JSON tree: the [`yeast::Ast`] plus the +/// comment/`unexpectedText` [`ExtraToken`]s harvested from it (in source order). /// -/// These are collected into a side channel rather than embedded in the -/// [`yeast::Ast`], mirroring how the extractor treats tree-sitter `extra` +/// The extra tokens are collected into a side channel rather than embedded in +/// the [`yeast::Ast`], mirroring how the extractor treats tree-sitter `extra` /// nodes: they carry a location and text but are not attached to a parent. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TriviaToken { - /// The trivia kind (e.g. `lineComment`, `blockComment`, `docLineComment`, - /// `docBlockComment`, `unexpectedText`). - pub kind: String, - /// The verbatim source text of the piece (e.g. `// comment`). - pub text: String, - /// The source range the piece occupies. - pub range: Range, -} - -/// The result of adapting a swift-syntax JSON tree: the [`yeast::Ast`] plus the -/// comment/`unexpectedText` trivia harvested from it (in source order). pub struct AdaptedTree { pub ast: Ast, - pub trivia: Vec, + pub extras: Vec, } /// swift-syntax `TokenKind` cases whose text is *not* determined by the kind @@ -170,17 +157,18 @@ fn children_of(value: &Value) -> Vec<&Value> { /// in the schema on the fly, immediately before the node is created. Children /// are built first so a parent's field lists reference existing ids. Any /// comment/`unexpectedText` trivia carried by a token is harvested into -/// `trivia` during the same pass rather than embedded in the tree. -fn build(node: &Value, ast: &mut Ast, trivia: &mut Vec) -> Result { +/// `extras` (as [`ExtraToken`]s) during the same pass rather than embedded in +/// the tree. +fn build(node: &Value, ast: &mut Ast, extras: &mut Vec) -> Result { let info = classify(node)?; - collect_trivia(node, trivia); + collect_extras(node, extras); let mut fields: BTreeMap> = BTreeMap::new(); for (field, value) in field_entries(node) { let field_id = ast.register_field(field); let mut ids = Vec::new(); for child in children_of(value) { - ids.push(build(child, ast, trivia)?); + ids.push(build(child, ast, extras)?); } fields.insert(field_id, ids); } @@ -201,9 +189,10 @@ fn build(node: &Value, ast: &mut Ast, trivia: &mut Vec) -> Result) { +/// filtered to comments/`unexpectedText` upstream) into `out` as +/// [`ExtraToken`]s. Non-token nodes have no trivia keys, so this is a no-op for +/// them. +fn collect_extras(node: &Value, out: &mut Vec) { for key in ["leadingTrivia", "trailingTrivia"] { let Some(Value::Array(pieces)) = node.get(key) else { continue; @@ -220,8 +209,8 @@ fn collect_trivia(node: &Value, out: &mut Vec) { .and_then(Value::as_str) .unwrap_or("") .to_string(); - out.push(TriviaToken { - kind: kind.to_string(), + out.push(ExtraToken { + kind: trivia_kind_id(kind), text, range, }); @@ -229,6 +218,21 @@ fn collect_trivia(node: &Value, out: &mut Vec) { } } +/// Map a swift-syntax trivia kind name to the stable integer id stored in an +/// [`ExtraToken`]'s `kind` (and written to the `unified_trivia_tokeninfo` +/// table). The value is opaque to the QL library (which reads only the text), +/// but is kept stable and meaningful. +fn trivia_kind_id(kind: &str) -> usize { + match kind { + "lineComment" => 1, + "blockComment" => 2, + "docLineComment" => 3, + "docBlockComment" => 4, + "unexpectedText" => 5, + _ => 0, + } +} + /// Parse a node's `range` into a [`yeast::Range`]. /// /// The JSON carries, for `start` and `end`, a 0-based UTF-8 file byte `offset`, @@ -258,21 +262,29 @@ fn parse_range(node: &Value) -> Option { }) } +/// The authoritative swift-syntax input node-types schema, generated from +/// swift-syntax (see the schemagen tool). [`json_to_ast`] seeds every parse +/// with the schema built from this, pre-registering every input kind and field +/// so rule matching never references a name absent from a given file's tree. +const SWIFT_NODE_TYPES: &str = include_str!("../../../swift_node_types.yml"); + /// Convert a swift-syntax JSON tree (as produced by [`crate::parse_to_json`]) /// into a [`yeast::Ast`] plus the comment/`unexpectedText` trivia harvested -/// from it. Both are produced in a single traversal. +/// from it. Both are produced in a single traversal. The AST is seeded with the +/// authoritative swift-syntax schema ([`SWIFT_NODE_TYPES`]); the adapter only +/// ever consumes swift-syntax input, so the schema is not a parameter. pub fn json_to_ast(json: &str) -> Result { let root: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?; - let mut ast = Ast::with_schema(Schema::new()); - let mut trivia = Vec::new(); - let root_id = build(&root, &mut ast, &mut trivia)?; + let mut ast = Ast::with_schema(yeast::node_types_yaml::schema_from_yaml(SWIFT_NODE_TYPES)?); + let mut extras = Vec::new(); + let root_id = build(&root, &mut ast, &mut extras)?; ast.set_root(root_id); - // Emit trivia in source order (the traversal visits nodes bottom-up). - trivia.sort_by_key(|t| t.range.start_byte); + // Emit extras in source order (the traversal visits nodes bottom-up). + extras.sort_by_key(|t| t.range.start_byte); - Ok(AdaptedTree { ast, trivia }) + Ok(AdaptedTree { ast, extras }) } #[cfg(test)] @@ -373,7 +385,7 @@ mod tests { } #[test] - fn collects_trivia_into_side_channel() { + fn collects_extras_into_side_channel() { // A token carrying a trailing line comment in its trivia. let json = r#"{ "kind": "sourceFile", @@ -395,9 +407,10 @@ mod tests { let adapted = json_to_ast(json).expect("adapter should succeed"); // The comment is in the side channel, with its text and location. - assert_eq!(adapted.trivia.len(), 1); - let comment = &adapted.trivia[0]; - assert_eq!(comment.kind, "lineComment"); + assert_eq!(adapted.extras.len(), 1); + let comment = &adapted.extras[0]; + // `lineComment` maps to extra kind id 1. + assert_eq!(comment.kind, 1); assert_eq!(comment.text, "// c"); assert_eq!(comment.range.start_byte, 2); assert_eq!(comment.range.end_byte, 6); diff --git a/unified/extractor/src/languages/swift/parse.rs b/unified/extractor/src/languages/swift/parse.rs new file mode 100644 index 000000000000..21ef1b9ad215 --- /dev/null +++ b/unified/extractor/src/languages/swift/parse.rs @@ -0,0 +1,73 @@ +//! Swift front-end parser: shells out to the separate `swift-syntax-parse` +//! binary (which links swift-syntax) to obtain a JSON syntax tree, then adapts +//! that JSON into a `yeast::Ast` via the pure-Rust [`swift_adapter`] module. +//! +//! Running the parser in a separate process keeps the Swift toolchain out of +//! the extractor's own build: the extractor never links Swift, so working on +//! other (e.g. tree-sitter based) languages needs no Swift toolchain. Each call +//! spawns the parser afresh; a longer-lived parser process could be swapped in +//! behind this same seam later without touching the extraction pipeline. + +use std::io::Write; +use std::process::{Command, Stdio}; + +use codeql_extractor::extractor::ParsedTree; + +use super::swift_adapter; + +/// Environment variable naming the `swift-syntax-parse` executable. When unset, +/// `swift-syntax-parse` is looked up on `PATH`. +const PARSE_BIN_ENV: &str = "CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE"; + +/// Parse Swift `source` into a [`ParsedTree`] (a raw `yeast::Ast` plus +/// side-channel `extra` tokens), ready to be desugared via `run_from_ast`. +pub fn parse(source: &[u8]) -> Result { + let source = + std::str::from_utf8(source).map_err(|e| format!("Swift source is not valid UTF-8: {e}"))?; + let json = run_parser(source)?; + let mut adapted = swift_adapter::json_to_ast(&json)?; + adapted.ast.set_source(source.as_bytes().to_vec()); + Ok(ParsedTree { + ast: adapted.ast, + extras: adapted.extras, + }) +} + +/// The `swift-syntax-parse` executable to invoke. +fn parse_bin() -> String { + std::env::var(PARSE_BIN_ENV).unwrap_or_else(|_| "swift-syntax-parse".to_string()) +} + +/// Run the external parser, feeding `source` on stdin and returning its JSON +/// stdout. +fn run_parser(source: &str) -> Result { + let bin = parse_bin(); + let mut child = Command::new(&bin) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| format!("failed to spawn Swift parser `{bin}`: {e}"))?; + + // The parser reads all of stdin before writing any stdout, so writing the + // whole source and then closing stdin (by dropping it) cannot deadlock. + child + .stdin + .take() + .expect("child stdin was piped") + .write_all(source.as_bytes()) + .map_err(|e| format!("failed to write source to Swift parser `{bin}`: {e}"))?; + + let output = child + .wait_with_output() + .map_err(|e| format!("failed to run Swift parser `{bin}`: {e}"))?; + if !output.status.success() { + return Err(format!( + "Swift parser `{bin}` failed ({}): {}", + output.status, + String::from_utf8_lossy(&output.stderr).trim() + )); + } + String::from_utf8(output.stdout) + .map_err(|e| format!("Swift parser produced non-UTF-8 output: {e}")) +} diff --git a/unified/extractor/swift_node_types.yml b/unified/extractor/swift_node_types.yml new file mode 100644 index 000000000000..d8793acce2ca --- /dev/null +++ b/unified/extractor/swift_node_types.yml @@ -0,0 +1,1281 @@ +# GENERATED from swift-syntax by the one-off schemagen tool. Do not edit. +supertypes: + decl: + - accessorDecl + - actorDecl + - associatedTypeDecl + - classDecl + - deinitializerDecl + - editorPlaceholderDecl + - enumCaseDecl + - enumDecl + - extensionDecl + - functionDecl + - ifConfigDecl + - importDecl + - initializerDecl + - macroDecl + - macroExpansionDecl + - missingDecl + - operatorDecl + - poundSourceLocation + - precedenceGroupDecl + - protocolDecl + - structDecl + - subscriptDecl + - typeAliasDecl + - unexpectedCodeDecl + - usingDecl + - variableDecl + expr: + - _canImportExpr + - _canImportVersionInfo + - arrayExpr + - arrowExpr + - asExpr + - assignmentExpr + - awaitExpr + - binaryOperatorExpr + - booleanLiteralExpr + - borrowExpr + - closureExpr + - consumeExpr + - copyExpr + - declReferenceExpr + - dictionaryExpr + - discardAssignmentExpr + - doExpr + - editorPlaceholderExpr + - floatLiteralExpr + - forceUnwrapExpr + - functionCallExpr + - genericSpecializationExpr + - ifExpr + - inOutExpr + - infixOperatorExpr + - integerLiteralExpr + - isExpr + - keyPathExpr + - macroExpansionExpr + - memberAccessExpr + - missingExpr + - nilLiteralExpr + - optionalChainingExpr + - packElementExpr + - packExpansionExpr + - patternExpr + - postfixIfConfigExpr + - postfixOperatorExpr + - prefixOperatorExpr + - regexLiteralExpr + - sequenceExpr + - simpleStringLiteralExpr + - stringLiteralExpr + - subscriptCallExpr + - superExpr + - switchExpr + - ternaryExpr + - tryExpr + - tupleExpr + - typeExpr + - unresolvedAsExpr + - unresolvedIsExpr + - unresolvedTernaryExpr + - unsafeExpr + pattern: + - expressionPattern + - identifierPattern + - isTypePattern + - missingPattern + - tuplePattern + - valueBindingPattern + - wildcardPattern + stmt: + - breakStmt + - continueStmt + - deferStmt + - discardStmt + - doStmt + - expressionStmt + - fallThroughStmt + - forStmt + - guardStmt + - labeledStmt + - missingStmt + - repeatStmt + - returnStmt + - thenStmt + - throwStmt + - whileStmt + - yieldStmt + syntax: + - abiAttributeArguments + - accessorBlock + - accessorBlockFile + - accessorEffectSpecifiers + - accessorParameters + - arrayElement + - attribute + - attributeClauseFile + - availabilityArgument + - availabilityCondition + - availabilityLabeledArgument + - availabilityMacroDefinitionFile + - backDeployedAttributeArguments + - catchClause + - catchItem + - closureCapture + - closureCaptureClause + - closureCaptureSpecifier + - closureParameter + - closureParameterClause + - closureShorthandParameter + - closureSignature + - codeBlock + - codeBlockFile + - codeBlockItem + - compositionTypeElement + - conditionElement + - conformanceRequirement + - declModifier + - declModifierDetail + - declNameArgument + - declNameArguments + - deinitializerEffectSpecifiers + - derivativeAttributeArguments + - designatedType + - dictionaryElement + - differentiabilityArgument + - differentiabilityArguments + - differentiabilityWithRespectToArgument + - differentiableAttributeArguments + - documentationAttributeArgument + - dynamicReplacementAttributeArguments + - enumCaseElement + - enumCaseParameter + - enumCaseParameterClause + - expressionSegment + - functionEffectSpecifiers + - functionParameter + - functionParameterClause + - functionSignature + - genericArgument + - genericArgumentClause + - genericParameter + - genericParameterClause + - genericRequirement + - genericWhereClause + - ifConfigClause + - implementsAttributeArguments + - importPathComponent + - inheritanceClause + - inheritedType + - initializerClause + - keyPathComponent + - keyPathMethodComponent + - keyPathOptionalComponent + - keyPathPropertyComponent + - keyPathSubscriptComponent + - labeledExpr + - labeledSpecializeArgument + - layoutRequirement + - lifetimeSpecifierArgument + - lifetimeTypeSpecifier + - matchingPatternCondition + - memberBlock + - memberBlockItem + - memberBlockItemListFile + - missing + - moduleSelector + - multipleTrailingClosureElement + - nonisolatedSpecifierArgument + - nonisolatedTypeSpecifier + - objCSelectorPiece + - operatorPrecedenceAndTypes + - optionalBindingCondition + - originallyDefinedInAttributeArguments + - patternBinding + - platformVersion + - platformVersionItem + - poundSourceLocationArguments + - precedenceGroupAssignment + - precedenceGroupAssociativity + - precedenceGroupName + - precedenceGroupRelation + - primaryAssociatedType + - primaryAssociatedTypeClause + - returnClause + - sameTypeRequirement + - simpleTypeSpecifier + - sourceFile + - specializeAvailabilityArgument + - specializeTargetFunctionArgument + - specializedAttributeArgument + - stringSegment + - switchCase + - switchCaseItem + - switchCaseLabel + - switchDefaultLabel + - throwsClause + - tuplePatternElement + - tupleTypeElement + - typeAnnotation + - typeEffectSpecifiers + - typeInitializerClause + - versionComponent + - versionTuple + - whereClause + - yieldedExpression + - yieldedExpressionsClause + type: + - arrayType + - attributedType + - classRestrictionType + - compositionType + - dictionaryType + - functionType + - identifierType + - implicitlyUnwrappedOptionalType + - inlineArrayType + - memberType + - metatypeType + - missingType + - namedOpaqueReturnType + - optionalType + - packElementType + - packExpansionType + - someOrAnyType + - suppressedType + - tupleType +named: + _canImportExpr: + canImportKeyword: _token + leftParen: _token + importPath: _token + versionInfo?: _canImportVersionInfo + rightParen: _token + _canImportVersionInfo: + comma: _token + label: _token + colon: _token + version: versionTuple + abiAttributeArguments: + provider: [associatedTypeDecl, deinitializerDecl, enumCaseDecl, functionDecl, initializerDecl, missingDecl, subscriptDecl, typeAliasDecl, variableDecl] + accessorBlock: + leftBrace: _token + accessors: [accessorDecl, codeBlockItemList] + rightBrace: _token + accessorBlockFile: + leftBrace?: _token + accessors*: accessorDecl + rightBrace?: _token + endOfFileToken: _token + accessorDecl: + attributes*: [attribute, ifConfigDecl] + modifier?: declModifier + accessorSpecifier: _token + parameters?: accessorParameters + effectSpecifiers?: accessorEffectSpecifiers + body?: codeBlock + accessorEffectSpecifiers: + asyncSpecifier?: _token + throwsClause?: throwsClause + accessorParameters: + leftParen: _token + name: _token + rightParen: _token + actorDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + actorKeyword: _token + name: _token + genericParameterClause?: genericParameterClause + inheritanceClause?: inheritanceClause + genericWhereClause?: genericWhereClause + memberBlock: memberBlock + arrayElement: + expression: expr + trailingComma?: _token + arrayExpr: + leftSquare: _token + elements*: arrayElement + rightSquare: _token + arrayType: + leftSquare: _token + element: type + rightSquare: _token + arrowExpr: + effectSpecifiers?: typeEffectSpecifiers + arrow: _token + asExpr: + expression: expr + asKeyword: _token + questionOrExclamationMark?: _token + type: type + assignmentExpr: + equal: _token + associatedTypeDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + associatedtypeKeyword: _token + name: _token + inheritanceClause?: inheritanceClause + initializer?: typeInitializerClause + genericWhereClause?: genericWhereClause + attribute: + atSign: _token + attributeName: type + leftParen?: _token + arguments?: [labeledExprList, availabilityArgumentList, specializeAttributeArgumentList, specializedAttributeArgument, objCSelectorPieceList, implementsAttributeArguments, differentiableAttributeArguments, derivativeAttributeArguments, backDeployedAttributeArguments, originallyDefinedInAttributeArguments, dynamicReplacementAttributeArguments, effectsAttributeArgumentList, documentationAttributeArgumentList, abiAttributeArguments] + rightParen?: _token + attributeClauseFile: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + endOfFileToken: _token + attributedType: + specifiers*: [simpleTypeSpecifier, lifetimeTypeSpecifier, nonisolatedTypeSpecifier] + attributes*: [attribute, ifConfigDecl] + lateSpecifiers*: [simpleTypeSpecifier, lifetimeTypeSpecifier, nonisolatedTypeSpecifier] + baseType: type + availabilityArgument: + argument: [_token, platformVersion, availabilityLabeledArgument] + trailingComma?: _token + availabilityCondition: + availabilityKeyword: _token + leftParen: _token + availabilityArguments*: availabilityArgument + rightParen: _token + availabilityLabeledArgument: + label: _token + colon: _token + value: [simpleStringLiteralExpr, versionTuple] + availabilityMacroDefinitionFile: + platformVersion: platformVersion + colon: _token + specs*: availabilityArgument + endOfFileToken: _token + awaitExpr: + awaitKeyword: _token + expression: expr + backDeployedAttributeArguments: + beforeLabel: _token + colon: _token + platforms*: platformVersionItem + binaryOperatorExpr: + operator: _token + booleanLiteralExpr: + literal: _token + borrowExpr: + borrowKeyword: _token + expression: expr + breakStmt: + breakKeyword: _token + label?: _token + catchClause: + catchKeyword: _token + catchItems*: catchItem + body: codeBlock + catchItem: + pattern?: pattern + whereClause?: whereClause + trailingComma?: _token + classDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + classKeyword: _token + name: _token + genericParameterClause?: genericParameterClause + inheritanceClause?: inheritanceClause + genericWhereClause?: genericWhereClause + memberBlock: memberBlock + classRestrictionType: + classKeyword: _token + closureCapture: + specifier?: closureCaptureSpecifier + name: _token + initializer?: initializerClause + trailingComma?: _token + closureCaptureClause: + leftSquare: _token + items*: closureCapture + rightSquare: _token + closureCaptureSpecifier: + specifier: _token + leftParen?: _token + detail?: _token + rightParen?: _token + closureExpr: + leftBrace: _token + signature?: closureSignature + statements*: codeBlockItem + rightBrace: _token + closureParameter: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + firstName: _token + secondName?: _token + colon?: _token + type?: type + ellipsis?: _token + trailingComma?: _token + closureParameterClause: + leftParen: _token + parameters*: closureParameter + rightParen: _token + closureShorthandParameter: + name: _token + trailingComma?: _token + closureSignature: + attributes*: [attribute, ifConfigDecl] + capture?: closureCaptureClause + parameterClause?: [closureShorthandParameterList, closureParameterClause] + effectSpecifiers?: typeEffectSpecifiers + returnClause?: returnClause + inKeyword: _token + codeBlock: + leftBrace: _token + statements*: codeBlockItem + rightBrace: _token + codeBlockFile: + body: codeBlock + endOfFileToken: _token + codeBlockItem: + item: [decl, stmt, expr] + semicolon?: _token + compositionType: + elements*: compositionTypeElement + compositionTypeElement: + type: type + ampersand?: token + conditionElement: + condition: [expr, availabilityCondition, matchingPatternCondition, optionalBindingCondition] + trailingComma?: _token + conformanceRequirement: + leftType: type + colon: _token + rightType: type + consumeExpr: + consumeKeyword: _token + expression: expr + continueStmt: + continueKeyword: _token + label?: _token + copyExpr: + copyKeyword: _token + expression: expr + declModifier: + name: _token + detail?: declModifierDetail + declModifierDetail: + leftParen: _token + detail: _token + rightParen: _token + declNameArgument: + name: token + colon: _token + declNameArguments: + leftParen: _token + arguments*: declNameArgument + rightParen: _token + declReferenceExpr: + moduleSelector?: moduleSelector + baseName: _token + argumentNames?: declNameArguments + deferStmt: + deferKeyword: _token + body: codeBlock + deinitializerDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + deinitKeyword: _token + effectSpecifiers?: deinitializerEffectSpecifiers + body?: codeBlock + deinitializerEffectSpecifiers: + asyncSpecifier?: _token + derivativeAttributeArguments: + ofLabel: _token + colon: _token + originalDeclName: expr + period?: _token + accessorSpecifier?: _token + comma?: _token + arguments?: differentiabilityWithRespectToArgument + designatedType: + leadingComma: _token + name: token + dictionaryElement: + key: expr + colon: _token + value: expr + trailingComma?: _token + dictionaryExpr: + leftSquare: _token + content: [_token, dictionaryElementList] + rightSquare: _token + dictionaryType: + leftSquare: _token + key: type + colon: _token + value: type + rightSquare: _token + differentiabilityArgument: + argument: _token + trailingComma?: _token + differentiabilityArguments: + leftParen: _token + arguments*: differentiabilityArgument + rightParen: _token + differentiabilityWithRespectToArgument: + wrtLabel: _token + colon: _token + arguments: [differentiabilityArgument, differentiabilityArguments] + differentiableAttributeArguments: + kindSpecifier?: _token + kindSpecifierComma?: _token + arguments?: differentiabilityWithRespectToArgument + argumentsComma?: _token + genericWhereClause?: genericWhereClause + discardAssignmentExpr: + wildcard: _token + discardStmt: + discardKeyword: _token + expression: expr + doExpr: + doKeyword: _token + body: codeBlock + catchClauses*: catchClause + doStmt: + doKeyword: _token + throwsClause?: throwsClause + body: codeBlock + catchClauses*: catchClause + documentationAttributeArgument: + label: _token + colon: _token + value: [_token, stringLiteralExpr] + trailingComma?: _token + dynamicReplacementAttributeArguments: + forLabel: _token + colon: _token + declName: declReferenceExpr + editorPlaceholderDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + placeholder: _token + editorPlaceholderExpr: + placeholder: _token + enumCaseDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + caseKeyword: _token + elements*: enumCaseElement + enumCaseElement: + name: _token + parameterClause?: enumCaseParameterClause + rawValue?: initializerClause + trailingComma?: _token + enumCaseParameter: + modifiers*: declModifier + firstName?: _token + secondName?: _token + colon?: _token + type: type + defaultValue?: initializerClause + trailingComma?: _token + enumCaseParameterClause: + leftParen: _token + parameters*: enumCaseParameter + rightParen: _token + enumDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + enumKeyword: _token + name: _token + genericParameterClause?: genericParameterClause + inheritanceClause?: inheritanceClause + genericWhereClause?: genericWhereClause + memberBlock: memberBlock + expressionPattern: + expression: expr + expressionSegment: + backslash: _token + pounds?: _token + leftParen: _token + expressions*: labeledExpr + rightParen: _token + expressionStmt: + expression: expr + extensionDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + extensionKeyword: _token + extendedType: type + inheritanceClause?: inheritanceClause + genericWhereClause?: genericWhereClause + memberBlock: memberBlock + fallThroughStmt: + fallthroughKeyword: _token + floatLiteralExpr: + literal: _token + forStmt: + forKeyword: _token + tryKeyword?: _token + awaitKeyword?: _token + unsafeKeyword?: _token + caseKeyword?: _token + pattern: pattern + typeAnnotation?: typeAnnotation + inKeyword: _token + sequence: expr + whereClause?: whereClause + body: codeBlock + forceUnwrapExpr: + expression: expr + exclamationMark: _token + functionCallExpr: + calledExpression: expr + leftParen?: _token + arguments*: labeledExpr + rightParen?: _token + trailingClosure?: closureExpr + additionalTrailingClosures*: multipleTrailingClosureElement + functionDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + funcKeyword: _token + name: _token + genericParameterClause?: genericParameterClause + signature: functionSignature + genericWhereClause?: genericWhereClause + body?: codeBlock + functionEffectSpecifiers: + asyncSpecifier?: _token + throwsClause?: throwsClause + functionParameter: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + firstName: _token + secondName?: _token + colon: _token + type: type + ellipsis?: _token + defaultValue?: initializerClause + trailingComma?: _token + functionParameterClause: + leftParen: _token + parameters*: functionParameter + rightParen: _token + functionSignature: + parameterClause: functionParameterClause + effectSpecifiers?: functionEffectSpecifiers + returnClause?: returnClause + functionType: + leftParen: _token + parameters*: tupleTypeElement + rightParen: _token + effectSpecifiers?: typeEffectSpecifiers + returnClause: returnClause + genericArgument: + argument: [type, expr] + trailingComma?: _token + genericArgumentClause: + leftAngle: _token + arguments*: genericArgument + rightAngle: _token + genericParameter: + attributes*: [attribute, ifConfigDecl] + specifier?: _token + name: _token + colon?: _token + inheritedType?: type + trailingComma?: _token + genericParameterClause: + leftAngle: _token + parameters*: genericParameter + genericWhereClause?: genericWhereClause + rightAngle: _token + genericRequirement: + requirement: [sameTypeRequirement, conformanceRequirement, layoutRequirement] + trailingComma?: _token + genericSpecializationExpr: + expression: expr + genericArgumentClause: genericArgumentClause + genericWhereClause: + whereKeyword: _token + requirements*: genericRequirement + guardStmt: + guardKeyword: _token + conditions*: conditionElement + elseKeyword: _token + body: codeBlock + identifierPattern: + identifier: _token + identifierType: + moduleSelector?: moduleSelector + name: _token + genericArgumentClause?: genericArgumentClause + ifConfigClause: + poundKeyword: _token + condition?: expr + elements?: [codeBlockItemList, switchCaseList, memberBlockItemList, expr, attributeList] + ifConfigDecl: + clauses*: ifConfigClause + poundEndif: _token + ifExpr: + ifKeyword: _token + conditions*: conditionElement + body: codeBlock + elseKeyword?: _token + elseBody?: [ifExpr, codeBlock] + implementsAttributeArguments: + type: type + comma: _token + declName: declReferenceExpr + implicitlyUnwrappedOptionalType: + wrappedType: type + exclamationMark: _token + importDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + importKeyword: _token + importKindSpecifier?: _token + path*: importPathComponent + importPathComponent: + name: _token + trailingPeriod?: _token + inOutExpr: + ampersand: _token + expression: expr + infixOperatorExpr: + leftOperand: expr + operator: expr + rightOperand: expr + inheritanceClause: + colon: _token + inheritedTypes*: inheritedType + inheritedType: + type: type + trailingComma?: _token + initializerClause: + equal: _token + value: expr + initializerDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + initKeyword: _token + optionalMark?: _token + genericParameterClause?: genericParameterClause + signature: functionSignature + genericWhereClause?: genericWhereClause + body?: codeBlock + inlineArrayType: + leftSquare: _token + count: genericArgument + separator: _token + element: genericArgument + rightSquare: _token + integerLiteralExpr: + literal: _token + isExpr: + expression: expr + isKeyword: _token + type: type + isTypePattern: + isKeyword: _token + type: type + keyPathComponent: + period?: _token + component: [keyPathPropertyComponent, keyPathMethodComponent, keyPathSubscriptComponent, keyPathOptionalComponent] + keyPathExpr: + backslash: _token + root?: type + components*: keyPathComponent + keyPathMethodComponent: + declName: declReferenceExpr + leftParen: _token + arguments*: labeledExpr + rightParen: _token + keyPathOptionalComponent: + questionOrExclamationMark: _token + keyPathPropertyComponent: + declName: declReferenceExpr + genericArgumentClause?: genericArgumentClause + keyPathSubscriptComponent: + leftSquare: _token + arguments*: labeledExpr + rightSquare: _token + labeledExpr: + label?: _token + colon?: _token + expression: expr + trailingComma?: _token + labeledSpecializeArgument: + label: _token + colon: _token + value: token + trailingComma?: _token + labeledStmt: + label: _token + colon: _token + statement: stmt + layoutRequirement: + type: type + colon: _token + layoutSpecifier: _token + leftParen?: _token + size?: _token + comma?: _token + alignment?: _token + rightParen?: _token + lifetimeSpecifierArgument: + parameter: _token + trailingComma?: _token + lifetimeTypeSpecifier: + dependsOnKeyword: _token + leftParen: _token + scopedKeyword?: _token + arguments*: lifetimeSpecifierArgument + rightParen: _token + macroDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + macroKeyword: _token + name: _token + genericParameterClause?: genericParameterClause + signature: functionSignature + definition?: initializerClause + genericWhereClause?: genericWhereClause + macroExpansionDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + pound: _token + moduleSelector?: moduleSelector + macroName: _token + genericArgumentClause?: genericArgumentClause + leftParen?: _token + arguments*: labeledExpr + rightParen?: _token + trailingClosure?: closureExpr + additionalTrailingClosures*: multipleTrailingClosureElement + macroExpansionExpr: + pound: _token + moduleSelector?: moduleSelector + macroName: _token + genericArgumentClause?: genericArgumentClause + leftParen?: _token + arguments*: labeledExpr + rightParen?: _token + trailingClosure?: closureExpr + additionalTrailingClosures*: multipleTrailingClosureElement + matchingPatternCondition: + caseKeyword: _token + pattern: pattern + typeAnnotation?: typeAnnotation + initializer: initializerClause + memberAccessExpr: + base?: expr + period: _token + declName: declReferenceExpr + memberBlock: + leftBrace: _token + members*: memberBlockItem + rightBrace: _token + memberBlockItem: + decl: decl + semicolon?: _token + memberBlockItemListFile: + members*: memberBlockItem + endOfFileToken: _token + memberType: + baseType: type + period: _token + moduleSelector?: moduleSelector + name: _token + genericArgumentClause?: genericArgumentClause + metatypeType: + baseType: type + period: _token + metatypeSpecifier: _token + missing: + placeholder: _token + missingDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + placeholder: _token + missingExpr: + placeholder: _token + missingPattern: + placeholder: _token + missingStmt: + placeholder: _token + missingType: + placeholder: _token + moduleSelector: + moduleName: _token + colonColon: _token + multipleTrailingClosureElement: + label: _token + colon: _token + closure: closureExpr + namedOpaqueReturnType: + genericParameterClause: genericParameterClause + type: type + nilLiteralExpr: + nilKeyword: _token + nonisolatedSpecifierArgument: + leftParen: _token + nonsendingKeyword: _token + rightParen: _token + nonisolatedTypeSpecifier: + nonisolatedKeyword: _token + argument?: nonisolatedSpecifierArgument + objCSelectorPiece: + name?: token + colon?: _token + operatorDecl: + fixitySpecifier: _token + operatorKeyword: _token + name: _token + operatorPrecedenceAndTypes?: operatorPrecedenceAndTypes + operatorPrecedenceAndTypes: + colon: _token + precedenceGroup: _token + designatedTypes*: designatedType + optionalBindingCondition: + bindingSpecifier: _token + pattern: pattern + typeAnnotation?: typeAnnotation + initializer?: initializerClause + optionalChainingExpr: + expression: expr + questionMark: _token + optionalType: + wrappedType: type + questionMark: _token + originallyDefinedInAttributeArguments: + moduleLabel: _token + colon: _token + moduleName: stringLiteralExpr + comma: _token + platforms*: platformVersionItem + packElementExpr: + eachKeyword: _token + pack: expr + packElementType: + eachKeyword: _token + pack: type + packExpansionExpr: + repeatKeyword: _token + repetitionPattern: expr + packExpansionType: + repeatKeyword: _token + repetitionPattern: type + patternBinding: + pattern: pattern + typeAnnotation?: typeAnnotation + initializer?: initializerClause + accessorBlock?: accessorBlock + trailingComma?: _token + patternExpr: + pattern: pattern + platformVersion: + platform: _token + version?: versionTuple + platformVersionItem: + platformVersion: platformVersion + trailingComma?: _token + postfixIfConfigExpr: + base?: expr + config: ifConfigDecl + postfixOperatorExpr: + expression: expr + operator: _token + poundSourceLocation: + poundSourceLocation: _token + leftParen: _token + arguments?: poundSourceLocationArguments + rightParen: _token + poundSourceLocationArguments: + fileLabel: _token + fileColon: _token + fileName: simpleStringLiteralExpr + comma: _token + lineLabel: _token + lineColon: _token + lineNumber: _token + precedenceGroupAssignment: + assignmentLabel: _token + colon: _token + value: _token + precedenceGroupAssociativity: + associativityLabel: _token + colon: _token + value: _token + precedenceGroupDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + precedencegroupKeyword: _token + name: _token + leftBrace: _token + groupAttributes*: [precedenceGroupRelation, precedenceGroupAssignment, precedenceGroupAssociativity] + rightBrace: _token + precedenceGroupName: + name: _token + trailingComma?: _token + precedenceGroupRelation: + higherThanOrLowerThanLabel: _token + colon: _token + precedenceGroups*: precedenceGroupName + prefixOperatorExpr: + operator: _token + expression: expr + primaryAssociatedType: + name: _token + trailingComma?: _token + primaryAssociatedTypeClause: + leftAngle: _token + primaryAssociatedTypes*: primaryAssociatedType + rightAngle: _token + protocolDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + protocolKeyword: _token + name: _token + primaryAssociatedTypeClause?: primaryAssociatedTypeClause + inheritanceClause?: inheritanceClause + genericWhereClause?: genericWhereClause + memberBlock: memberBlock + regexLiteralExpr: + openingPounds?: _token + openingSlash: _token + regex: _token + closingSlash: _token + closingPounds?: _token + repeatStmt: + repeatKeyword: _token + body: codeBlock + whileKeyword: _token + condition: expr + returnClause: + arrow: _token + type: type + returnStmt: + returnKeyword: _token + expression?: expr + sameTypeRequirement: + leftType: [type, expr] + equal: _token + rightType: [type, expr] + sequenceExpr: + elements*: expr + simpleStringLiteralExpr: + openingQuote: _token + segments*: stringSegment + closingQuote: _token + simpleTypeSpecifier: + specifier: _token + someOrAnyType: + someOrAnySpecifier: _token + constraint: type + sourceFile: + shebang?: _token + statements*: codeBlockItem + endOfFileToken: _token + specializeAvailabilityArgument: + availabilityLabel: _token + colon: _token + availabilityArguments*: availabilityArgument + semicolon: _token + specializeTargetFunctionArgument: + targetLabel: _token + colon: _token + declName: declReferenceExpr + trailingComma?: _token + specializedAttributeArgument: + genericWhereClause: genericWhereClause + stringLiteralExpr: + openingPounds?: _token + openingQuote: _token + segments*: [stringSegment, expressionSegment] + closingQuote: _token + closingPounds?: _token + stringSegment: + content: _token + structDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + structKeyword: _token + name: _token + genericParameterClause?: genericParameterClause + inheritanceClause?: inheritanceClause + genericWhereClause?: genericWhereClause + memberBlock: memberBlock + subscriptCallExpr: + calledExpression: expr + leftSquare: _token + arguments*: labeledExpr + rightSquare: _token + trailingClosure?: closureExpr + additionalTrailingClosures*: multipleTrailingClosureElement + subscriptDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + subscriptKeyword: _token + genericParameterClause?: genericParameterClause + parameterClause: functionParameterClause + returnClause: returnClause + genericWhereClause?: genericWhereClause + accessorBlock?: accessorBlock + superExpr: + superKeyword: _token + suppressedType: + withoutTilde: _token + type: type + switchCase: + attribute?: attribute + label: [switchDefaultLabel, switchCaseLabel] + statements*: codeBlockItem + switchCaseItem: + pattern: pattern + whereClause?: whereClause + trailingComma?: _token + switchCaseLabel: + caseKeyword: _token + caseItems*: switchCaseItem + colon: _token + switchDefaultLabel: + defaultKeyword: _token + colon: _token + switchExpr: + switchKeyword: _token + subject: expr + leftBrace: _token + cases*: [switchCase, ifConfigDecl] + rightBrace: _token + ternaryExpr: + condition: expr + questionMark: _token + thenExpression: expr + colon: _token + elseExpression: expr + thenStmt: + thenKeyword: _token + expression: expr + throwStmt: + throwKeyword: _token + expression: expr + throwsClause: + throwsSpecifier: _token + leftParen?: _token + type?: type + rightParen?: _token + tryExpr: + tryKeyword: _token + questionOrExclamationMark?: _token + expression: expr + tupleExpr: + leftParen: _token + elements*: labeledExpr + rightParen: _token + tuplePattern: + leftParen: _token + elements*: tuplePatternElement + rightParen: _token + tuplePatternElement: + label?: _token + colon?: _token + pattern: pattern + trailingComma?: _token + tupleType: + leftParen: _token + elements*: tupleTypeElement + rightParen: _token + tupleTypeElement: + inoutKeyword?: _token + firstName?: _token + secondName?: _token + colon?: _token + type: type + ellipsis?: _token + trailingComma?: _token + typeAliasDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + typealiasKeyword: _token + name: _token + genericParameterClause?: genericParameterClause + initializer: typeInitializerClause + genericWhereClause?: genericWhereClause + typeAnnotation: + colon: _token + type: type + typeEffectSpecifiers: + asyncSpecifier?: _token + throwsClause?: throwsClause + typeExpr: + type: type + typeInitializerClause: + equal: _token + value: type + unexpectedCodeDecl: + unresolvedAsExpr: + asKeyword: _token + questionOrExclamationMark?: _token + unresolvedIsExpr: + isKeyword: _token + unresolvedTernaryExpr: + questionMark: _token + thenExpression: expr + colon: _token + unsafeExpr: + unsafeKeyword: _token + expression: expr + usingDecl: + usingKeyword: _token + specifier: [attribute, _token] + valueBindingPattern: + bindingSpecifier: _token + pattern: pattern + variableDecl: + attributes*: [attribute, ifConfigDecl] + modifiers*: declModifier + bindingSpecifier: _token + bindings*: patternBinding + versionComponent: + period: _token + number: _token + versionTuple: + major: _token + components*: versionComponent + whereClause: + whereKeyword: _token + condition: expr + whileStmt: + whileKeyword: _token + conditions*: conditionElement + body: codeBlock + wildcardPattern: + wildcard: _token + yieldStmt: + yieldKeyword: _token + yieldedExpressions: [yieldedExpressionsClause, expr] + yieldedExpression: + expression: expr + comma?: _token + yieldedExpressionsClause: + leftParen: _token + elements*: yieldedExpression + rightParen: _token + _token: + binaryOperator: + dollarIdentifier: + floatLiteral: + identifier: + integerLiteral: + postfixOperator: + prefixOperator: + rawStringPoundDelimiter: + regexLiteralPattern: + regexPoundDelimiter: + shebang: + unknown: diff --git a/unified/ql/lib/codeql/unified/Ast.qll b/unified/ql/lib/codeql/unified/Ast.qll index 8adb4c2e44a0..e9a827269cc4 100644 --- a/unified/ql/lib/codeql/unified/Ast.qll +++ b/unified/ql/lib/codeql/unified/Ast.qll @@ -567,6 +567,8 @@ module Unified { final override AstNode getAFieldOrChild() { unified_expr_equality_pattern_def(this, result) } } + final class ExprOrOperator extends @unified_expr_or_operator, AstNodeImpl { } + final class ExprOrPattern extends @unified_expr_or_pattern, AstNodeImpl { } final class ExprOrType extends @unified_expr_or_type, AstNodeImpl { } @@ -1407,6 +1409,22 @@ module Unified { } } + /** A class representing `unresolved_operator_sequence` nodes. */ + final class UnresolvedOperatorSequence extends @unified_unresolved_operator_sequence, AstNodeImpl { + /** Gets the name of the primary QL class for this element. */ + final override string getAPrimaryQlClass() { result = "UnresolvedOperatorSequence" } + + /** Gets the node corresponding to the field `element`. */ + final ExprOrOperator getElement(int i) { + unified_unresolved_operator_sequence_element(this, i, result) + } + + /** Gets a field or child node of this node. */ + final override AstNode getAFieldOrChild() { + unified_unresolved_operator_sequence_element(this, _, result) + } + } + /** A class representing `unsupported_node` tokens. */ final class UnsupportedNode extends @unified_token_unsupported_node, TokenImpl { /** Gets the name of the primary QL class for this element. */ @@ -1773,6 +1791,8 @@ module Unified { or result = node.(UnaryExpr).getOperator() and i = -1 and name = "getOperator" or + result = node.(UnresolvedOperatorSequence).getElement(i) and name = "getElement" + or result = node.(VariableDeclaration).getModifier(i) and name = "getModifier" or result = node.(VariableDeclaration).getPattern() and i = -1 and name = "getPattern" diff --git a/unified/ql/lib/unified.dbscheme b/unified/ql/lib/unified.dbscheme index e957e303c22f..3aafb2a494f9 100644 --- a/unified/ql/lib/unified.dbscheme +++ b/unified/ql/lib/unified.dbscheme @@ -452,13 +452,15 @@ unified_equality_type_constraint_def( int right: @unified_type_expr ref ); -@unified_expr = @unified_array_literal | @unified_assign_expr | @unified_binary_expr | @unified_block | @unified_break_expr | @unified_call_expr | @unified_compound_assign_expr | @unified_continue_expr | @unified_function_expr | @unified_if_expr | @unified_key_value_pair | @unified_map_literal | @unified_member_access_expr | @unified_name_expr | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_expr | @unified_throw_expr | @unified_token_boolean_literal | @unified_token_builtin_expr | @unified_token_empty_expr | @unified_token_float_literal | @unified_token_int_literal | @unified_token_regex_literal | @unified_token_string_literal | @unified_token_super_expr | @unified_token_unsupported_node | @unified_try_expr | @unified_tuple_expr | @unified_type_cast_expr | @unified_type_test_expr | @unified_unary_expr +@unified_expr = @unified_array_literal | @unified_assign_expr | @unified_binary_expr | @unified_block | @unified_break_expr | @unified_call_expr | @unified_compound_assign_expr | @unified_continue_expr | @unified_function_expr | @unified_if_expr | @unified_key_value_pair | @unified_map_literal | @unified_member_access_expr | @unified_name_expr | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_expr | @unified_throw_expr | @unified_token_boolean_literal | @unified_token_builtin_expr | @unified_token_empty_expr | @unified_token_float_literal | @unified_token_int_literal | @unified_token_regex_literal | @unified_token_string_literal | @unified_token_super_expr | @unified_token_unsupported_node | @unified_try_expr | @unified_tuple_expr | @unified_type_cast_expr | @unified_type_test_expr | @unified_unary_expr | @unified_unresolved_operator_sequence unified_expr_equality_pattern_def( unique int id: @unified_expr_equality_pattern, int expr: @unified_expr ref ); +@unified_expr_or_operator = @unified_expr | @unified_token_infix_operator + @unified_expr_or_pattern = @unified_expr | @unified_pattern @unified_expr_or_type = @unified_expr | @unified_type_expr @@ -999,6 +1001,17 @@ unified_unary_expr_def( int operator: @unified_operator ref ); +#keyset[unified_unresolved_operator_sequence, index] +unified_unresolved_operator_sequence_element( + int unified_unresolved_operator_sequence: @unified_unresolved_operator_sequence ref, + int index: int ref, + unique int element: @unified_expr_or_operator ref +); + +unified_unresolved_operator_sequence_def( + unique int id: @unified_unresolved_operator_sequence +); + #keyset[unified_variable_declaration, index] unified_variable_declaration_modifier( int unified_variable_declaration: @unified_variable_declaration ref, @@ -1072,7 +1085,7 @@ unified_trivia_tokeninfo( string value: string ref ); -@unified_ast_node = @unified_accessor_declaration | @unified_argument | @unified_array_literal | @unified_assign_expr | @unified_associated_type_declaration | @unified_base_type | @unified_binary_expr | @unified_block | @unified_bound_type_constraint | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_catch_clause | @unified_class_like_declaration | @unified_compound_assign_expr | @unified_constructor_declaration | @unified_constructor_pattern | @unified_continue_expr | @unified_destructor_declaration | @unified_do_while_stmt | @unified_equality_type_constraint | @unified_expr_equality_pattern | @unified_for_each_stmt | @unified_function_declaration | @unified_function_expr | @unified_function_type_expr | @unified_generic_type_expr | @unified_guard_if_stmt | @unified_if_expr | @unified_import_declaration | @unified_initializer_declaration | @unified_key_value_pair | @unified_labeled_stmt | @unified_map_literal | @unified_member_access_expr | @unified_name_expr | @unified_name_pattern | @unified_named_type_expr | @unified_operator_syntax_declaration | @unified_or_pattern | @unified_parameter | @unified_pattern_element | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_case | @unified_switch_expr | @unified_throw_expr | @unified_token | @unified_top_level | @unified_trivia_token | @unified_try_expr | @unified_tuple_expr | @unified_tuple_pattern | @unified_tuple_type_element | @unified_tuple_type_expr | @unified_type_alias_declaration | @unified_type_cast_expr | @unified_type_parameter | @unified_type_test_expr | @unified_type_test_pattern | @unified_unary_expr | @unified_variable_declaration | @unified_while_stmt +@unified_ast_node = @unified_accessor_declaration | @unified_argument | @unified_array_literal | @unified_assign_expr | @unified_associated_type_declaration | @unified_base_type | @unified_binary_expr | @unified_block | @unified_bound_type_constraint | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_catch_clause | @unified_class_like_declaration | @unified_compound_assign_expr | @unified_constructor_declaration | @unified_constructor_pattern | @unified_continue_expr | @unified_destructor_declaration | @unified_do_while_stmt | @unified_equality_type_constraint | @unified_expr_equality_pattern | @unified_for_each_stmt | @unified_function_declaration | @unified_function_expr | @unified_function_type_expr | @unified_generic_type_expr | @unified_guard_if_stmt | @unified_if_expr | @unified_import_declaration | @unified_initializer_declaration | @unified_key_value_pair | @unified_labeled_stmt | @unified_map_literal | @unified_member_access_expr | @unified_name_expr | @unified_name_pattern | @unified_named_type_expr | @unified_operator_syntax_declaration | @unified_or_pattern | @unified_parameter | @unified_pattern_element | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_case | @unified_switch_expr | @unified_throw_expr | @unified_token | @unified_top_level | @unified_trivia_token | @unified_try_expr | @unified_tuple_expr | @unified_tuple_pattern | @unified_tuple_type_element | @unified_tuple_type_expr | @unified_type_alias_declaration | @unified_type_cast_expr | @unified_type_parameter | @unified_type_test_expr | @unified_type_test_pattern | @unified_unary_expr | @unified_unresolved_operator_sequence | @unified_variable_declaration | @unified_while_stmt unified_ast_node_location( unique int node: @unified_ast_node ref, From ad2ce9315f20aa3fb486626f8dc7a99f6ab4686c Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 16 Jul 2026 12:31:26 +0000 Subject: [PATCH 06/27] unified: Port top-level, literal, and name rules to swift-syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retarget the top-level, literal, and name mapping rules from the tree-sitter grammar to the swift-syntax AST. The output each rule builds is unchanged; only the input pattern differs, reflecting the different AST shape: - `source_file` -> `sourceFile` (statements live in an elided `statements` collection of `codeBlockItem` wrappers); the tree-sitter `global_declaration` / `local_declaration` wrappers have no swift-syntax counterpart. - The lexical integer/string variants (`hex_literal`, `oct_literal`, `multi_line_string_literal`, ...) collapse into single `integerLiteralExpr` / `stringLiteralExpr` kinds. - `simple_identifier` and `referenceable_operator` both become `declReferenceExpr` (its `baseName` is the referenced name or operator). This is the first step of an in-place, rule-by-rule migration; intermediate commits do not pass the corpus test (the runtime front-end is still tree-sitter) — the corpus is regenerated once at the end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 991e95aaa6d7..546249921671 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -124,25 +124,9 @@ fn member_chain( fn translation_rules() -> Vec> { vec![ // ---- Top-level ---- - // Capture all top-level statements, including unnamed tokens like `nil`. - rule!( - (source_file statement: _* @children) - => - (top_level - body: (block stmt: {children}) - ) - ), - // Declarations may be wrapped in local/global wrapper nodes. - rule!((global_declaration _ @inner) => stmt { inner }), - rule!((local_declaration _ @inner) => stmt { inner }), - // ---- swift-syntax front-end (minimal hook-up) ---- - // These rules target the swift-syntax AST (camelCase kind names), - // produced by the sibling `adapter` module. They coexist with the - // tree-sitter rules (snake_case names): rules are dispatched by exact - // kind name, and the two name spaces never collide, so these are inert - // on the tree-sitter path. Only the minimal top-level mapping lives here - // to demonstrate the pipeline end-to-end; the full translation is added - // separately. Unmatched swift-syntax nodes fall through to the + // These rules translate the swift-syntax AST (camelCase kind names), + // produced by the sibling `adapter` module from the `swift-syntax-parse` + // binary's JSON. Anything unmatched falls through to the // `unsupported_node` fallback at the end. // // `sourceFile` holds its top-level statements in an (elided) @@ -155,23 +139,25 @@ fn translation_rules() -> Vec> { ), rule!((codeBlockItem item: @item) => stmt { item }), // ---- Literals ---- - rule!((integer_literal) => (int_literal)), - rule!((hex_literal) => (int_literal)), - rule!((bin_literal) => (int_literal)), - rule!((oct_literal) => (int_literal)), - rule!((real_literal) => (float_literal)), - rule!((boolean_literal) => (boolean_literal)), - rule!("nil" => (builtin_expr)), - rule!((special_literal) => (builtin_expr)), - rule!((line_string_literal) => (string_literal)), - rule!((multi_line_string_literal) => (string_literal)), - rule!((raw_string_literal) => (string_literal)), - rule!((regex_literal) => (regex_literal)), + // swift-syntax does not distinguish the lexical integer/string forms + // (hex/binary/octal, single- vs multi-line, raw): each is a single + // `*LiteralExpr` kind, so the tree-sitter variants collapse to one rule. + rule!((integerLiteralExpr) => (int_literal)), + rule!((floatLiteralExpr) => (float_literal)), + rule!((booleanLiteralExpr) => (boolean_literal)), + rule!((nilLiteralExpr) => (builtin_expr)), + rule!((stringLiteralExpr) => (string_literal)), + rule!((regexLiteralExpr) => (regex_literal)), // ---- Names ---- - rule!((simple_identifier) @id => (name_expr identifier: (identifier #{id}))), - // A referenceable_operator (e.g. `+` used as a value, as in `reduce(0, +)`) - // is treated as a name reference to the operator symbol. - rule!((referenceable_operator) @op => (name_expr identifier: (identifier #{op}))), + // A bare name reference (`x`), and an operator used as a value (`+` in + // `reduce(0, +)`), are both `declReferenceExpr`; its `baseName` is the + // referenced identifier / operator symbol. + rule!((declReferenceExpr baseName: @name) => (name_expr identifier: (identifier #{name}))), + // A discard `_` used as an expression — e.g. the target of a discarding + // assignment `_ = x`. swift-syntax models it as a `discardAssignmentExpr`; + // the tree-sitter path treated the bare `_` as a name, so map it to a + // `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})), From 8328fba68a62b54cc25f073cb301c727611b4edc Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 17 Jul 2026 14:07:40 +0000 Subject: [PATCH 07/27] unified: Port operator rules to swift-syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- .../extractor/src/languages/swift/swift.rs | 120 +++++++++--------- 1 file changed, 57 insertions(+), 63 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 546249921671..3dcd1597b601 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -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> { vec![ // ---- Top-level ---- @@ -159,55 +166,56 @@ fn translation_rules() -> Vec> { // `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.. (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. - rule!( - (operator_declaration "prefix" (referenceable_operator _ @op) (simple_identifier)? @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}) - ), - 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)), + // 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!( + (infixOperatorExpr leftOperand: @l operator: (binaryOperatorExpr) @@op rightOperand: @r) + => + 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!( + (infixOperatorExpr leftOperand: @l operator: (assignmentExpr) rightOperand: @r) + => + (assign_expr target: {l} value: {r}) + ), + // 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> { 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) From 437e48239b15c92cd76adeb993e41f1f89776663 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 17 Jul 2026 15:03:34 +0000 Subject: [PATCH 08/27] unified: Port variable-binding rules to swift-syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retarget `let`/`var` bindings to the swift-syntax AST, preserving the output: - A `variableDecl` publishes its `bindingSpecifier` (`let`/`var`) as the binding modifier, followed by its attributes and modifiers (`@objc`, `public`, `static`, …), and flattens each `patternBinding` into its own `variable_declaration`, tagging non-first ones `chained_declaration`. - One `patternBinding` rule with optional `typeAnnotation`/`initializer` covers `let x`, `let x = e`, `let x: T`, and `let x: T = e`. - `identifierPattern` -> `name_pattern`; `tuplePattern` / `tuplePatternElement` -> `tuple_pattern` / `pattern_element` (tuple destructuring), carrying an optional element label through as the `pattern_element` key. - `codeBlockItem` now captures `_*` / annotates `stmt*` so a multi-binding declaration splices as several statements. - Add a `declModifier` -> `modifier` rule (swift-syntax unifies the visibility/function/member/mutation/ownership modifiers into one kind). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 3dcd1597b601..c17f3550aa05 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -144,7 +144,11 @@ fn translation_rules() -> Vec> { => (top_level body: (block stmt: {items})) ), - rule!((codeBlockItem item: @item) => stmt { item }), + // `codeBlockItem` wraps a top-level statement. It is a simple unwrapper, + // but a single wrapped `variableDecl` can translate to *several* + // declarations (`let x = 1, y = 2`), so the wrapped node is captured with + // `_*` and the result annotated `stmt*` to splice all of them. + rule!((codeBlockItem item: _* @item) => stmt* { item }), // ---- Literals ---- // swift-syntax does not distinguish the lexical integer/string forms // (hex/binary/octal, single- vs multi-line, raw): each is a single @@ -328,18 +332,17 @@ fn translation_rules() -> Vec> { result } ), - // property_binding with any pattern name (identifier or - // destructuring). Reads outer modifiers / chained tag from `ctx`. - // - // The enclosing `property_declaration` leads `ctx.outer_modifiers` - // with the `let`/`var` binding modifier, so the auto-translated name - // pattern (the LHS) becomes a binding, while the initializer value is - // translated with a reset context (see `SwiftContext::reset`). + // The individual bindings of a `variableDecl`. The binding modifier and + // chained tag come from `ctx` (set by the `variableDecl` rule below). The + // type annotation and initializer are both optional (one combined rule + // covers `let x`, `let x = e`, `let x: T`, and `let x: T = e`); the + // initializer value is translated in a reset scope so it is not treated + // as a binding. rule!( - (property_binding - name: @pattern - type: _? @ty - value: _? @@val) + (patternBinding + pattern: @pattern + typeAnnotation: (typeAnnotation type: @ty)? + initializer: (initializerClause value: @@val)?) => (variable_declaration modifier: {ctx.outer_modifiers.clone()} @@ -348,34 +351,28 @@ fn translation_rules() -> Vec> { type: {ty} value: {ctx.reset(); ctx.translate(val)?}) ), - // property_declaration: flatten declarators (each may translate - // to multiple nodes — variable_declaration and/or - // accessor_declaration) and attach the binding modifier - // (let/var), outer modifiers, and `chained_declaration` for - // non-first declarations. Manual rule: publishes - // binding/outer modifiers into `ctx` and translates each - // declarator with `ctx.is_chained` toggled per iteration. The - // inner declaration rules (`property_binding` variants, - // accessor inner rules) read these fields and emit complete - // `modifier:` lists from the start. - rule!( - (property_declaration - binding: (value_binding_pattern mutability: @@binding_kind) - declarator: _* @@decls - (modifiers)* @mods) - => - member* { - let binding_text = ctx.ast.source_text(binding_kind); - let binding = ctx.literal("modifier", &binding_text); - // The `let`/`var` binding modifier leads the declaration's - // modifier list and doubles as the "this is a binding" signal - // for pattern translation (see `in_binding_pattern`). - ctx.outer_modifiers = std::iter::once(binding).chain(mods).collect(); - + // A `let`/`var` declaration binds one or more comma-separated patterns + // (`let x = 1, y = 2`). The `bindingSpecifier` (`let`/`var`) is published + // as the binding modifier, followed by any attributes and modifiers + // (`@objc`, `public`, `static`, …); each `patternBinding` becomes its own + // `variable_declaration`, with non-first ones tagged `chained_declaration`. + // Accessor/observer forms are handled by the earlier rules. + rule!( + (variableDecl + attributes: _* @attrs + modifiers: _* @mods + bindingSpecifier: @@spec + bindings: _* @@bindings) + => + stmt* { + let binding = tree!((modifier #{spec})); + // The binding (`let`/`var`) leads, then attributes then modifiers + // in source order (Swift writes attributes before modifiers). + ctx.outer_modifiers = std::iter::once(binding).chain(attrs).chain(mods).collect(); let mut result = Vec::new(); - for (i, decl) in decls.into_iter().enumerate() { + for (i, b) in bindings.into_iter().enumerate() { ctx.is_chained = i > 0; - result.extend(ctx.translate(decl)?); + result.extend(ctx.translate(b)?); } result } @@ -451,9 +448,9 @@ fn translation_rules() -> Vec> { ), // Unwrap `type` wrapper node rule!((type name: @inner) => type_expr { inner }), - // Pattern with bound_identifier → name_pattern. + // `identifierPattern` wraps a single identifier token. rule!( - (pattern bound_identifier: @name) + (identifierPattern identifier: @name) => (name_pattern identifier: (identifier #{name})) ), @@ -485,10 +482,15 @@ fn translation_rules() -> Vec> { constructor: (member_access_expr base: (inferred_type_expr #{dot}) member: (identifier #{name})) element: {items}) ), - // Tuple pattern and its (optionally named) items - 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})), + // A tuple destructuring pattern (`let (a, b) = …`). A labelled element + // (`let (x: a) = …`) carries its label through as the `pattern_element` + // key; unlabelled elements have no key. + rule!((tuplePattern elements: _* @els) => (tuple_pattern element: {els})), + rule!( + (tuplePatternElement label: _? @label pattern: @p) + => + (pattern_element key: {label.map(|l| tree!((identifier #{l})))} pattern: {p}) + ), // Type casting pattern (TODO) rule!((pattern kind: (type_casting_pattern)) => (unsupported_node)), // Wildcard pattern @@ -906,6 +908,9 @@ fn translation_rules() -> Vec> { // Modifiers — unwrap to individual modifier children rule!((modifiers _* @mods) => modifier* { mods }), rule!((attribute) @m => (modifier #{m})), + // swift-syntax models every access/function/member/mutation/ownership + // modifier as a single `declModifier`; its source text is the modifier. + rule!((declModifier) @m => (modifier #{m})), rule!((visibility_modifier) @m => (modifier #{m})), rule!((function_modifier) @m => (modifier #{m})), rule!((member_modifier) @m => (modifier #{m})), From 191fc542ba47bdd6fc6fa62756b1a5fdf753a20a Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 17 Jul 2026 15:36:03 +0000 Subject: [PATCH 09/27] unified: Port type-expression rules to swift-syntax Retarget type expressions to the swift-syntax AST, output unchanged: - `user_type` -> `identifierType` (its `name` is the type-name token). - The sugared types keep desugaring to `generic_type_expr`: `optionalType` -> Optional, `arrayType` -> Array, `dictionaryType` -> Dictionary. - A generic type with explicit arguments (`Set`) stays opaque (its whole source text as the name), matched before the plain `identifierType` rule. This matches the tree-sitter `user_type` rule, which was also opaque. - Tuple types (`(Int, String)`) -> `tuple_type_expr` and function types (`(Int) -> Bool`) -> `function_type_expr`. swift-syntax holds both as `tupleTypeElement`s but a tuple element maps to `tuple_type_element` while a function parameter maps to `parameter`; the containers set `SwiftContext::in_function_type` for their direct children so the shared `tupleTypeElement` rule emits the right kind (and nested types stay correct). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 111 ++++++++++++++---- 1 file changed, 85 insertions(+), 26 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index c17f3550aa05..d893b21d616e 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -37,6 +37,14 @@ struct SwiftContext { /// `chained_declaration` modifier so the original grouping can be /// recovered downstream. is_chained: bool, + /// True while translating the parameters of a `functionType`. swift-syntax + /// models a function type's parameters with the same `tupleTypeElement` + /// kind as a tuple type's elements, so the shared `tupleTypeElement` rule + /// reads this to emit a `parameter` (function-type param) rather than a + /// `tuple_type_element` (tuple-type element). The `tupleType` / + /// `functionType` rules each set it for their direct children, so nested + /// types are translated in the correct context. + in_function_type: bool, } impl SwiftContext { @@ -920,32 +928,83 @@ fn translation_rules() -> Vec> { rule!((parameter_modifier) @m => (modifier #{m})), rule!((inheritance_modifier) @m => (modifier #{m})), rule!((property_behavior_modifier) @m => (modifier #{m})), - // Type annotations — unwrap - rule!((type_annotation type: @inner) => type_expr { inner }), - // user_type is split into simple_user_type parts. - // 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_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 - rule!((array_type element: @e) => (generic_type_expr - base: (named_type_expr name: (identifier "Array")) - type_argument: {e})), - // Dictionary type `[K: V]` → generic_type_expr with Dictionary base - rule!((dictionary_type key: @k value: @v) => (generic_type_expr - base: (named_type_expr name: (identifier "Dictionary")) - type_argument: {k} - type_argument: {v})), - // Optional type `T?` → generic_type_expr with Optional base - rule!((optional_type wrapped: @w) => (generic_type_expr - 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 name: @name type: @ty) => (parameter external_name: (identifier #{name}) type: {ty})), - rule!((function_type_parameter type: @ty) => (parameter type: {ty})), + // Type expressions. A generic type applied with explicit arguments + // (`Set`) is represented opaquely, using the whole source text as + // the name (PARITY(tree-sitter): the generic arguments are not + // structured `type_argument`s). Matched before the plain `identifierType` + // rule, which would otherwise drop the arguments. + rule!( + (identifierType genericArgumentClause: (genericArgumentClause)) @@ty + => + (named_type_expr name: (identifier #{ty})) + ), + // A named type (`Int`). `identifierType.name` is the type-name token. + rule!((identifierType name: @@n) => (named_type_expr name: (identifier #{n}))), + // A qualified type (`Outer.Inner`, `NSString.CompareOptions`). swift-syntax + // nests these as `memberType` nodes; like the old tree-sitter `user_type` + // rule, we keep the whole dotted path as the opaque `named_type_expr` name. + rule!((memberType) @ty => (named_type_expr name: (identifier #{ty}))), + // Sugared types desugar to `generic_type_expr`: `T?` -> Optional, + // `[T]` -> Array, `[K: V]` -> Dictionary. + rule!( + (optionalType wrappedType: @w) + => + (generic_type_expr base: (named_type_expr name: (identifier "Optional")) type_argument: {w}) + ), + rule!( + (arrayType element: @e) + => + (generic_type_expr base: (named_type_expr name: (identifier "Array")) type_argument: {e}) + ), + rule!( + (dictionaryType key: @k value: @v) + => + (generic_type_expr base: (named_type_expr name: (identifier "Dictionary")) type_argument: {k} type_argument: {v}) + ), + // A tuple type (`(Int, String)`) or function type (`(Int) -> Bool`). + // Both hold their contents as `tupleTypeElement`s, but a tuple element + // maps to `tuple_type_element` while a function parameter maps to + // `parameter`. Each container sets `ctx.in_function_type` for its direct + // children (and translates them explicitly, so a nested type is + // translated in the right context) and the shared `tupleTypeElement` + // rule below reads it. An element's label (`firstName`) is optional. + rule!( + (tupleType elements: _* @@elems) + => + tuple_type_expr { + ctx.in_function_type = false; + let mut out = Vec::new(); + for e in elems { + out.extend(ctx.translate(e)?); + } + tree!((tuple_type_expr element: {out})) + } + ), + rule!( + (functionType parameters: _* @@params returnClause: (returnClause type: @ret)) + => + function_type_expr { + ctx.in_function_type = true; + let mut out = Vec::new(); + for p in params { + out.extend(ctx.translate(p)?); + } + ctx.in_function_type = false; + tree!((function_type_expr parameter: {out} return_type: {ret})) + } + ), + rule!( + (tupleTypeElement firstName: _? @@name type: @ty) + => + tuple_type_element { + let name = name.map(|n| tree!((identifier #{n}))); + if ctx.in_function_type { + tree!((parameter external_name: {name} type: {ty})) + } else { + tree!((tuple_type_element name: {name} type: {ty})) + } + } + ), // Selector expression: `#selector(inner)` -- not yet supported rule!( (selector_expression _ @inner) From 52cdf59f2748d33dae412f59fdfc11e2c90cda6d Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 17 Jul 2026 21:13:21 +0000 Subject: [PATCH 10/27] unified: Port function, call, and member-access rules to swift-syntax Retarget function declarations, calls, member access, and control transfer to the swift-syntax AST, output unchanged: - `functionDecl` -> `function_declaration` (parameters and return type nest under `signature`; the body is a `codeBlock`). A bodyless function (a protocol requirement) still emits an empty `block`. - `functionParameter` -> `parameter`: two names give the external label and internal name, one name just the internal name; the default value is handled inline, so the `ctx.default_value` threading (and its `SwiftContext` field) is removed. The declared type is dropped: in the tree-sitter path the untyped-parameter rule was ordered first and shadowed the typed one, so the baseline emits no parameter type. - A function reference spelled with argument labels (`f(x:y:z:)`) is a `declReferenceExpr` with `argumentNames`; it is mapped to `unsupported_node` (matched before the bare-name rule) so downstream QL isn't handed a malformed reference, as in the tree-sitter path. - `functionCallExpr` -> `call_expr` (a trailing closure becomes a final unlabelled argument); `labeledExpr` -> `argument`; `memberAccessExpr` -> `member_access_expr` (base-ful matched before leading-dot). - `returnStmt`/`breakStmt`/`continueStmt`/`throwStmt` -> `return_expr`/`break_expr`/`continue_expr`/`throw_expr`, collapsing the labelled/unlabelled variants via optional captures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 162 ++++++++++-------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index d893b21d616e..74f76202a906 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -21,10 +21,6 @@ struct SwiftContext { /// `protocol_property_declaration` when present; read by the /// accessor inner rules. property_type: Option, - /// Default-value expression for the next translated `parameter`. Set - /// by the outer `function_parameter` rule; read by the `parameter` - /// rules. - default_value: Option, /// Translated outer modifiers to attach to each child of a flattening /// outer rule. Set by `property_declaration`, `binding_pattern`, /// `enum_entry`, and `protocol_property_declaration`. For `let`/`var` @@ -168,6 +164,17 @@ fn translation_rules() -> Vec> { rule!((stringLiteralExpr) => (string_literal)), rule!((regexLiteralExpr) => (regex_literal)), // ---- Names ---- + // A function reference spelled with argument labels (`f(x:y:z:)`) is a + // `declReferenceExpr` carrying `argumentNames`. Mark it unsupported for + // now (rather than let the bare-name rule below treat it as a plain + // reference), so downstream QL isn't handed a malformed reference. In + // the future this should become a lambda expression. Matched before the + // bare-name rule. + rule!( + (declReferenceExpr argumentNames: (declNameArguments)) + => + (unsupported_node) + ), // A bare name reference (`x`), and an operator used as a value (`+` in // `reduce(0, +)`), are both `declReferenceExpr`; its `baseName` is the // referenced identifier / operator symbol. @@ -524,108 +531,111 @@ fn translation_rules() -> Vec> { // the 'expression' case is the only remaining possibility when this rule tries to match. rule!((pattern kind: @expr) => (expr_equality_pattern expr: {expr})), // ---- Functions ---- - // Function declaration - // Function declaration (return type optional, body statements optional). + // A function declaration (parameters/return type/body optional). The + // parameters and return type nest under `signature`; the body is a + // `codeBlock`. A bodyless function (a protocol requirement) still emits + // an empty `block`, matching the tree-sitter path. rule!( - (function_declaration + (functionDecl name: @name - parameter: _* @params - return_type: _? @ret - body: (block statement: _* @body_stmts)) + signature: (functionSignature + parameterClause: (functionParameterClause parameters: _* @params) + returnClause: (returnClause type: @ret)?) + body: (codeBlock statements: _* @body)) => (function_declaration name: (identifier #{name}) 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` - // before translating the inner `parameter` so the `parameter` - // rules can include it as a `default:` field directly. - rule!( - (function_parameter parameter: @@p default_value: _? @def) - => - parameter* { - ctx.default_value = def; - ctx.translate(p)? - } - ), - // Parameter with external name and type - rule!( - (parameter external_name: @ext name: @name) - => - (parameter - external_name: (identifier #{ext}) - pattern: (name_pattern identifier: (identifier #{name})) - default: {ctx.default_value}) + body: (block stmt: {body})) ), rule!( - (parameter external_name: @ext name: @name type: @ty) + (functionDecl + name: @name + signature: (functionSignature + parameterClause: (functionParameterClause parameters: _* @params) + returnClause: (returnClause type: @ret)?)) => - (parameter - external_name: (identifier #{ext}) - pattern: (name_pattern identifier: (identifier #{name})) - type: {ty} - default: {ctx.default_value}) + (function_declaration + name: (identifier #{name}) + parameter: {params} + return_type: {ret} + body: (block)) ), - // Parameter with just name and type (no external name) - rule!( - (parameter name: @name) - => - (parameter - pattern: (name_pattern identifier: (identifier #{name})) - default: {ctx.default_value}) + // A function parameter. With two names (`firstName`+`secondName`) the + // first is the external argument label and the second the internal name; + // with one name it is just the internal name. The default value is + // optional. + // + // PARITY: the declared type is intentionally dropped. In the tree-sitter + // path the untyped-parameter rule was ordered before the typed one and + // shadowed it (first match wins), so the baseline emits no parameter + // type; emitting one here would diverge from it. + rule!( + (functionParameter + firstName: @@first + secondName: _? @@second + defaultValue: (initializerClause value: @val)?) + => + parameter { + let (external, name) = match second { + Some(second) => (Some(tree!((identifier #{first}))), second), + None => (None, first), + }; + tree!((parameter + external_name: {external} + pattern: (name_pattern identifier: (identifier #{name})) + default: {val})) + } ), + // A function/method call (`foo(1, 2)`). `calledExpression` is the callee + // and `arguments` is an (elided) list of `labeledExpr`, each translated + // to an `argument` below. A trailing closure (`xs.map { … }`) becomes a + // final unlabelled argument; that variant is matched first. rule!( - (parameter name: @name type: @ty) + (functionCallExpr calledExpression: @callee arguments: _* @args trailingClosure: @tc) => - (parameter - pattern: (name_pattern identifier: (identifier #{name})) - type: {ty} - default: {ctx.default_value}) + (call_expr callee: {callee} argument: {args} argument: (argument value: {tc})) ), - // 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. - // In the future we probably want to translate this to a lambda expression. rule!( - (call_expression suffix: (call_suffix arguments: (value_arguments argument: (value_argument reference_specifier: _+) @ref_arg))) + (functionCallExpr calledExpression: @callee arguments: _* @args) => - (unsupported_node) + (call_expr callee: {callee} argument: {args}) ), - // Call expression: function(args...) + // A call argument keeps its optional label as the `name` and its value. + // (Enum-case pattern arguments reuse `labeledExpr` too; that handling is + // added with the switch/pattern rules.) rule!( - (call_expression function: @func suffix: (call_suffix arguments: (value_arguments argument: (value_argument)* @args))) + (labeledExpr label: @lbl expression: @val) => - (call_expr callee: {func} argument: {args}) + (argument name: (identifier #{lbl}) value: {val}) ), - // Value argument with label (value: _ matches both named nodes and anonymous tokens like nil) rule!( - (value_argument name: (value_argument_label name: @label) value: @val) + (labeledExpr expression: @val) => - (argument name: (identifier #{label}) value: {val}) + (argument value: {val}) ), - // Value argument without label + // Member access (`list.append`). The `declName` is itself a + // `declReferenceExpr`; pull its `baseName` out as the member identifier. + // A leading-dot access (`.foo`) has no explicit base — the base is an + // `inferred_type_expr`. The base-ful form is matched first. rule!( - (value_argument value: @val) + (memberAccessExpr base: @base declName: (declReferenceExpr baseName: @member)) => - (argument value: {val}) + (member_access_expr base: {base} member: (identifier #{member})) ), - // Navigation expression → member_access_expr rule!( - (navigation_expression target: @target suffix: (navigation_suffix suffix: @member)) + (memberAccessExpr declName: (declReferenceExpr baseName: @member)) => - (member_access_expr base: {target} member: (identifier #{member})) + (member_access_expr base: (inferred_type_expr) member: (identifier #{member})) ), - // 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: "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}))), - rule!((control_transfer_statement kind: "continue") => (continue_expr)), - rule!((control_transfer_statement kind: (throw_keyword) result: @val) => (throw_expr value: {val})), + // Control transfer, one rule per keyword. `return` carries an optional + // value; `break` / `continue` an optional target label; `throw` its + // thrown expression. + rule!((returnStmt expression: _? @val) => (return_expr value: {val})), + rule!((breakStmt label: _? @@lbl) => (break_expr label: {lbl.map(|l| tree!((identifier #{l})))})), + rule!((continueStmt label: _? @@lbl) => (continue_expr label: {lbl.map(|l| tree!((identifier #{l})))})), + rule!((throwStmt expression: @val) => (throw_expr value: {val})), // ---- Closures ---- // Lambda literal with optional type header (parameters + optional return type). // The return_type capture is optional, so this rule covers both cases. From 6ad1facee1ba8529c543f8cefa3d694599fece14 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 17 Jul 2026 21:27:32 +0000 Subject: [PATCH 11/27] unified: Port closure rules to swift-syntax Retarget closures to the swift-syntax AST, output unchanged. swift-syntax nests the whole closure header under an optional `closureSignature`, so a single `closureExpr` rule (with optional attributes, capture list, parameter clause, and return clause) replaces the tree-sitter `lambda_literal` rule. The parameter clause is a union of the parenthesised form (`closureParameterClause`, unwrapped to its `closureParameter` children) and the shorthand form (`closureShorthandParameter`, a bare name); one rule each replaces the four `lambda_parameter` variants. `closureCapture` -> `variable_declaration` (an optional ownership specifier becomes a modifier; an explicit capture initializer becomes the bound value). The trailing-closure call form is already handled by the `functionCallExpr` `trailingClosure` variant, so the tree-sitter trailing-closure call rule is dropped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 74 ++++++++----------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 74f76202a906..a12e42e8ffc3 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -637,16 +637,20 @@ fn translation_rules() -> Vec> { rule!((continueStmt label: _? @@lbl) => (continue_expr label: {lbl.map(|l| tree!((identifier #{l})))})), rule!((throwStmt expression: @val) => (throw_expr value: {val})), // ---- Closures ---- - // Lambda literal with optional type header (parameters + optional return type). - // The return_type capture is optional, so this rule covers both cases. - rule!( - (lambda_literal - attribute: _* @attrs - captures: (capture_list item: _* @captures)? - type: (lambda_function_type - params: (lambda_function_type_parameters parameter: _* @params) - return_type: _? @ret)? - statement: _* @body) + // A closure (`{ (x: Int) -> Int in … }`) becomes a `function_expr`. The + // whole signature is optional, as are its capture list, parameter + // clause, and return clause, so one rule covers everything from a bare + // `{ … }` to `{ [weak self] (x) -> T in … }`. Shorthand `$0` closures + // have no signature and their `$0` references are ordinary name + // expressions. + rule!( + (closureExpr + signature: (closureSignature + attributes: _* @attrs + capture: (closureCaptureClause items: _* @captures)? + parameterClause: _* @params + returnClause: (returnClause type: @ret)?)? + statements: _* @body) => (function_expr modifier: {attrs} @@ -655,51 +659,37 @@ fn translation_rules() -> Vec> { return_type: {ret} body: (block stmt: {body})) ), - // capture_list_item with ownership modifier (e.g. [weak self], [unowned x]) + // A closure capture (`[weak self]`, `[x]`, `[y = expr]`). The optional + // ownership specifier (`weak`/`unowned`) becomes a modifier; the + // captured name becomes the bound `name_pattern`; an explicit capture + // initializer (`[y = expr]`) becomes the bound value. rule!( - (capture_list_item ownership: _? @ownership name: @name value: _? @val) + (closureCapture + specifier: (closureCaptureSpecifier specifier: @@spec)? + name: @@name + initializer: (initializerClause value: @val)?) => (variable_declaration - modifier: {ownership} + modifier: {spec.map(|s| tree!((modifier #{s})))} pattern: (name_pattern identifier: (identifier #{name})) value: {val}) ), - // Lambda parameter with type and optional external name - rule!( - (lambda_parameter external_name: @ext name: @name type: @ty) - => - (parameter - external_name: (identifier #{ext}) - pattern: (name_pattern identifier: (identifier #{name})) - type: {ty}) - ), - rule!( - (lambda_parameter name: @name type: @ty) - => - (parameter - pattern: (name_pattern identifier: (identifier #{name})) - type: {ty}) - ), + // A closure parameter clause (`(x: Int, y)`) unwraps to its parameters. + rule!((closureParameterClause parameters: _* @params) => parameter* { params }), + // A closure parameter (`x: Int`, or just `x`). Unlike a function + // parameter it has no external label; the type is optional. rule!( - (lambda_parameter external_name: @ext name: @name) + (closureParameter firstName: @name type: _? @ty) => - (parameter - external_name: (identifier #{ext}) - pattern: (name_pattern identifier: (identifier #{name}))) + (parameter pattern: (name_pattern identifier: (identifier #{name})) type: {ty}) ), + // A shorthand closure parameter (`x` in `{ x, y in … }`): a bare name + // with no parentheses and no type. rule!( - (lambda_parameter name: @name) + (closureShorthandParameter name: @name) => (parameter pattern: (name_pattern identifier: (identifier #{name}))) ), - // Call expression with trailing closure (no value_arguments) - rule!( - (call_expression function: @func suffix: (call_suffix lambda: (lambda_literal) @closure)) - => - (call_expr - callee: {func} - argument: (argument value: {closure})) - ), // ---- Control flow ---- // If statement rule!( From 2de1549f52de532234660e8c51ec0f9dbb126a98 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 20 Jul 2026 11:48:48 +0000 Subject: [PATCH 12/27] unified: Port control-flow and pattern rules to swift-syntax Retarget `if`/`guard`/`switch`/ternary and the case/binding patterns to the swift-syntax AST, output unchanged. swift-syntax distinguishes a binding pattern (`valueBindingPattern`, `let x`) from a match pattern (`expressionPattern`, `someConstant`) by node kind, so the tree-sitter path's context-based `in_binding_pattern` disambiguation is removed: - `ifExpr`/`guardStmt`/`ternaryExpr` -> `if_expr`/`guard_if_stmt`/`if_expr`; `switchExpr` + `switchCase` -> `switch_expr` + `switch_case` (comma cases become an `or_pattern`); `conditionElement`/`switchCaseItem` unwrap; a statement-position `if`/`switch`/`do` is unwrapped from its `expressionStmt`. - `optionalBindingCondition` (`if let`) and `matchingPatternCondition` (`if case`) -> `pattern_guard_expr`. - An `expressionPattern` wrapping a leading-dot or qualified call -> `constructor_pattern` (setting `ctx.in_pattern`); `valueBindingPattern` unwraps; a bare `expressionPattern` -> `expr_equality_pattern`; a wildcard (`discardAssignmentExpr`) -> `ignore_pattern`; a `tupleExpr` match pattern -> `tuple_pattern`; `isTypePattern` (`case is T`) -> `unsupported_node`. - The `labeledExpr` argument rules gain `in_pattern`-aware pattern variants so an enum-case pattern's arguments (`case .foo(let x, _)`) become `pattern_element`s. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 250 ++++++++++-------- 1 file changed, 142 insertions(+), 108 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index a12e42e8ffc3..752e5ae90be5 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -22,11 +22,9 @@ struct SwiftContext { /// accessor inner rules. property_type: Option, /// Translated outer modifiers to attach to each child of a flattening - /// outer rule. Set by `property_declaration`, `binding_pattern`, - /// `enum_entry`, and `protocol_property_declaration`. For `let`/`var` - /// declarations and `binding_pattern`s the list is led by the binding - /// modifier, which also serves as the "this is a binding" signal for - /// pattern translation (see `in_binding_pattern`). + /// outer rule — e.g. the `let`/`var` binding modifier on each + /// `patternBinding` of a `variableDecl`, or the binding modifier on each + /// accessor of a property. outer_modifiers: Vec, /// True when the current child of a flattening outer rule is not /// the first one — its inner rule should emit a @@ -41,21 +39,15 @@ struct SwiftContext { /// `functionType` rules each set it for their direct children, so nested /// types are translated in the correct context. in_function_type: bool, + /// True while translating the argument list of an enum-case + /// `constructor_pattern` (e.g. `case .foo(let x, 3)`). Read by the + /// `labeledExpr` rules so a bare expression argument becomes an + /// `expr_equality_pattern` (wrapped in a `pattern_element`) rather than a + /// call `argument`. + in_pattern: bool, } impl SwiftContext { - /// Whether the pattern currently being translated is a binding - /// (the LHS of a `let`/`var` declaration or a `binding_pattern`). - /// - /// True exactly when an enclosing binding has published its modifier into - /// `outer_modifiers`. This is reliable because non-binding subtrees - /// (bodies, initializer values, ...) are translated after resetting the - /// context (see `reset`), so a bare identifier only sees a - /// non-empty `outer_modifiers` when it really is a binding. - fn in_binding_pattern(&self) -> bool { - !self.outer_modifiers.is_empty() - } - /// Clear the context fields that must not propagate into an /// expression / statement / body subtree. /// @@ -469,34 +461,33 @@ fn translation_rules() -> Vec> { => (name_pattern identifier: (identifier #{name})) ), - // Pattern with 'let' or 'var' binding: publish the binding modifier - // into `ctx` and translate the inner pattern under it. - rule!( - (pattern kind: (binding_pattern binding: (value_binding_pattern mutability: @@binding_kind) pattern: @@pattern)) - => - pattern* { - let binding_text = ctx.ast.source_text(binding_kind); - let binding = ctx.literal("modifier", &binding_text); - ctx.outer_modifiers = vec![binding]; - ctx.translate(pattern)? + // A `let`/`var` value-binding pattern (`let x`) inside a case or `if case` + // introduces a new binding; it unwraps to its inner pattern (a + // `name_pattern`). + rule!((valueBindingPattern pattern: @p) => pattern { p }), + // An enum-case pattern with associated values (`case .foo(let x)`, + // `case Color.foo(let x)`) is an expression pattern wrapping a call of a + // member access. It becomes a `constructor_pattern`; its arguments are + // translated as pattern elements (see the `labeledExpr` rules, gated by + // `ctx.in_pattern`). Matched before the generic `expressionPattern` rule. + // The base is optional: a leading-dot form (`.foo`) has none, so the + // constructor's base is an `inferred_type_expr`. + rule!( + (expressionPattern expression: (functionCallExpr + calledExpression: (memberAccessExpr base: _? @base period: @dot declName: (declReferenceExpr baseName: @name)) + arguments: _* @@args)) + => + constructor_pattern { + ctx.in_pattern = true; + let elements = ctx.translate(args)?; + let base = base.unwrap_or_else(|| tree!((inferred_type_expr #{dot}))); + tree!((constructor_pattern + constructor: (member_access_expr + base: {base} + member: (identifier #{name})) + element: {elements})) } ), - // case T.foo(x,y) pattern - rule!( - (pattern kind: (case_pattern type: @typ name: @name arguments: (tuple_pattern item: (tuple_pattern_item)* @items)? )) - => - (constructor_pattern - constructor: (member_access_expr base: {typ} member: (identifier #{name})) - element: {items}) - ), - // case .foo(x,y) pattern - rule!( - (pattern kind: (case_pattern dot: @dot name: @name arguments: (tuple_pattern item: (tuple_pattern_item)* @items)? )) - => - (constructor_pattern - constructor: (member_access_expr base: (inferred_type_expr #{dot}) member: (identifier #{name})) - element: {items}) - ), // A tuple destructuring pattern (`let (a, b) = …`). A labelled element // (`let (x: a) = …`) carries its label through as the `pattern_element` // key; unlabelled elements have no key. @@ -506,30 +497,41 @@ fn translation_rules() -> Vec> { => (pattern_element key: {label.map(|l| tree!((identifier #{l})))} pattern: {p}) ), - // Type casting pattern (TODO) - rule!((pattern kind: (type_casting_pattern)) => (unsupported_node)), - // Wildcard pattern - rule!((pattern kind: (wildcard_pattern)) => (ignore_pattern)), - // A bare identifier used as an expression-pattern. Under a `var`/`let` - // binding it introduces a new variable and becomes a `name_pattern`; - // otherwise it matches by equality and is left as an `expr_equality_pattern` - // over the name expression. - rule!( - (pattern kind: (simple_identifier) @name) - => - pattern { - if ctx.in_binding_pattern() { - tree!((name_pattern identifier: (identifier #{name}))) - } else { - let expr = tree!((name_expr identifier: (identifier #{name}))); - tree!((expr_equality_pattern expr: {expr})) - } + // A type-casting pattern (`case is T`). Not yet supported, so it is + // mapped to `unsupported_node` — an explicit reminder that this needs + // handling in the future. (Redundant with the catch-all fallback, but + // kept as a signpost.) + rule!((isTypePattern) => (unsupported_node)), + // A standalone wildcard pattern (`case _:`, `if case _`): swift-syntax + // models the bare `_` as an `expressionPattern` wrapping a + // `discardAssignmentExpr`. Matched before the generic `expressionPattern` + // rule so `_` becomes an `ignore_pattern` rather than an equality match. + // (Wildcards *inside* an enum-case argument list are handled by the + // `labeledExpr`/`discardAssignmentExpr` rules.) + rule!((expressionPattern expression: (discardAssignmentExpr)) => (ignore_pattern)), + // A wildcard *binding* pattern (`let _ = x`, `for _ in xs`). swift-syntax + // models this as a `wildcardPattern` — distinct from the `_` *match* + // pattern above, which is an `expressionPattern` over a + // `discardAssignmentExpr`. + rule!((wildcardPattern) => (ignore_pattern)), + // A tuple pattern in a match position (`case (let a, 3):`) is parsed by + // swift-syntax as an `expressionPattern` wrapping a `tupleExpr` — unlike a + // binding tuple (`let (a, b)`), which is a real `tuplePattern`. Recognise + // it as a `tuple_pattern`; its `labeledExpr` elements translate to + // `pattern_element`s under `ctx.in_pattern` (a binding element becomes a + // `name_pattern`, any other expression an `expr_equality_pattern`). + rule!( + (expressionPattern expression: (tupleExpr elements: _* @@els)) + => + tuple_pattern { + ctx.in_pattern = true; + let elements = ctx.translate(els)?; + tree!((tuple_pattern element: {elements})) } ), - // Expression pattern - // We lack a way to check if 'expr' is actually an expression, but due to rule ordering - // the 'expression' case is the only remaining possibility when this rule tries to match. - rule!((pattern kind: @expr) => (expr_equality_pattern expr: {expr})), + // A bare expression pattern (`case 1:`, `case someConstant:`) matches by + // equality. + rule!((expressionPattern expression: @e) => (expr_equality_pattern expr: {e})), // ---- Functions ---- // A function declaration (parameters/return type/body optional). The // parameters and return type nest under `signature`; the body is a @@ -602,18 +604,38 @@ fn translation_rules() -> Vec> { => (call_expr callee: {callee} argument: {args}) ), - // A call argument keeps its optional label as the `name` and its value. - // (Enum-case pattern arguments reuse `labeledExpr` too; that handling is - // added with the switch/pattern rules.) + // A call argument or an enum-case pattern argument. When translating an + // enum-case `constructor_pattern`'s arguments (`ctx.in_pattern`), a + // `patternExpr` argument (`let x`) becomes a bound `name_pattern`, a + // wildcard (`_`) becomes an `ignore_pattern`, and any other expression + // becomes an `expr_equality_pattern`; each is wrapped in a + // `pattern_element` carrying the optional argument label as its `key`. + // Otherwise the argument keeps its label as the `name` and its value. + // The pattern-only shapes (`patternExpr`, `discardAssignmentExpr`) are + // matched first; they never occur as ordinary call arguments. rule!( - (labeledExpr label: @lbl expression: @val) + (labeledExpr label: _? @lbl expression: (patternExpr pattern: @p)) => - (argument name: (identifier #{lbl}) value: {val}) + (pattern_element key: {lbl.map(|l| tree!((identifier #{l})))} pattern: {p}) ), rule!( - (labeledExpr expression: @val) + (labeledExpr label: _? @lbl expression: (discardAssignmentExpr) @@wildcard) => - (argument value: {val}) + (pattern_element key: {lbl.map(|l| tree!((identifier #{l})))} pattern: (ignore_pattern #{wildcard})) + ), + rule!( + (labeledExpr label: _? @lbl expression: @val) + => + argument { + let key = lbl.map(|l| tree!((identifier #{l}))); + if ctx.in_pattern { + tree!((pattern_element + key: {key} + pattern: (expr_equality_pattern expr: {val}))) + } else { + tree!((argument name: {key} value: {val})) + } + } ), // Member access (`list.append`). The `declName` is itself a // `declReferenceExpr`; pull its `baseName` out as the member identifier. @@ -691,68 +713,77 @@ fn translation_rules() -> Vec> { (parameter pattern: (name_pattern identifier: (identifier #{name}))) ), // ---- Control flow ---- - // If statement + // An `if`/`else` expression. Conditions are joined via `and_chain`; the + // then-body and optional else-body (another block, or an `ifExpr` for an + // else-if chain) are translated recursively. rule!( - (if_statement condition: _* @cond body: @then_body else_branch: _? @else_stmts) + (ifExpr conditions: _* @cond body: @then_body elseBody: _? @else_stmts) => (if_expr condition: {and_chain(&mut ctx, cond)} then: {then_body} else: {else_stmts}) ), - // Guard statement + // A `guard … else { }` statement. The `body` is the else block. rule!( - (guard_statement condition: _* @cond body: (block statement: _* @else_stmts)) + (guardStmt conditions: _* @cond body: @else_stmts) => (guard_if_stmt condition: {and_chain(&mut ctx, cond)} - else: (block stmt: {else_stmts})) + else: {else_stmts}) ), - // Ternary expression → if_expr + // Ternary (`c ? a : b`) desugars to an `if_expr`, as in the tree-sitter + // path. rule!( - (ternary_expression condition: @cond if_true: @then_val if_false: @else_val) + (ternaryExpr condition: @cond thenExpression: @then_val elseExpression: @else_val) => (if_expr condition: {cond} then: {then_val} else: {else_val}) ), - // Switch statement + // A `switch` statement. Each `switchCase` becomes a `switch_case` with a + // pattern (or an `or_pattern` for comma-separated `case a, b:`) and a + // body; a `default:` case has a body but no pattern. The case items and + // body are auto-translated; the Rust block only picks the pattern shape + // by arity (the query engine can't branch on list length). rule!( - (switch_statement expr: @val entry: (switch_entry)* @cases) + (switchExpr subject: @val cases: _* @cases) => (switch_expr value: {val} case: {cases}) ), - // Switch entry with multiple patterns and body rule!( - (switch_entry - pattern: (switch_pattern pattern: @first) - pattern: (switch_pattern pattern: @rest)+ - statement: _* @body) + (switchCase label: (switchCaseLabel caseItems: _* @items) statements: _* @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 { + let pattern = if items.len() == 1 { + items[0] + } else { + tree!((or_pattern pattern: {items})) + }; + tree!((switch_case pattern: {pattern} body: (block stmt: {body}))) + } ), - // Switch entry: default case (no patterns) rule!( - (switch_entry default: (default_keyword) statement: _* @body) + (switchCase label: (switchDefaultLabel) statements: _* @body) => (switch_case body: (block stmt: {body})) ), - // if case PATTERN = expr — preserve the pattern directly (no Optional wrapping) + // A single case item unwraps to its pattern (used as an `or_pattern` + // element). + rule!((switchCaseItem pattern: @p) => pattern { p }), + // A pattern-matching condition (`if case let x = e`, `if case .foo(let x) + // = e`) becomes a `pattern_guard_expr`: the matched pattern and the + // scrutinee value are translated recursively. rule!( - (if_let_binding "case" pattern: @pat value: @val) + (matchingPatternCondition pattern: @pat initializer: (initializerClause value: @val)) => - (pattern_guard_expr - value: {val} - pattern: {pat}) + (pattern_guard_expr pattern: {pat} value: {val}) ), + // Optional binding (`if let x = foo`, or shorthand `if let x`) desugars + // to a `pattern_guard_expr` matching `Optional.some(x)`, exactly as the + // tree-sitter path does. The initialized form is matched first. rule!( - (if_let_binding - pattern: (pattern binding: (value_binding_pattern) bound_identifier: @name) - value: @val) + (optionalBindingCondition + pattern: (identifierPattern identifier: @name) + initializer: (initializerClause value: @val)) => (pattern_guard_expr value: {val} @@ -760,10 +791,8 @@ fn translation_rules() -> Vec> { constructor: (member_access_expr base: (named_type_expr name: (identifier "Optional")) member: (identifier "some")) element: (pattern_element pattern: (name_pattern identifier: (identifier #{name}))))) ), - // Shorthand if let x (Swift 5.7+) — also semantically .some(x) rule!( - (if_let_binding - pattern: (pattern binding: (value_binding_pattern) bound_identifier: @name)) + (optionalBindingCondition pattern: (identifierPattern identifier: @name)) => (pattern_guard_expr value: (name_expr identifier: (identifier #{name})) @@ -771,8 +800,13 @@ fn translation_rules() -> Vec> { constructor: (member_access_expr base: (named_type_expr name: (identifier "Optional")) member: (identifier "some")) element: (pattern_element pattern: (name_pattern identifier: (identifier #{name}))))) ), - // If-condition — unwrap (pass through the inner expression/pattern) - rule!((if_condition kind: @inner) => expr_or_pattern { inner }), + // A single condition in an `if`/`while`/`guard` condition list unwraps to + // its inner expression; `and_chain` joins multiple with `&&`. + rule!((conditionElement condition: @c) => expr { c }), + // `if`/`switch`/`do` are expressions in Swift; when used as a statement + // swift-syntax wraps them in an `expressionStmt`. Unwrap to the inner + // expression (a plain expression statement, e.g. a call, is not wrapped). + rule!((expressionStmt expression: @e) => expr { e }), // ---- Loops ---- // For-in loop with optional where-clause guard. rule!( From 2fcbc9b728c4749da69c4af227e3a04037c0fb07 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 20 Jul 2026 12:00:28 +0000 Subject: [PATCH 13/27] unified: Port loop rules to swift-syntax Retarget loops to the swift-syntax AST, output unchanged: `forStmt` -> `for_each_stmt` (the optional `where` clause becomes the `guard`), `whileStmt` -> `while_stmt`, `repeatStmt` -> `do_while_stmt`, and `labeledStmt` -> `labeled_stmt`. Unlike the tree-sitter grammar, swift-syntax stores a labeled statement's label and colon as separate tokens, so the label token is already the bare name (no trailing `:` to strip). A `repeat`-`while` loop has a single condition in swift-syntax (not a condition list), so it needs no `and_chain`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 752e5ae90be5..e0c2f66e2101 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -808,42 +808,42 @@ fn translation_rules() -> Vec> { // expression (a plain expression statement, e.g. a call, is not wrapped). rule!((expressionStmt expression: @e) => expr { e }), // ---- Loops ---- - // For-in loop with optional where-clause guard. + // A `for`-`in` loop. The optional `where` clause becomes the `guard`. rule!( - (for_statement - item: @pat - collection: @iter - where: (where_clause expr: @guard)? - body: (block statement: _* @body)) + (forStmt + pattern: @pat + sequence: @iter + whereClause: (whereClause condition: @guard)? + body: @body) => (for_each_stmt pattern: {pat} iterable: {iter} guard: {guard} - body: (block stmt: {body})) + body: {body}) ), - // While loop + // A `while` loop. rule!( - (while_statement condition: _* @cond body: (block statement: _* @body)) + (whileStmt conditions: _* @cond body: @body) => (while_stmt condition: {and_chain(&mut ctx, cond)} - body: (block stmt: {body})) + body: {body}) ), - // Repeat-while loop + // A `repeat { } while c` loop desugars to a `do_while_stmt`. rule!( - (repeat_while_statement condition: _* @cond body: (block statement: _* @body)) + (repeatStmt body: @body condition: @cond) => - (do_while_stmt - condition: {and_chain(&mut ctx, cond)} - body: (block stmt: {body})) + (do_while_stmt condition: {cond} body: {body}) + ), + // A labeled statement (`outer: for … { }`). swift-syntax stores the + // label and colon as separate tokens, so the label token is already the + // bare name (no trailing `:` to strip). + rule!( + (labeledStmt label: @@lbl statement: @stmt) + => + (labeled_stmt label: (identifier #{lbl}) stmt: {stmt}) ), - // Labeled statement (e.g. `outer: for ...`). Strip the trailing ':' from the label token. - rule!((labeled_statement label: (statement_label) @lbl statement: @stmt) => labeled_stmt { - let text = ctx.ast.source_text(lbl); - let name = &text[..text.len() - 1]; - tree!((labeled_stmt label: (identifier #{name}) stmt: {stmt})) - }), // ---- Collections ---- // Array literal rule!((array_literal element: _* @elems) => (array_literal element: {elems})), From 4c764b52dc177cbf5565d3fb8f86e4c8f5518cb5 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 20 Jul 2026 12:11:54 +0000 Subject: [PATCH 14/27] unified: Port collection rules to swift-syntax Retarget collection literals and subscripts to the swift-syntax AST, output unchanged: `arrayExpr` -> `array_literal` (each `arrayElement` unwraps to its expression); `dictionaryExpr` -> an opaque `map_literal` leaf (its source span, matching the tree-sitter path); and `subscriptCallExpr` (`xs[0]`) -> `call_expr`, mirroring the tree-sitter grammar's treatment of a subscript as a call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index e0c2f66e2101..2068cca7a64a 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -845,20 +845,24 @@ fn translation_rules() -> Vec> { (labeled_stmt label: (identifier #{lbl}) stmt: {stmt}) ), // ---- Collections ---- - // Array literal - 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 + // An array literal (`[1, 2, 3]`). Each `arrayElement` unwraps to its + // contained expression. rule!( - (dictionary_literal key: _* @keys value: _* @vals) + (arrayExpr elements: _* @els) => - (map_literal element: {keys.into_iter().zip(vals).map(|(k, v)| - tree!((key_value_pair key: {k} value: {v})) - )}) + (array_literal element: {els}) + ), + rule!((arrayElement expression: @e) => expr { e }), + // A dictionary literal (`["a": 1]`) is kept as an opaque `map_literal` + // leaf (its source span), matching the tree-sitter path. + rule!((dictionaryExpr) => (map_literal)), + // A subscript access (`xs[0]`) is modelled as a call, exactly as the + // tree-sitter grammar does (it parses `xs[0]` like `xs(0)`). + rule!( + (subscriptCallExpr calledExpression: @callee arguments: _* @args) + => + (call_expr callee: {callee} argument: {args}) ), - 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 rule!((optional_chain_marker expr: @inner) => expr { inner }), From 6275977bc53257fe84c5801b40051c38ccd0dc79 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 20 Jul 2026 13:15:11 +0000 Subject: [PATCH 15/27] unified: Port optional and error-handling rules to swift-syntax Retarget optional chaining, `try`, `do`/`catch`, casts, type tests, `await`, and force-unwrap to the swift-syntax AST, output unchanged: - `optionalChainingExpr` (`x?`) unwraps transparently (the enclosing member access / call carries the semantics). - `tryExpr` -> prefix `unary_expr`; swift-syntax splits the operator into a `try` keyword and an optional `?`/`!` mark, recombined into one `prefix_operator` spelling. - `doStmt` -> `try_expr` with `catchClause` -> `catch_clause`; a `catch` binds the first `catchItem`'s pattern and optional `where` guard. - `asExpr` (`x as`/`as?`/`as!` `T`) -> `type_cast_expr`, `isExpr` (`x is T`) -> `type_test_expr`, and `awaitExpr` -> prefix `unary_expr`. - `forceUnwrapExpr` (`x!`) -> postfix `unary_expr` (swift-syntax has a dedicated node; the tree-sitter path used the generic postfix operator). A couple of rewritten rules keep their opening inlined so this commit's diff stays readable; a later commit restores the canonical formatting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 2068cca7a64a..dfddfa9c66ad 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -865,57 +865,52 @@ fn translation_rules() -> Vec> { ), // ---- Optionals and errors ---- // Optional chaining — unwrap the marker - rule!((optional_chain_marker expr: @inner) => expr { inner }), + rule!((optionalChainingExpr expression: @inner) => expr { inner }), // try/try?/try! expr → unary_expr with operator "try", "try?" or "try!" - rule!((try_expression (try_operator) @op expr: @inner) => (unary_expr operator: (prefix_operator #{op}) operand: {inner})), - rule!((try_expression operator: (try_operator) @op expr: @inner) => (unary_expr operator: (prefix_operator #{op}) operand: {inner})), + rule!( + (tryExpr questionOrExclamationMark: _? @@m expression: @e) + => + expr { + let op = format!("try{}", m.map(|m| ctx.source_text(m)).unwrap_or_default()); + tree!((unary_expr operator: (prefix_operator #{op}) operand: {e})) + } + ), // Do-catch → try_expr rule!( - (do_statement body: (block statement: _* @body) catch: (catch_block)* @catches) + (doStmt body: @body catchClauses: _* @catches) => (try_expr - body: (block stmt: {body}) + body: {body} catch_clause: {catches}) ), // Catch block with bound identifier; optional where-clause guard. rule!( - (catch_block - keyword: (catch_keyword) - error: @pattern - where: (where_clause expr: @guard)? - body: (block statement: _* @body)) + (catchClause + catchItems: (catchItem + pattern: @pattern + whereClause: (whereClause condition: @guard)?) + body: @body) => (catch_clause pattern: {pattern} guard: {guard} - body: (block stmt: {body})) + body: {body}) ), // Catch block without error binding - rule!( - (catch_block keyword: (catch_keyword) body: (block statement: _* @body)) - => - (catch_clause body: (block stmt: {body})) - ), - // Empty catch block: catch {} - rule!( - (catch_block (catch_keyword)) - => - (catch_clause body: (block)) - ), - // Catch block with unhandled pattern — preserve pattern; optional body. - rule!( - (catch_block keyword: (catch_keyword) error: @pat body: (block statement: _* @body)) - => - (catch_clause - pattern: {pat} - body: (block stmt: {body})) - ), + rule!((catchClause body: @body) => (catch_clause body: {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})), + rule!((asExpr expression: @val questionOrExclamationMark: _? @@mark type: @ty) => type_cast_expr { + let op = format!("as{}", mark.map(|m| ctx.source_text(m)).unwrap_or_default()); + tree!((type_cast_expr expr: {val} operator: (infix_operator #{op}) type: {ty})) + }), // Check expression (`x is T`) → type_test_expr - rule!((check_expression op: @op target: @val type: @ty) => (type_test_expr expr: {val} operator: (infix_operator #{op}) type: {ty})), + rule!((isExpr expression: @val type: @ty) => (type_test_expr expr: {val} operator: (infix_operator "is") type: {ty})), // Await expression → unary_expr with operator "await" - rule!((await_expression expr: @val) => (unary_expr operator: (prefix_operator "await") operand: {val})), + rule!((awaitExpr expression: @val) => (unary_expr operator: (prefix_operator "await") operand: {val})), + // Force-unwrap (`x!`) → postfix unary_expr. swift-syntax has a dedicated + // `forceUnwrapExpr` node (the tree-sitter path used the generic postfix + // operator rule instead). + rule!((forceUnwrapExpr expression: @e) => (unary_expr operator: (postfix_operator "!") operand: {e})), // A multi-part identifier (for example `Foo.Bar.Baz`) is translated to // a member_access_expr chain with a name_expr base. rule!( From 37654b4bcc8398a96913932ac61629e2aa900d80 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 20 Jul 2026 13:49:55 +0000 Subject: [PATCH 16/27] unified: Port import rules to swift-syntax Retarget imports to the swift-syntax AST, output unchanged. swift-syntax represents the dotted path as a list of `importPathComponent`s, folded into a `name_expr`/`member_access_expr` chain via `member_chain`. A single rule handles both forms via an optional `importKindSpecifier`: a scoped import (`import struct Foo.Bar`) has one and binds the last path component as a `name_pattern`; a plain import (`import Foundation`) has none and uses a `bulk_importing_pattern`. Leading attributes (`@_exported`) and access modifiers (`public`) become `modifier`s. The tree-sitter multi-part `identifier` rule is dropped: swift-syntax qualified names are already `memberAccessExpr` chains, and import paths are `importPathComponent`s. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index dfddfa9c66ad..148a2df2218e 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -911,35 +911,37 @@ fn translation_rules() -> Vec> { // `forceUnwrapExpr` node (the tree-sitter path used the generic postfix // operator rule instead). rule!((forceUnwrapExpr expression: @e) => (unary_expr operator: (postfix_operator "!") operand: {e})), - // A multi-part identifier (for example `Foo.Bar.Baz`) is translated to - // a member_access_expr chain with a name_expr base. - rule!( - (identifier part: _+ @parts) - => - expr { member_chain(&mut ctx, parts) } - ), - // Scoped import declaration (for example `import struct Foo.Bar`): - // flatten the identifier parts into a member_access_expr and bind the - // final segment as a name_pattern. - rule!( - (import_declaration scoped_import_kind: @kind name: (identifier part: _+ @parts) @name modifiers: (modifiers)? @mods) - => - (import_declaration - pattern: (name_pattern identifier: (identifier #{parts.last().unwrap()})) - imported_expr: {name} - modifier: (modifier #{kind}) - modifier: {mods}) - ), - // Non-scoped import declaration (for example `import Foundation`): - // flatten the identifier parts into a member_access_expr and use a - // bulk_importing_pattern. - rule!( - (import_declaration name: @name modifiers: (modifiers)? @mods) - => - (import_declaration - pattern: (bulk_importing_pattern) - imported_expr: {name} - modifier: {mods}) + // ---- Imports ---- + // An import declaration. The dotted path (a list of + // `importPathComponent`s) becomes a `name_expr`/`member_access_expr` + // chain (via `member_chain`). A scoped import (`import struct Foo.Bar`) + // has an `importKindSpecifier` and binds the last path component as a + // `name_pattern`; a plain import (`import Foundation`) has none and uses + // a `bulk_importing_pattern` spanning the whole declaration. Any leading + // attributes (`@_exported`) and access modifiers (`public`) become + // `modifier`s. + rule!( + (importDecl + attributes: _* @attrs + modifiers: _* @mods + importKindSpecifier: _? @@kind + path: (importPathComponent name: @@parts)*) + => + import_declaration { + let pattern = match kind { + Some(_) => { + let last = *parts.last().ok_or("import has no path")?; + tree!((name_pattern identifier: (identifier #{last}))) + } + None => tree!((bulk_importing_pattern)), + }; + tree!((import_declaration + modifier: {kind.map(|k| tree!((modifier #{k})))} + modifier: {attrs} + modifier: {mods} + pattern: {pattern} + imported_expr: {member_chain(&mut ctx, parts)})) + } ), // ---- Types and classes ---- // Self expression → name_expr From 8e6651aa9b3cd184fde4bae35018180dbe24d4b8 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 20 Jul 2026 14:44:51 +0000 Subject: [PATCH 17/27] unified: Port type-container declarations to swift-syntax Retarget the nominal type declarations to the swift-syntax AST, output unchanged. `classDecl`/`structDecl`/`enumDecl`/`protocolDecl`/`extensionDecl` each become a `class_like_declaration` tagged with a modifier naming the declaration keyword, with members drawn from the `memberBlock`; each `memberBlockItem` unwraps to its contained declaration. `superExpr` maps to `super_expr`; `self` needs no rule (swift-syntax models it as an ordinary `declReferenceExpr`, already a `name_expr`). Following the tree-sitter path (PARITY), the inheritance clause is not emitted as a `base_type` (the tree-sitter rule captured it positionally, but the grammar nests it under a field, so it never actually matched); swift-syntax exposes it cleanly, so that is a correctness improvement to make once tree-sitter is retired. The tree-sitter grammar's standalone `self`, dead modifier (`visibility_modifier`, etc.), key-path, and inheritance-specifier rules are dropped; `#selector`/`#keyPath` (now a `macroExpansionExpr`) stays an `unsupported_node`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 95 ++++++++----------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 148a2df2218e..3b8d8e913f55 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -943,26 +943,15 @@ fn translation_rules() -> Vec> { imported_expr: {member_chain(&mut ctx, parts)})) } ), - // ---- Types and classes ---- - // Self expression → name_expr - rule!((self_expression) => (name_expr identifier: (identifier "self"))), - // Super expression → super_expr - rule!((super_expression) => (super_expr)), - // Modifiers — unwrap to individual modifier children - rule!((modifiers _* @mods) => modifier* { mods }), + // ---- Types and declarations ---- + // A leading attribute (`@objc`) or access/function/member/mutation/ + // ownership modifier (swift-syntax models each as a single `declModifier`) + // becomes a `modifier`; its source text is the modifier spelling. rule!((attribute) @m => (modifier #{m})), - // swift-syntax models every access/function/member/mutation/ownership - // modifier as a single `declModifier`; its source text is the modifier. rule!((declModifier) @m => (modifier #{m})), - rule!((visibility_modifier) @m => (modifier #{m})), - rule!((function_modifier) @m => (modifier #{m})), - rule!((member_modifier) @m => (modifier #{m})), - rule!((mutation_modifier) @m => (modifier #{m})), - rule!((ownership_modifier) @m => (modifier #{m})), - rule!((property_modifier) @m => (modifier #{m})), - rule!((parameter_modifier) @m => (modifier #{m})), - rule!((inheritance_modifier) @m => (modifier #{m})), - rule!((property_behavior_modifier) @m => (modifier #{m})), + // A `super` expression. (`self` needs no rule: swift-syntax models it as + // an ordinary `declReferenceExpr`, already mapped to a `name_expr`.) + rule!((superExpr) => (super_expr)), // Type expressions. A generic type applied with explicit arguments // (`Set`) is represented opaquely, using the whole source text as // the name (PARITY(tree-sitter): the generic arguments are not @@ -1041,77 +1030,71 @@ fn translation_rules() -> Vec> { } ), // Selector expression: `#selector(inner)` -- not yet supported - rule!( - (selector_expression _ @inner) - => - (unsupported_node) - ), - // Key path expressions are currently unsupported. - rule!((key_path_expression) => (unsupported_node)), - // Inheritance specifier → base_type - rule!((inheritance_specifier inherits_from: @ty) => (base_type type: {ty})), + // (swift-syntax represents `#selector`/`#keyPath` and other macro + // expansions uniformly as a `macroExpansionExpr`). + rule!((macroExpansionExpr) => (unsupported_node)), + // PARITY(tree-sitter): a nominal type's `inheritanceClause` (`: Base, + // Proto`) is not emitted as a `base_type` — the tree-sitter path drops + // it (no corpus target has a `base_type`). swift-syntax exposes it + // cleanly, so emitting `base_type` is a correctness improvement to make + // once tree-sitter is retired. Each declaration keyword gets its own + // rule; the bodies are identical but for the keyword. // Class declaration with body containing members rule!( - (class_declaration - declaration_kind: @kind - name: @name - body: (class_body member: _* @members) - (inheritance_specifier)* @bases - (modifiers)* @mods) + (classDecl classKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) - base_type: {bases} member: {members}) ), // Enum class declaration: same as a regular class but with an enum body. rule!( - (class_declaration - declaration_kind: @kind - name: @name - body: (enum_class_body member: _* @members) - (inheritance_specifier)* @bases - (modifiers)* @mods) + (enumDecl enumKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) - base_type: {bases} member: {members}) ), - // Class declaration with empty body + // A `struct` declaration. rule!( - (class_declaration - declaration_kind: @kind - name: @name - body: _ - (inheritance_specifier)* @bases - (modifiers)* @mods) + (structDecl structKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) - base_type: {bases}) + member: {members}) ), // Protocol declaration rule!( - (protocol_declaration - name: @name - body: (protocol_body member: _* @members) - (inheritance_specifier)* @bases - (modifiers)* @mods) + (protocolDecl protocolKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) => (class_like_declaration - modifier: (modifier "protocol") + modifier: (modifier #{kind}) + modifier: {mods} + name: (identifier #{name}) + member: {members}) + ), + // An `extension Foo { … }` is likewise a `class_like_declaration`, named + // by the extended type. The extended type is captured opaquely (as its + // source text) so that qualified names (`extension String.Interpolation`, + // a `memberType`) name the declaration just like simple ones, matching the + // old tree-sitter `user_type` behaviour. + rule!( + (extensionDecl extensionKeyword: @kind modifiers: _* @mods extendedType: @@name memberBlock: (memberBlock members: _* @members)) + => + (class_like_declaration + modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) - base_type: {bases} member: {members}) ), + // A member of a type declaration unwraps to the contained declaration. + rule!((memberBlockItem decl: _* @d) => member* { d }), // Protocol function — return type and body statements both optional. rule!( (protocol_function_declaration From ec0c49a0535f67ae80ad373848298b2daaaa0541 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 22 Jul 2026 14:28:48 +0000 Subject: [PATCH 18/27] unified: Port property accessor rules to swift-syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retarget property accessors to the swift-syntax AST, output unchanged. An accessor-bearing `variableDecl` publishes the property name/type into `ctx`; a computed property (`var v: T { get set }`) emits accessors carrying the type, while a stored property with observers (`var x = e { didSet {…} }`) emits the backing `variable_declaration` first. swift-syntax models get/set/willSet/didSet uniformly as `accessorDecl`, so a single `accessorDecl` rule — with an optional body distinguishing a computed accessor from a bodyless protocol requirement — replaces the tree-sitter grammar's separate computed-getter/setter/modify, willset/didset, and getter-/setter-specifier rules. The tree-sitter protocol property and function requirement rules (`protocol_property_declaration`, `protocol_function_declaration`) are dropped: swift-syntax models those requirements as ordinary `variableDecl`s (with bodyless accessors) and `functionDecl`s, already handled by the general rules. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 368 ++++++------------ 1 file changed, 109 insertions(+), 259 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 3b8d8e913f55..2abc783b9f01 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -8,18 +8,14 @@ use yeast::{ConcreteDesugarer, DesugaringConfig, PhaseKind, Rule, rule, tree}; /// post-hoc mutation. #[derive(Clone, Default)] struct SwiftContext { - /// Identifier node for the property name. Set by the outer - /// `property_binding` (computed accessors / willSet-didSet) and - /// `protocol_property_declaration` rules before translating accessor - /// children; read by the accessor inner rules - /// (`computed_getter`/`computed_setter`/`computed_modify`/ - /// `willset_clause`/`didset_clause`/`getter_specifier`/ - /// `setter_specifier`). + /// Identifier node for the property name. Set by the accessor-bearing + /// `variableDecl` rule before translating the accessor block; read by the + /// inner `accessorDecl` rules to name each `accessor_declaration`. property_name: Option, - /// Translated type node for the property type. Set by the outer - /// `property_binding` rule (computed accessors variant) and - /// `protocol_property_declaration` when present; read by the - /// accessor inner rules. + /// Translated type node for the property type. Set (for computed + /// properties) by the accessor-bearing `variableDecl` rule; read by the + /// inner `accessorDecl` rules. Left `None` for stored properties with + /// observers, so their `willSet`/`didSet` accessors carry no type. property_type: Option, /// Translated outer modifiers to attach to each child of a flattening /// outer rule — e.g. the `let`/`var` binding modifier on each @@ -227,118 +223,123 @@ fn translation_rules() -> Vec> { 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 - // and attaches binding/modifiers from the parent. - - // Computed property with explicit accessors (get/set/modify) → a - // sequence of `accessor_declaration` nodes. The outer rule - // publishes the property's name and type into `ctx` so that each - // inner accessor rule - // (`computed_getter`/`computed_setter`/`computed_modify`) builds - // its `accessor_declaration` with `name` and `type` set from the - // start — no schema-invalid intermediate state. - // - // Toggles `ctx.is_chained` per accessor iteration: the first - // accessor inherits the outer rule's chained state (i.e. whether - // this whole property_binding is itself a non-first declarator - // of a containing property_declaration); subsequent accessors - // always emit `chained_declaration`. - rule!( - (property_binding - name: @pattern - type: _? @ty - computed_value: (computed_property accessor: _+ @@accessors)) - => - accessor_declaration* { - ctx.property_name = Some(tree!((identifier #{pattern}))); - ctx.property_type = ty; - - let mut result = Vec::new(); - for (i, acc) in accessors.into_iter().enumerate() { - if i > 0 { - ctx.is_chained = true; - } - result.extend(ctx.translate(acc)?); - } - result - } - ), - // Computed property: shorthand getter (no explicit get/set, just - // statements) → a single accessor_declaration with kind "get". - // Reads outer modifiers / chained tag from `ctx` (set by the - // outer `property_declaration` rule). + // ---- Properties with accessors ---- + // A computed property with an implicit getter (`var a: T { }`) + // becomes a single `accessor_declaration` of kind `get`. This form is + // self-contained (no context threading). It must precede the plain + // `variableDecl` rules, which would otherwise match and drop the accessor + // block. rule!( - (property_binding - name: (pattern bound_identifier: @name) - type: _? @ty - computed_value: (computed_property statement: _* @@body)) + (variableDecl + bindingSpecifier: @@spec + bindings: (patternBinding + pattern: (identifierPattern identifier: @@name) + typeAnnotation: (typeAnnotation type: @ty) + accessorBlock: (accessorBlock accessors: (codeBlockItem)+ @body))) => (accessor_declaration - modifier: {ctx.outer_modifiers.clone()} - modifier: {chained_modifier(&mut ctx)} + modifier: (modifier #{spec}) name: (identifier #{name}) type: {ty} accessor_kind: (accessor_kind "get") - body: (block stmt: {ctx.reset(); ctx.translate(body)?})) - ), - // Stored property with willSet/didSet observers (initializer - // optional) → a `variable_declaration` followed by one - // `accessor_declaration` per observer, each born with the - // property name set. Manual rule: we publish the property name - // into `ctx` before translating the observer children so the - // inner `willset_clause` / `didset_clause` rules construct - // valid `accessor_declaration` nodes from the start. + body: (block stmt: {body})) + ), + // A property with an explicit accessor block. The two shapes differ only + // by the presence of an initializer (tree-sitter split them into distinct + // `willset_didset_block` vs computed-accessor node types; swift-syntax + // makes both plain `accessorDecl`s): + // + // * With an initializer (`var x: T = e { willSet {…} didSet {…} }`) it is + // a *stored* property with observers: emit the backing + // `variable_declaration` first, then one `accessor_declaration` per + // observer (observers carry no type). + // * Without an initializer (`var v: T { get set }`, incl. protocol + // requirements) it is a *computed* property: no backing variable; the + // type is published so the get/set accessors carry it. // - // The `variable_declaration` itself inherits the outer rule's - // chained state; observers always get `chained_declaration` - // because they're subsequent outputs of this flattening rule. + // In both cases the first emitted declaration is unchained and every + // subsequent one is tagged `chained_declaration` (the `!result.is_empty()` + // test). Must precede the plain `variableDecl` rules. rule!( - (property_binding - name: (pattern bound_identifier: @name) - type: _? @ty - value: _? @@val - observers: (willset_didset_block willset: _? @@ws didset: _? @@ds)) + (variableDecl + bindingSpecifier: @@spec + bindings: (patternBinding + pattern: (identifierPattern identifier: @@name) + typeAnnotation: (typeAnnotation type: @ty) + initializer: (initializerClause value: @@val)? + accessorBlock: (accessorBlock accessors: (accessorDecl)+ @@accessors))) => member* { - // The initializer value must not inherit the binding - // context (it may contain patterns, e.g. a switch - // expression), so translate it inside a `ctx.scoped` - // block — the block receives a temporary `ctx` whose - // `user_ctx` is a clone; mutations to it are discarded - // when the block returns, so the outer `ctx` is intact - // for the observer loop below. The observers keep the - // outer context: each willSet/didSet accessor emits - // the binding modifier and, in turn, resets the - // context for its own body. - let val = ctx.scoped(|ctx| { - ctx.reset(); - ctx.translate(val) - })?; - - let var_decl = tree!( - (variable_declaration - modifier: {ctx.outer_modifiers.clone()} - modifier: {chained_modifier(&mut ctx)} - pattern: (name_pattern identifier: (identifier #{name})) - type: {ty} - value: {val}) - ); - - // Publish the property name for the observer rules. + ctx.outer_modifiers = vec![tree!((modifier #{spec}))]; ctx.property_name = Some(tree!((identifier #{name}))); - // Observers are subsequent outputs of this flattening - // rule, so they always get `chained_declaration`. - ctx.is_chained = true; - - let mut result = vec![var_decl]; - for obs in ws.into_iter().chain(ds) { - result.extend(ctx.translate(obs)?); + let mut result = Vec::new(); + if let Some(val) = val { + // Stored property with observers: the initializer is not part + // of the binding, so translate it in a reset scope. + let val = ctx.scoped(|ctx| { + ctx.reset(); + ctx.translate(val) + })?; + result.push(tree!( + (variable_declaration + modifier: {ctx.outer_modifiers.clone()} + pattern: (name_pattern identifier: (identifier #{name})) + type: {ty} + value: {val}) + )); + } else { + // Computed property: the accessors carry the type. + ctx.property_type = Some(ty); + } + for acc in accessors.into_iter() { + ctx.is_chained = !result.is_empty(); + result.extend(ctx.translate(acc)?); } result } ), + // Each `accessorDecl` becomes an `accessor_declaration`, reading the + // property name/type and the binding/chained modifiers from `ctx`. The + // accessor kind comes straight from the specifier keyword + // (`get`/`set`/`willSet`/`didSet`). The body is optional: a body-bearing + // accessor (a computed getter/setter, or a `willSet`/`didSet` observer) + // carries the binding modifier and a translated body, whereas a bodyless + // one (a protocol requirement) carries neither. The property context is + // read out *before* translating the body, which resets `ctx` so the + // accessor's context does not leak into the body subtree. + rule!( + (accessorDecl accessorSpecifier: @@spec body: _? @@body) + => + accessor_declaration { + let binding = if body.is_some() { + ctx.outer_modifiers.clone() + } else { + Vec::new() + }; + let chained = chained_modifier(&mut ctx); + let name = ctx + .property_name + .ok_or("accessor outside property context")?; + let ty = ctx.property_type; + let body = match body { + Some(block) => { + ctx.reset(); + ctx.translate(block)?.into_iter().next() + } + None => None, + }; + tree!( + (accessor_declaration + modifier: {binding} + modifier: {chained} + name: {name} + type: {ty} + accessor_kind: (accessor_kind #{spec}) + body: {body}) + ) + } + ), + // ---- Variables ---- // The individual bindings of a `variableDecl`. The binding modifier and // chained tag come from `ctx` (set by the `variableDecl` rule below). The // type annotation and initializer are both optional (one combined rule @@ -1095,22 +1096,6 @@ fn translation_rules() -> Vec> { ), // A member of a type declaration unwraps to the contained declaration. rule!((memberBlockItem decl: _* @d) => member* { d }), - // Protocol function — return type and body statements both optional. - rule!( - (protocol_function_declaration - name: @name - (parameter)* @params - return_type: _? @ret - body: (block statement: _* @body_stmts)? - (modifiers)* @mods) - => - (function_declaration - modifier: {mods} - name: (identifier #{name}) - parameter: {params} - return_type: {ret} - body: (block stmt: {body_stmts})) - ), // Init declaration → constructor_declaration. Body statements optional; // body itself is also optional (protocol requirement). rule!( @@ -1158,141 +1143,6 @@ fn translation_rules() -> Vec> { name: (identifier #{name}) bound: {bound}) ), - // Protocol property declaration: translate each accessor - // requirement to an `accessor_declaration` carrying the property - // name, type, and outer modifiers. Manual rule: we publish the - // property's name/type/modifiers into `ctx` and translate each - // accessor with `ctx.is_chained` toggled per iteration so the - // inner `getter_specifier`/`setter_specifier` rules emit - // complete nodes from the start (including the - // `chained_declaration` tag for non-first accessors). - rule!( - (protocol_property_declaration - name: (pattern bound_identifier: @name) - requirements: (protocol_property_requirements accessor: _+ @@accessors) - type: _? @ty - (modifiers)* @mods) - => - accessor_declaration* { - ctx.property_name = Some(tree!((identifier #{name}))); - ctx.property_type = ty; - ctx.outer_modifiers = mods; - - let mut result = Vec::new(); - for (i, acc) in accessors.into_iter().enumerate() { - ctx.is_chained = i > 0; - result.extend(ctx.translate(acc)?); - } - result - } - ), - // getter_specifier / setter_specifier → bodyless accessor_declaration - // getter_specifier / setter_specifier → bodyless - // accessor_declaration. Reads property name/type/modifiers from - // `ctx` set by the outer `protocol_property_declaration` rule. - rule!( - (getter_specifier) - => - (accessor_declaration - name: {ctx.property_name.ok_or("getter_specifier outside protocol_property_declaration context")?} - type: {ctx.property_type} - accessor_kind: (accessor_kind "get") - 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} - accessor_kind: (accessor_kind "set") - 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) => accessor_declaration* { 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 - // property_declaration rule. - rule!( - (computed_getter body: (block statement: _* @@body)?) - => - (accessor_declaration - 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} - accessor_kind: (accessor_kind "get") - body: (block stmt: {ctx.reset(); ctx.translate(body)?})) - ), - // Computed setter with explicit parameter name. - rule!( - (computed_setter parameter: @param body: (block statement: _* @@body)) - => - (accessor_declaration - 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} - accessor_kind: (accessor_kind "set") - parameter: (parameter pattern: (name_pattern identifier: (identifier #{param}))) - body: (block stmt: {ctx.reset(); ctx.translate(body)?})) - ), - // Computed setter without explicit parameter name; body optional. - rule!( - (computed_setter body: (block statement: _* @@body)?) - => - (accessor_declaration - 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} - accessor_kind: (accessor_kind "set") - body: (block stmt: {ctx.reset(); ctx.translate(body)?})) - ), - // Computed modify → accessor_declaration - rule!( - (computed_modify body: (block statement: _* @@body)) - => - (accessor_declaration - 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} - accessor_kind: (accessor_kind "modify") - body: (block stmt: {ctx.reset(); ctx.translate(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) => accessor_declaration* { 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 - // property_declaration rule. - rule!( - (willset_clause body: (block statement: _* @@body)?) - => - (accessor_declaration - 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: {ctx.reset(); ctx.translate(body)?})) - ), - // didset clause → accessor_declaration (body optional). - rule!( - (didset_clause body: (block statement: _* @@body)?) - => - (accessor_declaration - 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: {ctx.reset(); ctx.translate(body)?})) - ), // Preprocessor conditionals — unsupported rule!((diagnostic) => (unsupported_node)), // ---- Fallbacks ---- From 90ea1b591d1254e621a20f7eb9f246d992ec3b10 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 23 Jul 2026 11:48:10 +0000 Subject: [PATCH 19/27] unified: Port enum-case rules to swift-syntax Retarget enum cases to the swift-syntax AST. An `enumCaseDecl` flattens its comma-separated `enumCaseElement`s (non-first tagged `chained_declaration`) and publishes any case modifiers (e.g. `indirect`) into `ctx`; an element with a payload becomes a nested `class_like_declaration` + constructor, an element with a raw value (`case a = 1`) or a plain element a `variable_declaration`; `enumCaseParameter` becomes a `parameter`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 2abc783b9f01..82709c2da20e 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -386,27 +386,22 @@ fn translation_rules() -> Vec> { } ), // ---- Enums ---- - // enum_type_parameter → parameter (with optional name as pattern). + // An enum-case payload parameter (`radius: Double`, or just `Double`). + // The label (`firstName`) is optional. rule!( - (enum_type_parameter name: @name type: @ty) + (enumCaseParameter firstName: _? @@name type: @ty) => - (parameter - pattern: (name_pattern identifier: (identifier #{name})) - type: {ty}) + (parameter pattern: {name.map(|name| tree!((name_pattern identifier: (identifier #{name}))))} type: {ty}) ), + // An enum element with associated values (`case circle(radius: Double)`) + // becomes a nested `class_like_declaration` whose constructor carries the + // payload parameters; an element with a raw value (`case a = 1`) or a + // plain element (`case north`) becomes a `variable_declaration`. All + // carry the shared case modifiers / chained tag from `ctx` (set by the + // `enumCaseDecl` rule below) and are tagged `enum_case` (after any + // `chained_declaration` tag, matching the tree-sitter modifier order). 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. Reads outer modifiers / chained tag from `ctx` - // (set by the outer `enum_entry` rule). - rule!( - (enum_case_entry - name: @name - data_contents: (enum_type_parameters parameter: _* @params)) + (enumCaseElement name: @name parameterClause: (enumCaseParameterClause parameters: _* @params)) => (class_like_declaration modifier: {ctx.outer_modifiers.clone()} @@ -415,9 +410,8 @@ fn translation_rules() -> Vec> { 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) + (enumCaseElement name: @name rawValue: (initializerClause value: @val)) => (variable_declaration modifier: {ctx.outer_modifiers.clone()} @@ -426,9 +420,8 @@ fn translation_rules() -> Vec> { 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) + (enumCaseElement name: @name) => (variable_declaration modifier: {ctx.outer_modifiers.clone()} @@ -436,12 +429,14 @@ fn translation_rules() -> Vec> { modifier: (modifier "enum_case") pattern: (name_pattern identifier: (identifier #{name}))) ), - // enum_entry: flatten case entries; publish outer modifiers - // into `ctx` and translate each case with `ctx.is_chained` - // toggled per iteration so the inner `enum_case_entry` rules - // emit complete `modifier:` lists from the start. + // Enum cases. A single `case` declaration may carry modifiers + // (e.g. `indirect`) and list several comma-separated elements; each + // becomes its own declaration carrying those shared modifiers, and + // non-first ones are tagged `chained_declaration` (mirroring the + // tree-sitter `enum_entry` rule). The modifiers are published into `ctx` + // for the element rules above, which build the actual declaration. rule!( - (enum_entry case: _+ @@cases (modifiers)* @mods) + (enumCaseDecl modifiers: _* @mods elements: _* @@cases) => member* { ctx.outer_modifiers = mods; From c4fbd74cfe1695576d02e9682efbeb45439551dd Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 23 Jul 2026 12:03:28 +0000 Subject: [PATCH 20/27] unified: Port constructor and related declaration rules to swift-syntax Retarget the remaining member declarations to the swift-syntax AST. An `initializerDecl` becomes a `constructor_declaration` (its body optional, so a bodyless protocol requirement still maps); `deinitializerDecl`, `typeAliasDecl`, and `associatedTypeDecl` map to `destructor_declaration`, `type_alias_declaration`, and `associated_type_declaration` respectively. The tree-sitter subscript and preprocessor-diagnostic rules are dropped: their swift-syntax counterparts (`subscriptDecl`, `ifConfigDecl`) fall through to the `unsupported_node` fallback, producing the same output. This also removes the now-unused `type` unwrap rule (swift-syntax has no such wrapper node) and retires the last `ctx.literal` helper use in favour of a `tree!` leaf. PARITY(tree-sitter): the initializer's parameters are still not emitted, because the tree-sitter path dropped them too. Emitting them is a future improvement. This completes the rule migration: every declaration now maps through the swift-syntax front-end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 82709c2da20e..8349214e3046 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -69,7 +69,7 @@ impl SwiftContext { /// 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")) + Some(tree!((modifier "chained_declaration"))) } else { None } @@ -449,8 +449,6 @@ fn translation_rules() -> Vec> { result } ), - // Unwrap `type` wrapper node - rule!((type name: @inner) => type_expr { inner }), // `identifierPattern` wraps a single identifier token. rule!( (identifierPattern identifier: @name) @@ -1093,22 +1091,24 @@ fn translation_rules() -> Vec> { rule!((memberBlockItem decl: _* @d) => member* { d }), // Init declaration → constructor_declaration. Body statements optional; // body itself is also optional (protocol requirement). + // + // PARITY(tree-sitter): the parameters are not emitted, because the + // tree-sitter path dropped them (its `(parameter)*` capture missed the + // field-attached parameters). Emitting them is a future improvement. rule!( - (init_declaration - (parameter)* @params - body: (block statement: _* @body_stmts)? - (modifiers)* @mods) + (initializerDecl + modifiers: _* @mods + body: (codeBlock statements: _* @body_stmts)?) => (constructor_declaration modifier: {mods} - parameter: {params} body: (block stmt: {body_stmts})) ), // Deinit declaration → destructor_declaration. Body statements optional. rule!( - (deinit_declaration - body: (block statement: _* @body_stmts) - (modifiers)* @mods) + (deinitializerDecl + modifiers: _* @mods + body: (codeBlock statements: _* @body_stmts)) => (destructor_declaration modifier: {mods} @@ -1116,30 +1116,28 @@ fn translation_rules() -> Vec> { ), // Typealias declaration rule!( - (typealias_declaration name: @name value: @val (modifiers)* @mods) + (typeAliasDecl + modifiers: _* @mods + name: @@name + initializer: (typeInitializerClause value: @val)) => (type_alias_declaration modifier: {mods} name: (identifier #{name}) r#type: {val}) ), - // Subscript declaration (not yet supported -- grammar needs to distinguish plain calls from subscript calls) - rule!( - (subscript_declaration (parameter)* @params (modifiers)* @mods) - => - (unsupported_node) - ), // Associated type declaration (with optional bound) rule!( - (associatedtype_declaration name: @name inherits_from: _? @bound (modifiers)* @mods) + (associatedTypeDecl + modifiers: _* @mods + name: @@name + inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bound))?) => (associated_type_declaration modifier: {mods} name: (identifier #{name}) bound: {bound}) ), - // Preprocessor conditionals — unsupported - rule!((diagnostic) => (unsupported_node)), // ---- Fallbacks ---- // Bare `_` (rather than `(_)`) so this matches both named nodes // and unnamed tokens. Any unnamed token that escapes the From 5c5fd5e1cc9e6b0261766103e1a5ee5475314c19 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 23 Jul 2026 12:44:23 +0000 Subject: [PATCH 21/27] unified: Switch the Swift front-end to swift-syntax Flip the runtime Swift front-end from tree-sitter to swift-syntax. The mapping rules were already ported, so the mapped AST is unchanged; this makes the switch live. - `language_spec` now builds a language-free desugarer (`ConcreteDesugarer::without_language`) and wires the swift-syntax parser (`swift_parse::parse`) as the front-end, dropping the tree-sitter language and node types. The desugarer supplies the output schema, so `node_types` is left empty. - The `swift_adapter`/`swift_parse` modules are no longer `allow(dead_code)`: they are now reached from the live extraction path. - `corpus_tests` skips (rather than fails) when the external `swift-syntax-parse` binary is unavailable, since it cannot run without the Swift-backed parser. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/extractor/src/languages/mod.rs | 14 ++------------ unified/extractor/src/languages/swift/swift.rs | 9 ++++----- unified/extractor/tests/corpus_tests.rs | 18 +++++++++++++++++- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/unified/extractor/src/languages/mod.rs b/unified/extractor/src/languages/mod.rs index 032bc884ca34..c79806dc9d43 100644 --- a/unified/extractor/src/languages/mod.rs +++ b/unified/extractor/src/languages/mod.rs @@ -4,23 +4,13 @@ use codeql_extractor::extractor::desugaring; mod swift; /// swift-syntax JSON -> `yeast::Ast` adapter for the Swift front-end. -/// -/// Currently exercised by tests and the forthcoming runtime extraction path; -/// `allow(dead_code)` because this is a binary crate, so its public API isn't -/// counted as used until the binary itself calls it. #[path = "swift/adapter.rs"] -#[allow(dead_code)] pub mod swift_adapter; /// Swift front-end parser: shells out to `swift-syntax-parse` and adapts its -/// JSON output via [`swift_adapter`]. -/// -/// Dormant for now: the runtime Swift front-end is still tree-sitter, so -/// nothing in the binary calls this yet. `allow(dead_code)` for the same -/// binary-crate reason as [`swift_adapter`]; both allows are removed once the -/// runtime switches the Swift front-end to swift-syntax. +/// JSON output via [`swift_adapter`]. This is the live Swift front-end used by +/// [`all_language_specs`]. #[path = "swift/parse.rs"] -#[allow(dead_code)] pub mod swift_parse; /// Shared YEAST output AST schema for all languages. diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 8349214e3046..32252f8a0228 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -1155,16 +1155,15 @@ fn translation_rules() -> Vec> { } pub fn language_spec(desugared_ast_schema: &'static str) -> desugaring::LanguageSpec { - let ts_language: tree_sitter::Language = tree_sitter_swift::LANGUAGE.into(); let config = DesugaringConfig::::new() .add_phase("translate", PhaseKind::OneShot, translation_rules()) .with_output_node_types_yaml(desugared_ast_schema); - let desugarer = ConcreteDesugarer::new(ts_language.clone(), config) - .expect("failed to build Swift desugarer"); + let desugarer = + ConcreteDesugarer::without_language(config).expect("failed to build Swift desugarer"); desugaring::LanguageSpec { prefix: "swift", - parser: Box::new(codeql_extractor::extractor::tree_sitter_parser(ts_language)), - node_types: tree_sitter_swift::NODE_TYPES, + parser: Box::new(super::swift_parse::parse), + node_types: "", file_globs: vec!["*.swift".into(), "*.swiftinterface".into()], desugarer: Box::new(desugarer), } diff --git a/unified/extractor/tests/corpus_tests.rs b/unified/extractor/tests/corpus_tests.rs index d192b19485e7..66374e56526e 100644 --- a/unified/extractor/tests/corpus_tests.rs +++ b/unified/extractor/tests/corpus_tests.rs @@ -20,6 +20,14 @@ fn update_mode_enabled() -> bool { .unwrap_or(false) } +/// Whether the external swift-syntax parser is available. When it is not (e.g. +/// no Swift toolchain / the `swift-syntax-parse` binary is not on `PATH` and +/// `CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE` is unset), the corpus test is +/// skipped rather than failed — it cannot run without the Swift-backed parser. +fn parser_available() -> bool { + languages::swift_parse::parse(b"").is_ok() +} + /// Parse a corpus `.output` file. The file holds a single test case made of /// three sections separated by `---` delimiter lines: /// @@ -28,7 +36,7 @@ fn update_mode_enabled() -> bool { /// /// --- /// -/// +/// /// /// --- /// @@ -98,6 +106,14 @@ fn collect_corpus_stems(dir: &Path, out: &mut Vec) { #[test] fn test_corpus() { + if !parser_available() { + eprintln!( + "skipping test_corpus: the swift-syntax parser is unavailable \ + (set CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE or put \ + `swift-syntax-parse` on PATH)" + ); + return; + } let update_mode = update_mode_enabled(); let all_languages = languages::all_language_specs(); let corpus_dir = Path::new("tests/corpus"); From 20a253729977dbd43edaffda0422da0ffa5fb642 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 23 Jul 2026 12:50:10 +0000 Subject: [PATCH 22/27] unified: Regenerate the raw-AST corpus section for swift-syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that the Swift front-end is swift-syntax, regenerate the second (raw parse tree) section of the corpus `.output` files to hold the swift-syntax AST the adapter builds, instead of the old tree-sitter parse tree. These 98 cases map to a byte-for-byte identical mapped AST (the third section), so only their raw section changes — the mapping rules were ported to produce the same output. The one case whose mapped AST differs (`types/property-with-getter-and-setter`) is handled separately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../closures/closure-with-capture-list.output | 90 +++++---- .../closure-with-explicit-parameters.output | 96 +++++---- .../closure-with-shorthand-parameters.output | 52 +++-- .../closures/multi-statement-closure.output | 141 +++++++------ .../swift/closures/trailing-closure.output | 52 +++-- .../swift/collections/array-literal.output | 53 +++-- .../collections/dictionary-literal.output | 71 ++++--- .../collections/dictionary-subscript.output | 59 +++--- .../empty-array-literal-with-type.output | 56 +++--- .../swift/collections/set-literal.output | 82 ++++---- .../swift/collections/subscript-access.output | 54 ++--- .../swift/collections/tuple-literal.output | 60 ++++-- .../collections/tuple-member-access.output | 43 ++-- ...g-modifier-does-not-leak-to-sibling.output | 109 ++++++---- .../swift/control-flow/guard-let.output | 50 +++-- ...t-with-shadowing-in-condition-value.output | 85 ++++---- .../control-flow/if-else-if-chain.output | 152 ++++++++------ .../corpus/swift/control-flow/if-else.output | 101 ++++++---- .../if-let-optional-binding.output | 68 ++++--- .../swift/control-flow/if-statement.output | 63 +++--- .../control-flow/switch-statement.output | 167 ++++++++++------ .../switch-with-binding-pattern.output | 176 ++++++++++------- ...with-labeled-case-pattern-arguments.output | 186 +++++++++++------- .../control-flow/ternary-expression.output | 64 +++--- .../additive-expression-is-desugared.output | 21 +- ...er-additive-expression-is-desugared.output | 21 +- ...with-deeply-nested-path-three-parts.output | 27 ++- .../import-with-dotted-path-two-parts.output | 23 ++- .../scoped-import-uses-name-pattern.output | 25 ++- .../simple-import-with-single-name.output | 18 +- ...nction-call-with-labelled-arguments.output | 37 ++-- .../swift/functions/function-call.output | 33 ++-- ...nction-with-default-parameter-value.output | 85 +++++--- .../function-with-named-parameters.output | 74 ++++--- .../function-with-no-parameters.output | 58 ++++-- ...ion-with-parameters-and-return-type.output | 108 +++++----- .../swift/functions/generic-function.output | 87 ++++---- .../leading-dot-expression-call.output | 57 +++--- .../leading-dot-expression-value.output | 38 ++-- .../corpus/swift/functions/method-call.output | 37 ++-- .../swift/functions/variadic-function.output | 114 ++++++----- .../swift/literals/boolean-literals.output | 15 +- .../literals/floating-point-literal.output | 9 +- .../swift/literals/integer-literal.output | 9 +- .../literals/negative-integer-literal.output | 15 +- .../corpus/swift/literals/nil-literal.output | 9 +- .../swift/literals/string-literal.output | 15 +- .../literals/string-with-interpolation.output | 29 ++- .../swift/loops/break-and-continue.output | 132 ++++++++----- .../loops/for-in-over-array-literal.output | 71 ++++--- .../swift/loops/for-in-over-range.output | 62 +++--- .../loops/for-in-with-where-clause.output | 72 ++++--- .../swift/loops/repeat-while-loop.output | 55 ++++-- .../corpus/swift/loops/while-loop.output | 56 ++++-- .../corpus/swift/operators/addition.output | 21 +- .../corpus/swift/operators/comparison.output | 21 +- .../corpus/swift/operators/division.output | 21 +- .../corpus/swift/operators/equality.output | 21 +- .../corpus/swift/operators/logical-and.output | 21 +- .../corpus/swift/operators/logical-not.output | 15 +- .../corpus/swift/operators/logical-or.output | 21 +- .../swift/operators/multiplication.output | 21 +- ...cedence-addition-and-multiplication.output | 33 +++- .../operators/parenthesised-expression.output | 43 ++-- .../swift/operators/range-operator.output | 21 +- .../corpus/swift/operators/subtraction.output | 21 +- .../optionals-and-errors/do-catch.output | 77 +++++--- .../optionals-and-errors/force-unwrap.output | 38 ++-- .../nil-coalescing.output | 43 ++-- .../optional-chaining.output | 64 +++--- .../optional-type-annotation.output | 52 ++--- .../throwing-function.output | 63 ++++-- .../try-expression-2.output | 52 ++--- .../try-expression.output | 52 ++--- ...er-does-not-leak-into-accessor-body.output | 126 +++++++----- .../swift/types/class-inheritance.output | 37 ++-- .../swift/types/class-with-initializer.output | 130 +++++++----- .../swift/types/class-with-method.output | 92 ++++++--- .../types/class-with-stored-properties.output | 93 ++++----- .../swift/types/computed-property.output | 158 ++++++++------- .../corpus/swift/types/empty-class.output | 22 ++- .../types/enum-with-associated-values.output | 97 +++++---- .../corpus/swift/types/enum-with-cases.output | 75 ++++--- ...separated-cases-chained-declaration.output | 51 +++-- .../tests/corpus/swift/types/extension.output | 90 +++++---- .../swift/types/protocol-declaration.output | 38 +++- ...nd-read-write-property-requirements.output | 116 ++++++----- .../tests/corpus/swift/types/struct.output | 93 ++++----- .../corpus/swift/variables/assignment.output | 23 ++- ...fier-does-not-leak-into-initializer.output | 78 +++++--- .../variables/compound-assignment.output | 23 ++- .../corpus/swift/variables/let-binding.output | 32 +-- .../variables/let-with-type-annotation.output | 47 +++-- .../multiple-bindings-on-one-line.output | 48 +++-- ...y-with-willset-and-didset-observers.output | 144 ++++++++------ .../tuple-destructuring-binding.output | 49 +++-- .../corpus/swift/variables/var-binding.output | 32 +-- .../variables/var-without-initialiser.output | 40 ++-- 98 files changed, 3740 insertions(+), 2257 deletions(-) diff --git a/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output b/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output index b4760e35591c..6833344de16d 100644 --- a/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output +++ b/unified/extractor/tests/corpus/swift/closures/closure-with-capture-list.output @@ -2,41 +2,61 @@ let f = { [weak self] in self?.doThing() } --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "f" - value: - lambda_literal - captures: - capture_list - item: - capture_list_item - name: simple_identifier "self" - ownership: - ownership_modifier - statement: - call_expression - function: - navigation_expression - suffix: - navigation_suffix - suffix: simple_identifier "doThing" - target: - optional_chain_marker - expr: - self_expression - suffix: - call_suffix - arguments: - value_arguments +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + closureExpr + leftBrace: { + rightBrace: } + signature: + closureSignature + attributes: + capture: + closureCaptureClause + leftSquare: [ + rightSquare: ] + items: + closureCapture + name: self + specifier: + closureCaptureSpecifier + specifier: weak + inKeyword: in + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "doThing" + base: + optionalChainingExpr + expression: + declReferenceExpr + baseName: self + questionMark: ? + pattern: + identifierPattern + identifier: identifier "f" --- diff --git a/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output b/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output index bb6b878b8bb9..c20da77c72cb 100644 --- a/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output +++ b/unified/extractor/tests/corpus/swift/closures/closure-with-explicit-parameters.output @@ -2,45 +2,63 @@ let f = { (x: Int) -> Int in x * 2 } --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "f" - value: - lambda_literal - statement: - multiplicative_expression - lhs: simple_identifier "x" - op: * - rhs: integer_literal "2" - type: - lambda_function_type - params: - lambda_function_type_parameters - parameter: - lambda_parameter - name: simple_identifier "x" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - return_type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + closureExpr + leftBrace: { + rightBrace: } + signature: + closureSignature + attributes: + inKeyword: in + parameterClause: + closureParameterClause + leftParen: ( + rightParen: ) + parameters: + closureParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: identifier "x" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "Int" + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "2" + pattern: + identifierPattern + identifier: identifier "f" --- diff --git a/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output b/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output index 67cdf3df63f2..1b07604e02c5 100644 --- a/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output +++ b/unified/extractor/tests/corpus/swift/closures/closure-with-shorthand-parameters.output @@ -2,24 +2,40 @@ let f = { $0 + $1 } --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "f" - value: - lambda_literal - statement: - additive_expression - lhs: simple_identifier "$0" - op: + - rhs: simple_identifier "$1" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + closureExpr + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: dollarIdentifier "$0" + rightOperand: + declReferenceExpr + baseName: dollarIdentifier "$1" + pattern: + identifierPattern + identifier: identifier "f" --- diff --git a/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output b/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output index 0d07ea6f7bbb..4fbc1c8f71f0 100644 --- a/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output +++ b/unified/extractor/tests/corpus/swift/closures/multi-statement-closure.output @@ -5,62 +5,91 @@ let f = { (x: Int) -> Int in --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "f" - value: - lambda_literal - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "y" - value: - additive_expression - lhs: simple_identifier "x" - op: + - rhs: integer_literal "1" - control_transfer_statement - kind: return - result: - multiplicative_expression - lhs: simple_identifier "y" - op: * - rhs: integer_literal "2" - type: - lambda_function_type - params: - lambda_function_type_parameters - parameter: - lambda_parameter - name: simple_identifier "x" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - return_type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + closureExpr + leftBrace: { + rightBrace: } + signature: + closureSignature + attributes: + inKeyword: in + parameterClause: + closureParameterClause + leftParen: ( + rightParen: ) + parameters: + closureParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: identifier "x" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "Int" + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "y" + codeBlockItem + item: + returnStmt + expression: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: identifier "y" + rightOperand: + integerLiteralExpr + literal: integerLiteral "2" + returnKeyword: return + pattern: + identifierPattern + identifier: identifier "f" --- diff --git a/unified/extractor/tests/corpus/swift/closures/trailing-closure.output b/unified/extractor/tests/corpus/swift/closures/trailing-closure.output index ef8d6bd21c6a..3e26ff27eb58 100644 --- a/unified/extractor/tests/corpus/swift/closures/trailing-closure.output +++ b/unified/extractor/tests/corpus/swift/closures/trailing-closure.output @@ -2,24 +2,40 @@ xs.map { $0 * 2 } --- -source_file - statement: - call_expression - function: - navigation_expression - suffix: - navigation_suffix - suffix: simple_identifier "map" - target: simple_identifier "xs" - suffix: - call_suffix - lambda: - lambda_literal - statement: - multiplicative_expression - lhs: simple_identifier "$0" - op: * - rhs: integer_literal "2" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionCallExpr + arguments: + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "map" + base: + declReferenceExpr + baseName: identifier "xs" + trailingClosure: + closureExpr + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: dollarIdentifier "$0" + rightOperand: + integerLiteralExpr + literal: integerLiteral "2" --- diff --git a/unified/extractor/tests/corpus/swift/collections/array-literal.output b/unified/extractor/tests/corpus/swift/collections/array-literal.output index f6ee44c1f809..c6ea2c2094fa 100644 --- a/unified/extractor/tests/corpus/swift/collections/array-literal.output +++ b/unified/extractor/tests/corpus/swift/collections/array-literal.output @@ -2,23 +2,42 @@ let xs = [1, 2, 3] --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "xs" - value: - array_literal - element: - integer_literal "1" - integer_literal "2" - integer_literal "3" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + arrayExpr + elements: + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "1" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "2" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "3" + leftSquare: [ + rightSquare: ] + pattern: + identifierPattern + identifier: identifier "xs" --- diff --git a/unified/extractor/tests/corpus/swift/collections/dictionary-literal.output b/unified/extractor/tests/corpus/swift/collections/dictionary-literal.output index a19028d3f3bb..edd306a69cbd 100644 --- a/unified/extractor/tests/corpus/swift/collections/dictionary-literal.output +++ b/unified/extractor/tests/corpus/swift/collections/dictionary-literal.output @@ -2,30 +2,53 @@ let d = ["a": 1, "b": 2] --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "d" - value: - dictionary_literal - element: - dictionary_literal_item - key: - line_string_literal - text: line_str_text "a" - value: integer_literal "1" - dictionary_literal_item - key: - line_string_literal - text: line_str_text "b" - value: integer_literal "2" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + dictionaryExpr + leftSquare: [ + rightSquare: ] + content: + dictionaryElement + colon: : + trailingComma: , + value: + integerLiteralExpr + literal: integerLiteral "1" + key: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "a" + dictionaryElement + colon: : + value: + integerLiteralExpr + literal: integerLiteral "2" + key: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "b" + pattern: + identifierPattern + identifier: identifier "d" --- diff --git a/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output b/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output index c30ab9326bb6..e2f939e0683c 100644 --- a/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output +++ b/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output @@ -4,31 +4,40 @@ let v = d["key"] --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "v" - value: - call_expression - function: simple_identifier "d" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - line_string_literal - text: line_str_text "key" - comment "// TODO: same parser issue as the array subscript case above —" - comment "// `d[\"key\"]` is parsed as `call_expression(d, (\"key\"))`." +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + subscriptCallExpr + leftSquare: [ + rightSquare: ] + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "key" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "d" + pattern: + identifierPattern + identifier: identifier "v" --- diff --git a/unified/extractor/tests/corpus/swift/collections/empty-array-literal-with-type.output b/unified/extractor/tests/corpus/swift/collections/empty-array-literal-with-type.output index 90d1a9dde368..e9a70a43bb42 100644 --- a/unified/extractor/tests/corpus/swift/collections/empty-array-literal-with-type.output +++ b/unified/extractor/tests/corpus/swift/collections/empty-array-literal-with-type.output @@ -2,32 +2,38 @@ let xs: [Int] = [] --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "xs" - type: - type_annotation - type: - type - name: - array_type +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + arrayExpr + elements: + leftSquare: [ + rightSquare: ] + pattern: + identifierPattern + identifier: identifier "xs" + typeAnnotation: + typeAnnotation + colon: : + type: + arrayType + leftSquare: [ + rightSquare: ] element: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - value: - array_literal + identifierType + name: identifier "Int" --- diff --git a/unified/extractor/tests/corpus/swift/collections/set-literal.output b/unified/extractor/tests/corpus/swift/collections/set-literal.output index a1a1dde75f9a..c29492c9bc0b 100644 --- a/unified/extractor/tests/corpus/swift/collections/set-literal.output +++ b/unified/extractor/tests/corpus/swift/collections/set-literal.output @@ -2,41 +2,57 @@ let s: Set = [1, 2, 3] --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "s" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + arrayExpr + elements: + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "1" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "2" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "3" + leftSquare: [ + rightSquare: ] + pattern: + identifierPattern + identifier: identifier "s" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Set" + genericArgumentClause: + genericArgumentClause arguments: - type_arguments + genericArgument argument: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - name: type_identifier "Set" - value: - array_literal - element: - integer_literal "1" - integer_literal "2" - integer_literal "3" + identifierType + name: identifier "Int" + leftAngle: < + rightAngle: > --- diff --git a/unified/extractor/tests/corpus/swift/collections/subscript-access.output b/unified/extractor/tests/corpus/swift/collections/subscript-access.output index 681afca891a7..f7e518b84773 100644 --- a/unified/extractor/tests/corpus/swift/collections/subscript-access.output +++ b/unified/extractor/tests/corpus/swift/collections/subscript-access.output @@ -5,30 +5,36 @@ let first = xs[0] --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "first" - value: - call_expression - function: simple_identifier "xs" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: integer_literal "0" - comment "// TODO: tree-sitter-swift parses `xs[0]` as a call_expression (same shape" - comment "// as `xs(0)`), so the mapping currently produces a call_expr. Update the" - comment "// parser / add a separate subscript_expr node and remap when fixed." +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + subscriptCallExpr + leftSquare: [ + rightSquare: ] + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "0" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "xs" + pattern: + identifierPattern + identifier: identifier "first" --- diff --git a/unified/extractor/tests/corpus/swift/collections/tuple-literal.output b/unified/extractor/tests/corpus/swift/collections/tuple-literal.output index a0ac8861674b..facbc2fcbb45 100644 --- a/unified/extractor/tests/corpus/swift/collections/tuple-literal.output +++ b/unified/extractor/tests/corpus/swift/collections/tuple-literal.output @@ -2,28 +2,46 @@ let t = (1, "two", 3.0) --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "t" - value: - tuple_expression - element: - tuple_expression_item - value: integer_literal "1" - tuple_expression_item +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = value: - line_string_literal - text: line_str_text "two" - tuple_expression_item - value: real_literal "3.0" + tupleExpr + leftParen: ( + rightParen: ) + elements: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "1" + trailingComma: , + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "two" + trailingComma: , + labeledExpr + expression: + floatLiteralExpr + literal: floatLiteral "3.0" + pattern: + identifierPattern + identifier: identifier "t" --- diff --git a/unified/extractor/tests/corpus/swift/collections/tuple-member-access.output b/unified/extractor/tests/corpus/swift/collections/tuple-member-access.output index 29b234f30e26..6ecbd22eebe0 100644 --- a/unified/extractor/tests/corpus/swift/collections/tuple-member-access.output +++ b/unified/extractor/tests/corpus/swift/collections/tuple-member-access.output @@ -2,23 +2,32 @@ let n = t.0 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "n" - value: - navigation_expression - suffix: - navigation_suffix - suffix: integer_literal "0" - target: simple_identifier "t" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: integerLiteral "0" + base: + declReferenceExpr + baseName: identifier "t" + pattern: + identifierPattern + identifier: identifier "n" --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output b/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output index 9c4088845f14..800647eb7474 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output +++ b/unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output @@ -8,44 +8,79 @@ default: --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - value: integer_literal "1" - switch_statement - entry: - switch_entry - pattern: - switch_pattern +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" pattern: - pattern - kind: simple_identifier "someConstant" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - line_string_literal - text: line_str_text "matched" - switch_entry - default: default_keyword "default" - statement: - control_transfer_statement - kind: break - expr: simple_identifier "y" + identifierPattern + identifier: identifier "x" + codeBlockItem + item: + expressionStmt + expression: + switchExpr + leftBrace: { + rightBrace: } + cases: + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + declReferenceExpr + baseName: identifier "someConstant" + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "matched" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + switchCase + label: + switchDefaultLabel + colon: : + defaultKeyword: default + statements: + codeBlockItem + item: + breakStmt + breakKeyword: break + subject: + declReferenceExpr + baseName: identifier "y" + switchKeyword: switch --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/guard-let.output b/unified/extractor/tests/corpus/swift/control-flow/guard-let.output index 2e90e9820640..d739eca564b5 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/guard-let.output +++ b/unified/extractor/tests/corpus/swift/control-flow/guard-let.output @@ -2,25 +2,37 @@ guard let value = optional else { return } --- -source_file - statement: - guard_statement - body: - block - statement: - control_transfer_statement - kind: return - condition: - if_condition - kind: - if_let_binding - pattern: - pattern - binding: - value_binding_pattern - mutability: let - bound_identifier: simple_identifier "value" - value: simple_identifier "optional" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + guardStmt + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + returnStmt + returnKeyword: return + conditions: + conditionElement + condition: + optionalBindingCondition + initializer: + initializerClause + equal: = + value: + declReferenceExpr + baseName: identifier "optional" + pattern: + identifierPattern + identifier: identifier "value" + bindingSpecifier: let + elseKeyword: else + guardKeyword: guard --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output b/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output index 61ca65811e97..2a0676513f23 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-case-let-with-shadowing-in-condition-value.output @@ -4,40 +4,59 @@ if case let x = x + 10 { --- -source_file - statement: - if_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "x" - condition: - if_condition - kind: - if_let_binding - pattern: - pattern - kind: - binding_pattern - binding: - value_binding_pattern - mutability: let +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + conditions: + conditionElement + condition: + matchingPatternCondition + initializer: + initializerClause + equal: = + value: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "10" pattern: - pattern - bound_identifier: simple_identifier "x" - value: - additive_expression - lhs: simple_identifier "x" - op: + - rhs: integer_literal "10" + valueBindingPattern + pattern: + identifierPattern + identifier: identifier "x" + bindingSpecifier: let + caseKeyword: case + ifKeyword: if --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output b/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output index 268a2d1f97c9..35f1c2c46d2c 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-else-if-chain.output @@ -8,61 +8,103 @@ if x > 0 { --- -source_file - statement: - if_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: integer_literal "1" - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "0" - else_branch: - if_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: integer_literal "2" - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: < - rhs: integer_literal "0" - else_branch: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: integer_literal "3" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "1" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + conditions: + conditionElement + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + elseKeyword: else + elseBody: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "2" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + conditions: + conditionElement + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "<" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + elseKeyword: else + elseBody: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "3" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + ifKeyword: if + ifKeyword: if --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-else.output b/unified/extractor/tests/corpus/swift/control-flow/if-else.output index e891cb4e2f6c..744b5c51ab24 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-else.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-else.output @@ -6,43 +6,70 @@ if x > 0 { --- -source_file - statement: - if_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "x" - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "0" - else_branch: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - prefix_expression - operation: - - target: simple_identifier "x" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + conditions: + conditionElement + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + elseKeyword: else + elseBody: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + prefixOperatorExpr + expression: + declReferenceExpr + baseName: identifier "x" + operator: prefixOperator "-" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + ifKeyword: if --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output b/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output index 0436a559236b..d1bcf82b0faa 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output @@ -4,32 +4,48 @@ if let value = optional { --- -source_file - statement: - if_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "value" - condition: - if_condition - kind: - if_let_binding - pattern: - pattern - binding: - value_binding_pattern - mutability: let - bound_identifier: simple_identifier "value" - value: simple_identifier "optional" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "value" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + conditions: + conditionElement + condition: + optionalBindingCondition + initializer: + initializerClause + equal: = + value: + declReferenceExpr + baseName: identifier "optional" + pattern: + identifierPattern + identifier: identifier "value" + bindingSpecifier: let + ifKeyword: if --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/if-statement.output b/unified/extractor/tests/corpus/swift/control-flow/if-statement.output index 6e89490b0128..77bba019ffb9 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/if-statement.output +++ b/unified/extractor/tests/corpus/swift/control-flow/if-statement.output @@ -4,28 +4,47 @@ if x > 0 { --- -source_file - statement: - if_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "x" - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "0" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + conditions: + conditionElement + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + ifKeyword: if --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output b/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output index e016a650787f..a88579852947 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output +++ b/unified/extractor/tests/corpus/swift/control-flow/switch-statement.output @@ -9,65 +9,114 @@ default: --- -source_file - statement: - switch_statement - entry: - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: integer_literal "1" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - line_string_literal - text: line_str_text "one" - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: integer_literal "2" - switch_pattern - pattern: - pattern - kind: integer_literal "3" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - line_string_literal - text: line_str_text "two or three" - switch_entry - default: default_keyword "default" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - line_string_literal - text: line_str_text "other" - expr: simple_identifier "x" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + switchExpr + leftBrace: { + rightBrace: } + cases: + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + integerLiteralExpr + literal: integerLiteral "1" + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "one" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + trailingComma: , + pattern: + expressionPattern + expression: + integerLiteralExpr + literal: integerLiteral "2" + switchCaseItem + pattern: + expressionPattern + expression: + integerLiteralExpr + literal: integerLiteral "3" + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "two or three" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + switchCase + label: + switchDefaultLabel + colon: : + defaultKeyword: default + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "other" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + subject: + declReferenceExpr + baseName: identifier "x" + switchKeyword: switch --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output b/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output index b995854d5999..90bb6b44e6e5 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output +++ b/unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output @@ -7,77 +7,111 @@ case .square(let s): --- -source_file - statement: - switch_statement - entry: - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: - case_pattern - arguments: - tuple_pattern - item: - tuple_pattern_item - pattern: - pattern - kind: - binding_pattern - binding: - value_binding_pattern - mutability: let - pattern: - pattern - bound_identifier: simple_identifier "r" - dot: . - name: simple_identifier "circle" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "r" - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: - case_pattern - arguments: - tuple_pattern - item: - tuple_pattern_item - pattern: - pattern - kind: - binding_pattern - binding: - value_binding_pattern - mutability: let - pattern: - pattern - bound_identifier: simple_identifier "s" - dot: . - name: simple_identifier "square" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "s" - expr: simple_identifier "shape" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + switchExpr + leftBrace: { + rightBrace: } + cases: + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + patternExpr + pattern: + valueBindingPattern + pattern: + identifierPattern + identifier: identifier "r" + bindingSpecifier: let + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "circle" + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "r" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + patternExpr + pattern: + valueBindingPattern + pattern: + identifierPattern + identifier: identifier "s" + bindingSpecifier: let + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "square" + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "s" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + subject: + declReferenceExpr + baseName: identifier "shape" + switchKeyword: switch --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output b/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output index f8f6f2a0fe89..20b17d160e44 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output +++ b/unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output @@ -7,79 +7,119 @@ case .thread(threadRowId: _, let rowId): --- -source_file - statement: - switch_statement - entry: - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: - case_pattern - arguments: - tuple_pattern - item: - tuple_pattern_item - name: simple_identifier "isAcknowledged" - pattern: - pattern - kind: - boolean_literal - dot: . - name: simple_identifier "implicit" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - line_string_literal - text: line_str_text "yes" - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: - case_pattern - arguments: - tuple_pattern - item: - tuple_pattern_item - name: simple_identifier "threadRowId" - pattern: - pattern - kind: wildcard_pattern "_" - tuple_pattern_item - pattern: - pattern - kind: - binding_pattern - binding: - value_binding_pattern - mutability: let - pattern: - pattern - bound_identifier: simple_identifier "rowId" - dot: . - name: simple_identifier "thread" - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "rowId" - expr: simple_identifier "x" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + expressionStmt + expression: + switchExpr + leftBrace: { + rightBrace: } + cases: + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + colon: : + label: identifier "isAcknowledged" + expression: + booleanLiteralExpr + literal: false + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "implicit" + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "yes" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + colon: : + label: identifier "threadRowId" + expression: + discardAssignmentExpr + wildcard: _ + trailingComma: , + labeledExpr + expression: + patternExpr + pattern: + valueBindingPattern + pattern: + identifierPattern + identifier: identifier "rowId" + bindingSpecifier: let + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "thread" + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "rowId" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + subject: + declReferenceExpr + baseName: identifier "x" + switchKeyword: switch --- diff --git a/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output b/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output index 482bd2382a9e..88c7bc1b886c 100644 --- a/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output +++ b/unified/extractor/tests/corpus/swift/control-flow/ternary-expression.output @@ -2,29 +2,47 @@ let y = x > 0 ? 1 : -1 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "y" - value: - ternary_expression - condition: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "0" - if_false: - prefix_expression - operation: - - target: integer_literal "1" - if_true: integer_literal "1" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + ternaryExpr + colon: : + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + questionMark: ? + elseExpression: + prefixOperatorExpr + expression: + integerLiteralExpr + literal: integerLiteral "1" + operator: prefixOperator "-" + thenExpression: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "y" --- diff --git a/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output b/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output index 849fe74107b6..30d9570f3bee 100644 --- a/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output +++ b/unified/extractor/tests/corpus/swift/desugar/additive-expression-is-desugared.output @@ -2,12 +2,21 @@ --- -source_file - statement: - additive_expression - lhs: integer_literal "1" - op: + - rhs: integer_literal "2" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + integerLiteralExpr + literal: integerLiteral "1" + rightOperand: + integerLiteralExpr + literal: integerLiteral "2" --- diff --git a/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output b/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output index 88de56051393..982309ffa5af 100644 --- a/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output +++ b/unified/extractor/tests/corpus/swift/desugar/another-additive-expression-is-desugared.output @@ -2,12 +2,21 @@ foo + bar --- -source_file - statement: - additive_expression - lhs: simple_identifier "foo" - op: + - rhs: simple_identifier "bar" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "foo" + rightOperand: + declReferenceExpr + baseName: identifier "bar" --- diff --git a/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output b/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output index 3d31437a66ef..cacdc64a46de 100644 --- a/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output +++ b/unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output @@ -2,15 +2,24 @@ import Foundation.Networking.URLSession --- -source_file - statement: - import_declaration - name: - identifier - part: - simple_identifier "Foundation" - simple_identifier "Networking" - simple_identifier "URLSession" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + importDecl + attributes: + modifiers: + importKeyword: import + path: + importPathComponent + name: identifier "Foundation" + trailingPeriod: . + importPathComponent + name: identifier "Networking" + trailingPeriod: . + importPathComponent + name: identifier "URLSession" --- diff --git a/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output b/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output index f1c1dbcfb979..4fc053a7bc53 100644 --- a/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output +++ b/unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output @@ -2,14 +2,21 @@ import Foundation.Networking --- -source_file - statement: - import_declaration - name: - identifier - part: - simple_identifier "Foundation" - simple_identifier "Networking" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + importDecl + attributes: + modifiers: + importKeyword: import + path: + importPathComponent + name: identifier "Foundation" + trailingPeriod: . + importPathComponent + name: identifier "Networking" --- diff --git a/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output b/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output index 79d1fa8bcb6d..93319c522981 100644 --- a/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output +++ b/unified/extractor/tests/corpus/swift/desugar/scoped-import-uses-name-pattern.output @@ -2,15 +2,22 @@ import struct Foundation.Date --- -source_file - statement: - import_declaration - name: - identifier - part: - simple_identifier "Foundation" - simple_identifier "Date" - scoped_import_kind: struct +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + importDecl + attributes: + modifiers: + importKeyword: import + importKindSpecifier: struct + path: + importPathComponent + name: identifier "Foundation" + trailingPeriod: . + importPathComponent + name: identifier "Date" --- diff --git a/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output b/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output index 8db7e15e3dc2..583f33563d13 100644 --- a/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output +++ b/unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output @@ -2,12 +2,18 @@ import Foundation --- -source_file - statement: - import_declaration - name: - identifier - part: simple_identifier "Foundation" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + importDecl + attributes: + modifiers: + importKeyword: import + path: + importPathComponent + name: identifier "Foundation" --- diff --git a/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output b/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output index 6c975e37be04..f885d5762f2d 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output +++ b/unified/extractor/tests/corpus/swift/functions/function-call-with-labelled-arguments.output @@ -2,22 +2,29 @@ greet(person: "Bob") --- -source_file - statement: - call_expression - function: simple_identifier "greet" - suffix: - call_suffix +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) arguments: - value_arguments - argument: - value_argument - name: - value_argument_label - name: simple_identifier "person" - value: - line_string_literal - text: line_str_text "Bob" + labeledExpr + colon: : + label: identifier "person" + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "Bob" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "greet" --- diff --git a/unified/extractor/tests/corpus/swift/functions/function-call.output b/unified/extractor/tests/corpus/swift/functions/function-call.output index 2e8e107b3a20..a1e2859ccd6a 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-call.output +++ b/unified/extractor/tests/corpus/swift/functions/function-call.output @@ -2,19 +2,28 @@ foo(1, 2) --- -source_file - statement: - call_expression - function: simple_identifier "foo" - suffix: - call_suffix +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) arguments: - value_arguments - argument: - value_argument - value: integer_literal "1" - value_argument - value: integer_literal "2" + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "1" + trailingComma: , + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "2" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "foo" --- diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output b/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output index 2c55abad52d9..01c0ddef8560 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output @@ -4,37 +4,60 @@ func greet(name: String = "world") { --- -source_file - statement: - function_declaration - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "name" - name: simple_identifier "greet" - parameter: - function_parameter - default_value: - line_string_literal - text: line_str_text "world" - parameter: - parameter - name: simple_identifier "name" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "String" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "name" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + name: identifier "greet" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "String" + firstName: identifier "name" + defaultValue: + initializerClause + equal: = + value: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "world" + funcKeyword: func --- diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output b/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output index d9eb41963965..f2024b473620 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output @@ -4,35 +4,51 @@ func greet(person name: String) { --- -source_file - statement: - function_declaration - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "name" - name: simple_identifier "greet" - parameter: - function_parameter - parameter: - parameter - external_name: simple_identifier "person" - name: simple_identifier "name" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "String" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "name" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + name: identifier "greet" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "String" + firstName: identifier "person" + secondName: identifier "name" + funcKeyword: func --- diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output b/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output index b59ed702f296..4ddc26ae94d6 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output @@ -4,24 +4,46 @@ func greet() { --- -source_file - statement: - function_declaration - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: - line_string_literal - text: line_str_text "hello" - name: simple_identifier "greet" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "hello" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + name: identifier "greet" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + funcKeyword: func --- diff --git a/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output b/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output index eca072fed022..1ffb391dbc5a 100644 --- a/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output +++ b/unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output @@ -4,52 +4,68 @@ func add(_ a: Int, _ b: Int) -> Int { --- -source_file - statement: - function_declaration - body: - block - statement: - control_transfer_statement - kind: return - result: - additive_expression - lhs: simple_identifier "a" - op: + - rhs: simple_identifier "b" - name: simple_identifier "add" - parameter: - function_parameter - parameter: - parameter - external_name: simple_identifier "_" - name: simple_identifier "a" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - function_parameter - parameter: - parameter - external_name: simple_identifier "_" - name: simple_identifier "b" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - return_type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + returnStmt + expression: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" + returnKeyword: return + name: identifier "add" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + trailingComma: , + type: + identifierType + name: identifier "Int" + firstName: _ + secondName: identifier "a" + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: _ + secondName: identifier "b" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "Int" + funcKeyword: func --- diff --git a/unified/extractor/tests/corpus/swift/functions/generic-function.output b/unified/extractor/tests/corpus/swift/functions/generic-function.output index 5fb2d4b03899..1652f7f47414 100644 --- a/unified/extractor/tests/corpus/swift/functions/generic-function.output +++ b/unified/extractor/tests/corpus/swift/functions/generic-function.output @@ -4,41 +4,58 @@ func identity(_ x: T) -> T { --- -source_file - statement: - function_declaration - body: - block - statement: - control_transfer_statement - kind: return - result: simple_identifier "x" - name: simple_identifier "identity" - parameter: - function_parameter - parameter: - parameter - external_name: simple_identifier "_" - name: simple_identifier "x" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "T" - return_type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "T" - type_parameters: - type_parameters - parameter: - type_parameter - name: type_identifier "T" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + returnStmt + expression: + declReferenceExpr + baseName: identifier "x" + returnKeyword: return + name: identifier "identity" + genericParameterClause: + genericParameterClause + parameters: + genericParameter + attributes: + name: identifier "T" + leftAngle: < + rightAngle: > + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "T" + firstName: _ + secondName: identifier "x" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "T" + funcKeyword: func --- diff --git a/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output b/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output index 75fa887f25ac..d0a844d02afa 100644 --- a/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output +++ b/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output @@ -2,30 +2,39 @@ let y = .some(1) --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "y" - value: - call_expression - function: - prefix_expression - operation: . - target: simple_identifier "some" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: integer_literal "1" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "1" + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "some" + pattern: + identifierPattern + identifier: identifier "y" --- diff --git a/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-value.output b/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-value.output index 85aeeffde6eb..bec014593d91 100644 --- a/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-value.output +++ b/unified/extractor/tests/corpus/swift/functions/leading-dot-expression-value.output @@ -2,21 +2,29 @@ let x = .foo --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - value: - prefix_expression - operation: . - target: simple_identifier "foo" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "foo" + pattern: + identifierPattern + identifier: identifier "x" --- diff --git a/unified/extractor/tests/corpus/swift/functions/method-call.output b/unified/extractor/tests/corpus/swift/functions/method-call.output index 76d6d3206d19..3c0b150b8235 100644 --- a/unified/extractor/tests/corpus/swift/functions/method-call.output +++ b/unified/extractor/tests/corpus/swift/functions/method-call.output @@ -2,22 +2,29 @@ list.append(1) --- -source_file - statement: - call_expression - function: - navigation_expression - suffix: - navigation_suffix - suffix: simple_identifier "append" - target: simple_identifier "list" - suffix: - call_suffix +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) arguments: - value_arguments - argument: - value_argument - value: integer_literal "1" + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "1" + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "append" + base: + declReferenceExpr + baseName: identifier "list" --- diff --git a/unified/extractor/tests/corpus/swift/functions/variadic-function.output b/unified/extractor/tests/corpus/swift/functions/variadic-function.output index da8d4afd4d7c..571f07c35e0c 100644 --- a/unified/extractor/tests/corpus/swift/functions/variadic-function.output +++ b/unified/extractor/tests/corpus/swift/functions/variadic-function.output @@ -4,54 +4,72 @@ func sum(_ values: Int...) -> Int { --- -source_file - statement: - function_declaration - body: - block - statement: - control_transfer_statement - kind: return - result: - call_expression - function: - navigation_expression - suffix: - navigation_suffix - suffix: simple_identifier "reduce" - target: simple_identifier "values" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: integer_literal "0" - value_argument - value: - referenceable_operator - operator: + - name: simple_identifier "sum" - parameter: - function_parameter - parameter: - parameter - external_name: simple_identifier "_" - name: simple_identifier "values" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - return_type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + returnStmt + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "0" + trailingComma: , + labeledExpr + expression: + declReferenceExpr + baseName: binaryOperator "+" + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "reduce" + base: + declReferenceExpr + baseName: identifier "values" + returnKeyword: return + name: identifier "sum" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + ellipsis: ... + firstName: _ + secondName: identifier "values" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "Int" + funcKeyword: func --- diff --git a/unified/extractor/tests/corpus/swift/literals/boolean-literals.output b/unified/extractor/tests/corpus/swift/literals/boolean-literals.output index d31893de0522..34b394712a35 100644 --- a/unified/extractor/tests/corpus/swift/literals/boolean-literals.output +++ b/unified/extractor/tests/corpus/swift/literals/boolean-literals.output @@ -3,10 +3,17 @@ false --- -source_file - statement: - boolean_literal - boolean_literal +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + booleanLiteralExpr + literal: true + codeBlockItem + item: + booleanLiteralExpr + literal: false --- diff --git a/unified/extractor/tests/corpus/swift/literals/floating-point-literal.output b/unified/extractor/tests/corpus/swift/literals/floating-point-literal.output index 0c374dc4452c..19fa40aac776 100644 --- a/unified/extractor/tests/corpus/swift/literals/floating-point-literal.output +++ b/unified/extractor/tests/corpus/swift/literals/floating-point-literal.output @@ -2,8 +2,13 @@ --- -source_file - statement: real_literal "3.14" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + floatLiteralExpr + literal: floatLiteral "3.14" --- diff --git a/unified/extractor/tests/corpus/swift/literals/integer-literal.output b/unified/extractor/tests/corpus/swift/literals/integer-literal.output index 018c57983948..9df79925753c 100644 --- a/unified/extractor/tests/corpus/swift/literals/integer-literal.output +++ b/unified/extractor/tests/corpus/swift/literals/integer-literal.output @@ -2,8 +2,13 @@ --- -source_file - statement: integer_literal "42" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + integerLiteralExpr + literal: integerLiteral "42" --- diff --git a/unified/extractor/tests/corpus/swift/literals/negative-integer-literal.output b/unified/extractor/tests/corpus/swift/literals/negative-integer-literal.output index e1ca11e070ab..907944054782 100644 --- a/unified/extractor/tests/corpus/swift/literals/negative-integer-literal.output +++ b/unified/extractor/tests/corpus/swift/literals/negative-integer-literal.output @@ -2,11 +2,16 @@ --- -source_file - statement: - prefix_expression - operation: - - target: integer_literal "7" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + prefixOperatorExpr + expression: + integerLiteralExpr + literal: integerLiteral "7" + operator: prefixOperator "-" --- diff --git a/unified/extractor/tests/corpus/swift/literals/nil-literal.output b/unified/extractor/tests/corpus/swift/literals/nil-literal.output index 6c826cabe7db..c7da232131fd 100644 --- a/unified/extractor/tests/corpus/swift/literals/nil-literal.output +++ b/unified/extractor/tests/corpus/swift/literals/nil-literal.output @@ -2,8 +2,13 @@ nil --- -source_file - statement: nil +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + nilLiteralExpr + nilKeyword: nil --- diff --git a/unified/extractor/tests/corpus/swift/literals/string-literal.output b/unified/extractor/tests/corpus/swift/literals/string-literal.output index ca2ac6df325d..8d3ea8e796c0 100644 --- a/unified/extractor/tests/corpus/swift/literals/string-literal.output +++ b/unified/extractor/tests/corpus/swift/literals/string-literal.output @@ -2,10 +2,17 @@ --- -source_file - statement: - line_string_literal - text: line_str_text "hello" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "hello" --- diff --git a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output index eb56fbbbb03f..5207085d174c 100644 --- a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output +++ b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output @@ -2,13 +2,28 @@ --- -source_file - statement: - line_string_literal - interpolation: - interpolated_expression - value: simple_identifier "name" - text: line_str_text "hello " +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "hello " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "name" + stringSegment + content: stringSegment --- diff --git a/unified/extractor/tests/corpus/swift/loops/break-and-continue.output b/unified/extractor/tests/corpus/swift/loops/break-and-continue.output index 76b2c77b968d..7ce869998272 100644 --- a/unified/extractor/tests/corpus/swift/loops/break-and-continue.output +++ b/unified/extractor/tests/corpus/swift/loops/break-and-continue.output @@ -6,51 +6,95 @@ for x in xs { --- -source_file - statement: - for_statement - body: - block - statement: - if_statement - body: - block - statement: - control_transfer_statement - kind: continue - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: < - rhs: integer_literal "0" - if_statement - body: - block - statement: - control_transfer_statement - kind: break - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "100" - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "x" - collection: simple_identifier "xs" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem item: - pattern - bound_identifier: simple_identifier "x" + forStmt + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + expressionStmt + expression: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + continueStmt + continueKeyword: continue + conditions: + conditionElement + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "<" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + ifKeyword: if + codeBlockItem + item: + expressionStmt + expression: + ifExpr + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + breakStmt + breakKeyword: break + conditions: + conditionElement + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "100" + ifKeyword: if + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + pattern: + identifierPattern + identifier: identifier "x" + inKeyword: in + forKeyword: for + sequence: + declReferenceExpr + baseName: identifier "xs" --- diff --git a/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output b/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output index cd1e6d8baab1..2d8db27bfe85 100644 --- a/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output +++ b/unified/extractor/tests/corpus/swift/loops/for-in-over-array-literal.output @@ -4,30 +4,55 @@ for x in [1, 2, 3] { --- -source_file - statement: - for_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "x" - collection: - array_literal - element: - integer_literal "1" - integer_literal "2" - integer_literal "3" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem item: - pattern - bound_identifier: simple_identifier "x" + forStmt + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + pattern: + identifierPattern + identifier: identifier "x" + inKeyword: in + forKeyword: for + sequence: + arrayExpr + elements: + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "1" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "2" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "3" + leftSquare: [ + rightSquare: ] --- diff --git a/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output b/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output index eb65a677d45b..cb8b4e67b68d 100644 --- a/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output +++ b/unified/extractor/tests/corpus/swift/loops/for-in-over-range.output @@ -4,29 +4,47 @@ for i in 0..<10 { --- -source_file - statement: - for_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "i" - collection: - range_expression - end: integer_literal "10" - op: ..< - start: integer_literal "0" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem item: - pattern - bound_identifier: simple_identifier "i" + forStmt + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "i" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + pattern: + identifierPattern + identifier: identifier "i" + inKeyword: in + forKeyword: for + sequence: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "..<" + leftOperand: + integerLiteralExpr + literal: integerLiteral "0" + rightOperand: + integerLiteralExpr + literal: integerLiteral "10" --- diff --git a/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output b/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output index be538bde4473..6659039029f3 100644 --- a/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output +++ b/unified/extractor/tests/corpus/swift/loops/for-in-with-where-clause.output @@ -4,33 +4,53 @@ for x in xs where x > 0 { --- -source_file - statement: - for_statement - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "x" - collection: simple_identifier "xs" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem item: - pattern - bound_identifier: simple_identifier "x" - where: - where_clause - expr: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "0" - keyword: where_keyword "where" + forStmt + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + pattern: + identifierPattern + identifier: identifier "x" + whereClause: + whereClause + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + whereKeyword: where + inKeyword: in + forKeyword: for + sequence: + declReferenceExpr + baseName: identifier "xs" --- diff --git a/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output b/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output index 547a50de735b..71c49fd2cd10 100644 --- a/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output +++ b/unified/extractor/tests/corpus/swift/loops/repeat-while-loop.output @@ -4,25 +4,42 @@ repeat { --- -source_file - statement: - repeat_while_statement - body: - block - statement: - assignment - operator: -= - result: integer_literal "1" - target: - directly_assignable_expression - expr: simple_identifier "x" - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "0" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + repeatStmt + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "-=" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "1" + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + repeatKeyword: repeat + whileKeyword: while --- diff --git a/unified/extractor/tests/corpus/swift/loops/while-loop.output b/unified/extractor/tests/corpus/swift/loops/while-loop.output index 7a57bb42068f..1132c6f9f819 100644 --- a/unified/extractor/tests/corpus/swift/loops/while-loop.output +++ b/unified/extractor/tests/corpus/swift/loops/while-loop.output @@ -4,25 +4,43 @@ while x > 0 { --- -source_file - statement: - while_statement - body: - block - statement: - assignment - operator: -= - result: integer_literal "1" - target: - directly_assignable_expression - expr: simple_identifier "x" - condition: - if_condition - kind: - comparison_expression - lhs: simple_identifier "x" - op: > - rhs: integer_literal "0" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + whileStmt + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "-=" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "1" + conditions: + conditionElement + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator ">" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + whileKeyword: while --- diff --git a/unified/extractor/tests/corpus/swift/operators/addition.output b/unified/extractor/tests/corpus/swift/operators/addition.output index 072682f6f381..9ba0f4de4770 100644 --- a/unified/extractor/tests/corpus/swift/operators/addition.output +++ b/unified/extractor/tests/corpus/swift/operators/addition.output @@ -2,12 +2,21 @@ a + b --- -source_file - statement: - additive_expression - lhs: simple_identifier "a" - op: + - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/operators/comparison.output b/unified/extractor/tests/corpus/swift/operators/comparison.output index aeee98ecd535..49bb8c996f2c 100644 --- a/unified/extractor/tests/corpus/swift/operators/comparison.output +++ b/unified/extractor/tests/corpus/swift/operators/comparison.output @@ -2,12 +2,21 @@ a < b --- -source_file - statement: - comparison_expression - lhs: simple_identifier "a" - op: < - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "<" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/operators/division.output b/unified/extractor/tests/corpus/swift/operators/division.output index c2a0a03a23f3..f15dce8e8ea5 100644 --- a/unified/extractor/tests/corpus/swift/operators/division.output +++ b/unified/extractor/tests/corpus/swift/operators/division.output @@ -2,12 +2,21 @@ a / b --- -source_file - statement: - multiplicative_expression - lhs: simple_identifier "a" - op: / - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "/" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/operators/equality.output b/unified/extractor/tests/corpus/swift/operators/equality.output index 64c2fb78b178..7cf139ffa18d 100644 --- a/unified/extractor/tests/corpus/swift/operators/equality.output +++ b/unified/extractor/tests/corpus/swift/operators/equality.output @@ -2,12 +2,21 @@ a == b --- -source_file - statement: - equality_expression - lhs: simple_identifier "a" - op: == - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "==" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/operators/logical-and.output b/unified/extractor/tests/corpus/swift/operators/logical-and.output index fbdbd904eafe..32a71ed1088c 100644 --- a/unified/extractor/tests/corpus/swift/operators/logical-and.output +++ b/unified/extractor/tests/corpus/swift/operators/logical-and.output @@ -2,12 +2,21 @@ a && b --- -source_file - statement: - conjunction_expression - lhs: simple_identifier "a" - op: && - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "&&" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/operators/logical-not.output b/unified/extractor/tests/corpus/swift/operators/logical-not.output index d07e357620fd..1e80aa2ca71e 100644 --- a/unified/extractor/tests/corpus/swift/operators/logical-not.output +++ b/unified/extractor/tests/corpus/swift/operators/logical-not.output @@ -2,11 +2,16 @@ --- -source_file - statement: - prefix_expression - operation: bang "!" - target: simple_identifier "a" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + prefixOperatorExpr + expression: + declReferenceExpr + baseName: identifier "a" + operator: prefixOperator "!" --- diff --git a/unified/extractor/tests/corpus/swift/operators/logical-or.output b/unified/extractor/tests/corpus/swift/operators/logical-or.output index 5d15828065cf..b75d018dea71 100644 --- a/unified/extractor/tests/corpus/swift/operators/logical-or.output +++ b/unified/extractor/tests/corpus/swift/operators/logical-or.output @@ -2,12 +2,21 @@ a || b --- -source_file - statement: - disjunction_expression - lhs: simple_identifier "a" - op: || - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "||" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/operators/multiplication.output b/unified/extractor/tests/corpus/swift/operators/multiplication.output index 77ce11a659e4..387c16439c48 100644 --- a/unified/extractor/tests/corpus/swift/operators/multiplication.output +++ b/unified/extractor/tests/corpus/swift/operators/multiplication.output @@ -2,12 +2,21 @@ a * b --- -source_file - statement: - multiplicative_expression - lhs: simple_identifier "a" - op: * - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output b/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output index 2c89c306a6d0..f458c3ef847c 100644 --- a/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output +++ b/unified/extractor/tests/corpus/swift/operators/operator-precedence-addition-and-multiplication.output @@ -2,16 +2,29 @@ a + b * c --- -source_file - statement: - additive_expression - lhs: simple_identifier "a" - op: + - rhs: - multiplicative_expression - lhs: simple_identifier "b" - op: * - rhs: simple_identifier "c" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: identifier "b" + rightOperand: + declReferenceExpr + baseName: identifier "c" --- diff --git a/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output b/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output index 36216b4201f4..0324476ce994 100644 --- a/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output +++ b/unified/extractor/tests/corpus/swift/operators/parenthesised-expression.output @@ -2,20 +2,35 @@ --- -source_file - statement: - multiplicative_expression - lhs: - tuple_expression - element: - tuple_expression_item - value: - additive_expression - lhs: simple_identifier "a" - op: + - rhs: simple_identifier "b" - op: * - rhs: simple_identifier "c" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + tupleExpr + leftParen: ( + rightParen: ) + elements: + labeledExpr + expression: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" + rightOperand: + declReferenceExpr + baseName: identifier "c" --- diff --git a/unified/extractor/tests/corpus/swift/operators/range-operator.output b/unified/extractor/tests/corpus/swift/operators/range-operator.output index 574eccd9795e..55ed3ab971a7 100644 --- a/unified/extractor/tests/corpus/swift/operators/range-operator.output +++ b/unified/extractor/tests/corpus/swift/operators/range-operator.output @@ -2,12 +2,21 @@ --- -source_file - statement: - range_expression - end: integer_literal "10" - op: ... - start: integer_literal "1" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "..." + leftOperand: + integerLiteralExpr + literal: integerLiteral "1" + rightOperand: + integerLiteralExpr + literal: integerLiteral "10" --- diff --git a/unified/extractor/tests/corpus/swift/operators/subtraction.output b/unified/extractor/tests/corpus/swift/operators/subtraction.output index 993a6c3b6838..5c6fd51a2d38 100644 --- a/unified/extractor/tests/corpus/swift/operators/subtraction.output +++ b/unified/extractor/tests/corpus/swift/operators/subtraction.output @@ -2,12 +2,21 @@ a - b --- -source_file - statement: - additive_expression - lhs: simple_identifier "a" - op: - - rhs: simple_identifier "b" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "-" + leftOperand: + declReferenceExpr + baseName: identifier "a" + rightOperand: + declReferenceExpr + baseName: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output index 1178570e5117..81491b295fc1 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output @@ -6,37 +6,54 @@ do { --- -source_file - statement: - do_statement - body: - block - statement: - try_expression - expr: - call_expression - function: simple_identifier "foo" - suffix: - call_suffix - arguments: - value_arguments - operator: - try_operator - catch: - catch_block +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + doStmt body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "error" - keyword: catch_keyword "catch" + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + tryExpr + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "foo" + tryKeyword: try + catchClauses: + catchClause + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "error" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + catchItems: + catchKeyword: catch + doKeyword: do --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/force-unwrap.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/force-unwrap.output index 96fb627e18b1..2c6fd1a6f763 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/force-unwrap.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/force-unwrap.output @@ -2,21 +2,29 @@ let n = opt! --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "n" - value: - postfix_expression - operation: bang "!" - target: simple_identifier "opt" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + forceUnwrapExpr + expression: + declReferenceExpr + baseName: identifier "opt" + exclamationMark: ! + pattern: + identifierPattern + identifier: identifier "n" --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output index c0b3a3a9783a..31a039ad0065 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/nil-coalescing.output @@ -2,21 +2,34 @@ let n = opt ?? 0 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "n" - value: - nil_coalescing_expression - if_nil: integer_literal "0" - value: simple_identifier "opt" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "??" + leftOperand: + declReferenceExpr + baseName: identifier "opt" + rightOperand: + integerLiteralExpr + literal: integerLiteral "0" + pattern: + identifierPattern + identifier: identifier "n" --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-chaining.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-chaining.output index 6c5b27a64fe2..b69b0ae47d50 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-chaining.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-chaining.output @@ -2,32 +2,44 @@ let n = obj?.foo?.bar --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "n" - value: - navigation_expression - suffix: - navigation_suffix - suffix: simple_identifier "bar" - target: - optional_chain_marker - expr: - navigation_expression - suffix: - navigation_suffix - suffix: simple_identifier "foo" - target: - optional_chain_marker - expr: simple_identifier "obj" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "bar" + base: + optionalChainingExpr + expression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "foo" + base: + optionalChainingExpr + expression: + declReferenceExpr + baseName: identifier "obj" + questionMark: ? + questionMark: ? + pattern: + identifierPattern + identifier: identifier "n" --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-type-annotation.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-type-annotation.output index 06191891496e..1927b494ef9a 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-type-annotation.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/optional-type-annotation.output @@ -2,29 +2,35 @@ let x: Int? = nil --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - type: - type_annotation - type: - type - name: - optional_type - wrapped: - user_type - part: - simple_user_type - name: type_identifier "Int" - value: nil +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + nilLiteralExpr + nilKeyword: nil + pattern: + identifierPattern + identifier: identifier "x" + typeAnnotation: + typeAnnotation + colon: : + type: + optionalType + wrappedType: + identifierType + name: identifier "Int" + questionMark: ? --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output index 880128cd372f..e68b4c3c57ac 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output @@ -4,25 +4,50 @@ func read() throws -> String { --- -source_file - statement: - function_declaration - body: - block - statement: - control_transfer_statement - kind: return - result: - line_string_literal - name: simple_identifier "read" - return_type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "String" - throws: throws "throws" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + returnStmt + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment + returnKeyword: return + name: identifier "read" + modifiers: + signature: + functionSignature + effectSpecifiers: + functionEffectSpecifiers + throwsClause: + throwsClause + throwsSpecifier: throws + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "String" + funcKeyword: func --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression-2.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression-2.output index 9d5ff032d75c..aafc0c67c217 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression-2.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression-2.output @@ -2,28 +2,36 @@ let result = try! foo() --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "result" - value: - try_expression - expr: - call_expression - function: simple_identifier "foo" - suffix: - call_suffix - arguments: - value_arguments - operator: - try_operator +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + tryExpr + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "foo" + questionOrExclamationMark: ! + tryKeyword: try + pattern: + identifierPattern + identifier: identifier "result" --- diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression.output b/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression.output index e6a7bfef3444..5b1b0b24d0fa 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression.output +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression.output @@ -2,28 +2,36 @@ let result = try? foo() --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "result" - value: - try_expression - expr: - call_expression - function: simple_identifier "foo" - suffix: - call_suffix - arguments: - value_arguments - operator: - try_operator +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + tryExpr + expression: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "foo" + questionOrExclamationMark: ? + tryKeyword: try + pattern: + identifierPattern + identifier: identifier "result" --- diff --git a/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output b/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output index dc137a3e621f..c05bc7794437 100644 --- a/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output +++ b/unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output @@ -11,54 +11,84 @@ var p: Int { --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - computed_value: - computed_property - accessor: - computed_getter - body: - block - statement: - switch_statement - entry: - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: simple_identifier "someConstant" - statement: - control_transfer_statement - kind: return - result: integer_literal "1" - switch_entry - default: default_keyword "default" - statement: - control_transfer_statement - kind: return - result: integer_literal "2" - expr: simple_identifier "y" - specifier: - getter_specifier - name: - pattern - bound_identifier: simple_identifier "p" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "p" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: get + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + expressionStmt + expression: + switchExpr + leftBrace: { + rightBrace: } + cases: + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + declReferenceExpr + baseName: identifier "someConstant" + statements: + codeBlockItem + item: + returnStmt + expression: + integerLiteralExpr + literal: integerLiteral "1" + returnKeyword: return + switchCase + label: + switchDefaultLabel + colon: : + defaultKeyword: default + statements: + codeBlockItem + item: + returnStmt + expression: + integerLiteralExpr + literal: integerLiteral "2" + returnKeyword: return + subject: + declReferenceExpr + baseName: identifier "y" + switchKeyword: switch + leftBrace: { + rightBrace: } --- diff --git a/unified/extractor/tests/corpus/swift/types/class-inheritance.output b/unified/extractor/tests/corpus/swift/types/class-inheritance.output index e90cccc4a8b6..62a0a43414d0 100644 --- a/unified/extractor/tests/corpus/swift/types/class-inheritance.output +++ b/unified/extractor/tests/corpus/swift/types/class-inheritance.output @@ -2,20 +2,29 @@ class Dog: Animal {} --- -source_file - statement: - class_declaration - body: - class_body - declaration_kind: class - inherits: - inheritance_specifier - inherits_from: - user_type - part: - simple_user_type - name: type_identifier "Animal" - name: type_identifier "Dog" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Dog" + inheritanceClause: + inheritanceClause + colon: : + inheritedTypes: + inheritedType + type: + identifierType + name: identifier "Animal" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + modifiers: + classKeyword: class --- diff --git a/unified/extractor/tests/corpus/swift/types/class-with-initializer.output b/unified/extractor/tests/corpus/swift/types/class-with-initializer.output index 77cfa70ac351..5e712d739e5f 100644 --- a/unified/extractor/tests/corpus/swift/types/class-with-initializer.output +++ b/unified/extractor/tests/corpus/swift/types/class-with-initializer.output @@ -7,60 +7,82 @@ class Point { --- -source_file - statement: - class_declaration - body: - class_body - member: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - init_declaration - body: - block - statement: - assignment - operator: = - result: simple_identifier "x" - target: - directly_assignable_expression - expr: - navigation_expression - suffix: - navigation_suffix - suffix: simple_identifier "x" - target: - self_expression - parameter: - function_parameter - parameter: - parameter - name: simple_identifier "x" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - declaration_kind: class - name: type_identifier "Point" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Point" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "x" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + memberBlockItem + decl: + initializerDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + assignmentExpr + equal: = + leftOperand: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "x" + base: + declReferenceExpr + baseName: self + rightOperand: + declReferenceExpr + baseName: identifier "x" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: identifier "x" + initKeyword: init + modifiers: + classKeyword: class --- diff --git a/unified/extractor/tests/corpus/swift/types/class-with-method.output b/unified/extractor/tests/corpus/swift/types/class-with-method.output index 20152cd26c8e..f45cb31e53ad 100644 --- a/unified/extractor/tests/corpus/swift/types/class-with-method.output +++ b/unified/extractor/tests/corpus/swift/types/class-with-method.output @@ -7,35 +7,69 @@ class Counter { --- -source_file - statement: - class_declaration - body: - class_body - member: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "n" - value: integer_literal "0" - function_declaration - body: - block - statement: - assignment - operator: += - result: integer_literal "1" - target: - directly_assignable_expression - expr: simple_identifier "n" - name: simple_identifier "bump" - declaration_kind: class - name: type_identifier "Counter" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Counter" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "0" + pattern: + identifierPattern + identifier: identifier "n" + memberBlockItem + decl: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+=" + leftOperand: + declReferenceExpr + baseName: identifier "n" + rightOperand: + integerLiteralExpr + literal: integerLiteral "1" + name: identifier "bump" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + funcKeyword: func + modifiers: + classKeyword: class --- diff --git a/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output b/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output index c2ae82ea3dae..e184426eb755 100644 --- a/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output +++ b/unified/extractor/tests/corpus/swift/types/class-with-stored-properties.output @@ -5,50 +5,55 @@ class Point { --- -source_file - statement: - class_declaration - body: - class_body - member: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "y" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - declaration_kind: class - name: type_identifier "Point" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Point" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "x" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "y" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + modifiers: + classKeyword: class --- diff --git a/unified/extractor/tests/corpus/swift/types/computed-property.output b/unified/extractor/tests/corpus/swift/types/computed-property.output index 287f75956330..24e816be0091 100644 --- a/unified/extractor/tests/corpus/swift/types/computed-property.output +++ b/unified/extractor/tests/corpus/swift/types/computed-property.output @@ -8,78 +8,92 @@ class Rect { --- -source_file - statement: - class_declaration - body: - class_body - member: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "w" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Double" - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "h" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Double" - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - computed_value: - computed_property - statement: - control_transfer_statement - kind: return - result: - multiplicative_expression - lhs: simple_identifier "w" - op: * - rhs: simple_identifier "h" - name: - pattern - bound_identifier: simple_identifier "area" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Double" - declaration_kind: class - name: type_identifier "Rect" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Rect" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "w" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Double" + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "h" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Double" + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "area" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Double" + accessorBlock: + accessorBlock + accessors: + codeBlockItem + item: + returnStmt + expression: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: identifier "w" + rightOperand: + declReferenceExpr + baseName: identifier "h" + returnKeyword: return + leftBrace: { + rightBrace: } + modifiers: + classKeyword: class --- diff --git a/unified/extractor/tests/corpus/swift/types/empty-class.output b/unified/extractor/tests/corpus/swift/types/empty-class.output index 761de778543d..6693744c9b43 100644 --- a/unified/extractor/tests/corpus/swift/types/empty-class.output +++ b/unified/extractor/tests/corpus/swift/types/empty-class.output @@ -2,13 +2,21 @@ class Foo {} --- -source_file - statement: - class_declaration - body: - class_body - declaration_kind: class - name: type_identifier "Foo" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Foo" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + modifiers: + classKeyword: class --- diff --git a/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output b/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output index f520b649095a..399239bbea30 100644 --- a/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output +++ b/unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output @@ -5,46 +5,63 @@ enum Shape { --- -source_file - statement: - class_declaration - body: - enum_class_body - member: - enum_entry - case: - enum_case_entry - data_contents: - enum_type_parameters - parameter: - enum_type_parameter - name: simple_identifier "radius" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Double" - name: simple_identifier "circle" - enum_entry - case: - enum_case_entry - data_contents: - enum_type_parameters - parameter: - enum_type_parameter - name: simple_identifier "side" - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Double" - name: simple_identifier "square" - declaration_kind: enum - name: type_identifier "Shape" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + enumDecl + attributes: + name: identifier "Shape" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + enumCaseDecl + attributes: + modifiers: + elements: + enumCaseElement + name: identifier "circle" + parameterClause: + enumCaseParameterClause + leftParen: ( + rightParen: ) + parameters: + enumCaseParameter + colon: : + modifiers: + type: + identifierType + name: identifier "Double" + firstName: identifier "radius" + caseKeyword: case + memberBlockItem + decl: + enumCaseDecl + attributes: + modifiers: + elements: + enumCaseElement + name: identifier "square" + parameterClause: + enumCaseParameterClause + leftParen: ( + rightParen: ) + parameters: + enumCaseParameter + colon: : + modifiers: + type: + identifierType + name: identifier "Double" + firstName: identifier "side" + caseKeyword: case + modifiers: + enumKeyword: enum --- diff --git a/unified/extractor/tests/corpus/swift/types/enum-with-cases.output b/unified/extractor/tests/corpus/swift/types/enum-with-cases.output index f70f43bfb8ec..f081c2a82046 100644 --- a/unified/extractor/tests/corpus/swift/types/enum-with-cases.output +++ b/unified/extractor/tests/corpus/swift/types/enum-with-cases.output @@ -7,30 +7,57 @@ enum Direction { --- -source_file - statement: - class_declaration - body: - enum_class_body - member: - enum_entry - case: - enum_case_entry - name: simple_identifier "north" - enum_entry - case: - enum_case_entry - name: simple_identifier "south" - enum_entry - case: - enum_case_entry - name: simple_identifier "east" - enum_entry - case: - enum_case_entry - name: simple_identifier "west" - declaration_kind: enum - name: type_identifier "Direction" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + enumDecl + attributes: + name: identifier "Direction" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + enumCaseDecl + attributes: + modifiers: + elements: + enumCaseElement + name: identifier "north" + caseKeyword: case + memberBlockItem + decl: + enumCaseDecl + attributes: + modifiers: + elements: + enumCaseElement + name: identifier "south" + caseKeyword: case + memberBlockItem + decl: + enumCaseDecl + attributes: + modifiers: + elements: + enumCaseElement + name: identifier "east" + caseKeyword: case + memberBlockItem + decl: + enumCaseDecl + attributes: + modifiers: + elements: + enumCaseElement + name: identifier "west" + caseKeyword: case + modifiers: + enumKeyword: enum --- diff --git a/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output b/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output index 56e793247bbf..6a4ea95e9a62 100644 --- a/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output +++ b/unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output @@ -4,24 +4,39 @@ enum Suit { --- -source_file - statement: - class_declaration - body: - enum_class_body - member: - enum_entry - case: - enum_case_entry - name: simple_identifier "clubs" - enum_case_entry - name: simple_identifier "diamonds" - enum_case_entry - name: simple_identifier "hearts" - enum_case_entry - name: simple_identifier "spades" - declaration_kind: enum - name: type_identifier "Suit" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + enumDecl + attributes: + name: identifier "Suit" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + enumCaseDecl + attributes: + modifiers: + elements: + enumCaseElement + name: identifier "clubs" + trailingComma: , + enumCaseElement + name: identifier "diamonds" + trailingComma: , + enumCaseElement + name: identifier "hearts" + trailingComma: , + enumCaseElement + name: identifier "spades" + caseKeyword: case + modifiers: + enumKeyword: enum --- diff --git a/unified/extractor/tests/corpus/swift/types/extension.output b/unified/extractor/tests/corpus/swift/types/extension.output index 894b8c530c81..1b1d02ebddad 100644 --- a/unified/extractor/tests/corpus/swift/types/extension.output +++ b/unified/extractor/tests/corpus/swift/types/extension.output @@ -4,39 +4,63 @@ extension Int { --- -source_file - statement: - class_declaration - body: - class_body - member: - function_declaration - body: - block - statement: - control_transfer_statement - kind: return - result: - multiplicative_expression - lhs: - self_expression - op: * - rhs: - self_expression - name: simple_identifier "squared" - return_type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - declaration_kind: extension - name: - user_type - part: - simple_user_type - name: type_identifier "Int" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + extensionDecl + attributes: + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + returnStmt + expression: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: self + rightOperand: + declReferenceExpr + baseName: self + returnKeyword: return + name: identifier "squared" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "Int" + funcKeyword: func + modifiers: + extendedType: + identifierType + name: identifier "Int" + extensionKeyword: extension --- diff --git a/unified/extractor/tests/corpus/swift/types/protocol-declaration.output b/unified/extractor/tests/corpus/swift/types/protocol-declaration.output index 628b58e979f6..e848fb23eb3f 100644 --- a/unified/extractor/tests/corpus/swift/types/protocol-declaration.output +++ b/unified/extractor/tests/corpus/swift/types/protocol-declaration.output @@ -4,15 +4,35 @@ protocol Drawable { --- -source_file - statement: - protocol_declaration - body: - protocol_body - member: - protocol_function_declaration - name: simple_identifier "draw" - name: type_identifier "Drawable" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + protocolDecl + attributes: + name: identifier "Drawable" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + functionDecl + attributes: + name: identifier "draw" + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + funcKeyword: func + modifiers: + protocolKeyword: protocol --- diff --git a/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output b/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output index 11296b02e005..83362740bb1e 100644 --- a/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output +++ b/unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output @@ -5,54 +5,74 @@ protocol P { --- -source_file - statement: - protocol_declaration - body: - protocol_body - member: - protocol_property_declaration - name: - pattern - binding: - value_binding_pattern - mutability: var - bound_identifier: simple_identifier "foo" - requirements: - protocol_property_requirements - accessor: - getter_specifier - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - protocol_property_declaration - name: - pattern - binding: - value_binding_pattern - mutability: var - bound_identifier: simple_identifier "bar" - requirements: - protocol_property_requirements - accessor: - getter_specifier - setter_specifier - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "String" - name: type_identifier "P" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + protocolDecl + attributes: + name: identifier "P" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "foo" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: get + attributes: + leftBrace: { + rightBrace: } + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "bar" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "String" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: get + attributes: + accessorDecl + accessorSpecifier: set + attributes: + leftBrace: { + rightBrace: } + modifiers: + protocolKeyword: protocol --- diff --git a/unified/extractor/tests/corpus/swift/types/struct.output b/unified/extractor/tests/corpus/swift/types/struct.output index 7de3a4f5fde5..57fb25c9e193 100644 --- a/unified/extractor/tests/corpus/swift/types/struct.output +++ b/unified/extractor/tests/corpus/swift/types/struct.output @@ -5,50 +5,55 @@ struct Point { --- -source_file - statement: - class_declaration - body: - class_body - member: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "y" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - declaration_kind: struct - name: type_identifier "Point" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + structDecl + attributes: + name: identifier "Point" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "x" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "y" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + modifiers: + structKeyword: struct --- diff --git a/unified/extractor/tests/corpus/swift/variables/assignment.output b/unified/extractor/tests/corpus/swift/variables/assignment.output index 9d1a61e89a82..a011eb76cafc 100644 --- a/unified/extractor/tests/corpus/swift/variables/assignment.output +++ b/unified/extractor/tests/corpus/swift/variables/assignment.output @@ -2,14 +2,21 @@ x = 1 --- -source_file - statement: - assignment - operator: = - result: integer_literal "1" - target: - directly_assignable_expression - expr: simple_identifier "x" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + assignmentExpr + equal: = + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "1" --- diff --git a/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output b/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output index a6d554ca8370..d159cd6b37cb 100644 --- a/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output +++ b/unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output @@ -5,31 +5,59 @@ default: 2 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - value: - switch_statement - entry: - switch_entry - pattern: - switch_pattern - pattern: - pattern - kind: simple_identifier "someConstant" - statement: integer_literal "1" - switch_entry - default: default_keyword "default" - statement: integer_literal "2" - expr: simple_identifier "y" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + switchExpr + leftBrace: { + rightBrace: } + cases: + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + declReferenceExpr + baseName: identifier "someConstant" + statements: + codeBlockItem + item: + integerLiteralExpr + literal: integerLiteral "1" + switchCase + label: + switchDefaultLabel + colon: : + defaultKeyword: default + statements: + codeBlockItem + item: + integerLiteralExpr + literal: integerLiteral "2" + subject: + declReferenceExpr + baseName: identifier "y" + switchKeyword: switch + pattern: + identifierPattern + identifier: identifier "x" --- diff --git a/unified/extractor/tests/corpus/swift/variables/compound-assignment.output b/unified/extractor/tests/corpus/swift/variables/compound-assignment.output index 5385e0ae46be..485b5044ad69 100644 --- a/unified/extractor/tests/corpus/swift/variables/compound-assignment.output +++ b/unified/extractor/tests/corpus/swift/variables/compound-assignment.output @@ -2,14 +2,21 @@ x += 1 --- -source_file - statement: - assignment - operator: += - result: integer_literal "1" - target: - directly_assignable_expression - expr: simple_identifier "x" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+=" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + integerLiteralExpr + literal: integerLiteral "1" --- diff --git a/unified/extractor/tests/corpus/swift/variables/let-binding.output b/unified/extractor/tests/corpus/swift/variables/let-binding.output index b8b16dc80144..4774eb3eeca8 100644 --- a/unified/extractor/tests/corpus/swift/variables/let-binding.output +++ b/unified/extractor/tests/corpus/swift/variables/let-binding.output @@ -2,18 +2,26 @@ let x = 1 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - value: integer_literal "1" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "x" --- diff --git a/unified/extractor/tests/corpus/swift/variables/let-with-type-annotation.output b/unified/extractor/tests/corpus/swift/variables/let-with-type-annotation.output index 3fd78d09fa42..a56feb445ace 100644 --- a/unified/extractor/tests/corpus/swift/variables/let-with-type-annotation.output +++ b/unified/extractor/tests/corpus/swift/variables/let-with-type-annotation.output @@ -2,27 +2,32 @@ let x: Int = 1 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - value: integer_literal "1" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "x" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" --- diff --git a/unified/extractor/tests/corpus/swift/variables/multiple-bindings-on-one-line.output b/unified/extractor/tests/corpus/swift/variables/multiple-bindings-on-one-line.output index 7f202885b9be..9470a578ad62 100644 --- a/unified/extractor/tests/corpus/swift/variables/multiple-bindings-on-one-line.output +++ b/unified/extractor/tests/corpus/swift/variables/multiple-bindings-on-one-line.output @@ -2,23 +2,37 @@ let x = 1, y = 2 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - value: integer_literal "1" - property_binding - name: - pattern - bound_identifier: simple_identifier "y" - value: integer_literal "2" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + trailingComma: , + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "x" + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "2" + pattern: + identifierPattern + identifier: identifier "y" --- diff --git a/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output b/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output index 5bf4b48efbcc..8071d5d52900 100644 --- a/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output +++ b/unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output @@ -7,63 +7,93 @@ class C { --- -source_file - statement: - class_declaration - body: - class_body - member: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - observers: - willset_didset_block - didset: - didset_clause - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "oldValue" - willset: - willset_clause - body: - block - statement: - call_expression - function: simple_identifier "print" - suffix: - call_suffix - arguments: - value_arguments - argument: - value_argument - value: simple_identifier "newValue" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - value: integer_literal "0" - declaration_kind: class - name: type_identifier "C" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "C" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "0" + pattern: + identifierPattern + identifier: identifier "x" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: willSet + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "newValue" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + accessorDecl + accessorSpecifier: didSet + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "oldValue" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + leftBrace: { + rightBrace: } + modifiers: + classKeyword: class --- diff --git a/unified/extractor/tests/corpus/swift/variables/tuple-destructuring-binding.output b/unified/extractor/tests/corpus/swift/variables/tuple-destructuring-binding.output index 5709911171c1..6b6bd81115ed 100644 --- a/unified/extractor/tests/corpus/swift/variables/tuple-destructuring-binding.output +++ b/unified/extractor/tests/corpus/swift/variables/tuple-destructuring-binding.output @@ -2,28 +2,37 @@ let (a, b) = pair --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: let - declarator: - property_binding - name: - pattern - kind: - tuple_pattern - item: - tuple_pattern_item +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + declReferenceExpr + baseName: identifier "pair" + pattern: + tuplePattern + leftParen: ( + rightParen: ) + elements: + tuplePatternElement + trailingComma: , pattern: - pattern - kind: simple_identifier "a" - tuple_pattern_item + identifierPattern + identifier: identifier "a" + tuplePatternElement pattern: - pattern - kind: simple_identifier "b" - value: simple_identifier "pair" + identifierPattern + identifier: identifier "b" --- diff --git a/unified/extractor/tests/corpus/swift/variables/var-binding.output b/unified/extractor/tests/corpus/swift/variables/var-binding.output index b7dc8e772700..63498105dc75 100644 --- a/unified/extractor/tests/corpus/swift/variables/var-binding.output +++ b/unified/extractor/tests/corpus/swift/variables/var-binding.output @@ -2,18 +2,26 @@ var x = 1 --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - value: integer_literal "1" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "x" --- diff --git a/unified/extractor/tests/corpus/swift/variables/var-without-initialiser.output b/unified/extractor/tests/corpus/swift/variables/var-without-initialiser.output index 692befea8553..d841ce2bb583 100644 --- a/unified/extractor/tests/corpus/swift/variables/var-without-initialiser.output +++ b/unified/extractor/tests/corpus/swift/variables/var-without-initialiser.output @@ -2,26 +2,26 @@ var x: Int --- -source_file - statement: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "x" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "x" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" --- From 046c88a329eaa4fd2d444a1b3470cd5f92cb0925 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 23 Jul 2026 12:53:57 +0000 Subject: [PATCH 23/27] unified: Regenerate the enhanced getter/setter property corpus case Regenerate `types/property-with-getter-and-setter`, whose mapped AST now differs from the tree-sitter output: the backing `private var _v` retains its `private` modifier (`modifier "var"` + `modifier "private"`), whereas the tree-sitter path dropped it (its `visibility_modifier` node carried no text). The swift-syntax front-end preserves the modifier, so the mapped AST is strictly richer here. The raw (second) section is regenerated to the swift-syntax AST like the other cases; the mapped (third) section gains the retained `private` modifier. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../property-with-getter-and-setter.output | 159 +++++++++++------- 1 file changed, 94 insertions(+), 65 deletions(-) diff --git a/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output b/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output index a44c6fc3c127..951768cd8444 100644 --- a/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output +++ b/unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output @@ -8,70 +8,97 @@ class Box { --- -source_file - statement: - class_declaration - body: - class_body - member: - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - name: - pattern - bound_identifier: simple_identifier "_v" - value: integer_literal "0" - modifiers: - modifiers - modifier: - visibility_modifier - property_declaration - binding: - value_binding_pattern - mutability: var - declarator: - property_binding - computed_value: - computed_property - accessor: - computed_getter - body: - block - statement: - control_transfer_statement - kind: return - result: simple_identifier "_v" - specifier: - getter_specifier - computed_setter - body: - block - statement: - assignment - operator: = - result: simple_identifier "newValue" - target: - directly_assignable_expression - expr: simple_identifier "_v" - specifier: - setter_specifier - name: - pattern - bound_identifier: simple_identifier "v" - type: - type_annotation - type: - type - name: - user_type - part: - simple_user_type - name: type_identifier "Int" - declaration_kind: class - name: type_identifier "Box" +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Box" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + declModifier + name: private + bindingSpecifier: var + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "0" + pattern: + identifierPattern + identifier: identifier "_v" + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "v" + typeAnnotation: + typeAnnotation + colon: : + type: + identifierType + name: identifier "Int" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: get + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + returnStmt + expression: + declReferenceExpr + baseName: identifier "_v" + returnKeyword: return + accessorDecl + accessorSpecifier: set + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + assignmentExpr + equal: = + leftOperand: + declReferenceExpr + baseName: identifier "_v" + rightOperand: + declReferenceExpr + baseName: identifier "newValue" + leftBrace: { + rightBrace: } + modifiers: + classKeyword: class --- @@ -84,7 +111,9 @@ top_level name: identifier "Box" member: variable_declaration - modifier: modifier "var" + modifier: + modifier "var" + modifier "private" pattern: name_pattern identifier: identifier "_v" From c50bcbacc06b937b38dfb56d92593449158dc514 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 23 Jul 2026 14:03:43 +0000 Subject: [PATCH 24/27] swift-syntax-rs: Degrade gracefully without a Swift toolchain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `swift-syntax-rs` is a workspace member, so its build script runs on a plain `cargo check`/`fmt`/`clippy` at the repo root. Previously it panicked when `swift build` could not be run, breaking those Swift-free workflows for anyone without a Swift toolchain. Instead, when `swift build` cannot be spawned, emit a `cargo:warning` and skip the link directives rather than panicking. `cargo check`/`fmt`/`clippy` don't link, so they keep working; only `cargo build`/`cargo test` then fail, at link time — which is fair, since those genuinely need Swift (and CI builds go through Bazel). A Swift toolchain that is present but whose build fails is still surfaced as a hard error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/swift-syntax-rs/build.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/unified/swift-syntax-rs/build.rs b/unified/swift-syntax-rs/build.rs index c599cdf4e70b..6a58ddb4d8da 100644 --- a/unified/swift-syntax-rs/build.rs +++ b/unified/swift-syntax-rs/build.rs @@ -24,20 +24,34 @@ fn main() { println!("cargo:rerun-if-env-changed=SWIFTC"); // Build the Swift FFI package as a release dynamic library. + // + // Degrade gracefully when there is no runnable Swift toolchain. This crate + // is a workspace member, so a plain `cargo check`/`fmt`/`clippy` at the repo + // root runs this build script; if `swift build` cannot even be spawned we + // emit a warning and skip the link directives rather than panicking, so + // those Swift-free workflows keep working. Only `cargo build`/`cargo test` + // then fail — at link time, which is fair: they genuinely need Swift (and CI + // builds go through Bazel anyway). A Swift toolchain that *is* present but + // whose build fails is still surfaced as a hard error below. let mut command = Command::new(swift_bin()); command .args(["build", "-c", "release"]) .current_dir(&swift_dir); apply_bare_repository_workaround(&mut command); - let status = command.status().unwrap_or_else(|e| { - panic!( - "failed to run `{swift} build`: {e}\n\ - Install a Swift toolchain (see https://www.swift.org/install/, e.g. via \ - swiftly) and ensure `swift` is on PATH, or set the `SWIFT` environment \ - variable to the `swift` executable. The pinned version is in `.swift-version`.", - swift = swift_bin(), - ) - }); + let status = match command.status() { + Ok(status) => status, + Err(e) => { + println!( + "cargo:warning=skipping the Swift FFI build: failed to run `{swift} build`: {e}. \ + Install a Swift toolchain (see https://www.swift.org/install/, e.g. via swiftly) \ + and ensure `swift` is on PATH, or set the `SWIFT` environment variable, to build \ + or test this crate. `cargo check`/`fmt`/`clippy` work without it. The pinned \ + version is in `.swift-version`.", + swift = swift_bin(), + ); + return; + } + }; assert!(status.success(), "`swift build` failed"); // Link against the freshly built dynamic library. From 17cbb4eb6b66115eefadd7134b2ca64b31d3d3c2 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 24 Jul 2026 13:18:13 +0000 Subject: [PATCH 25/27] unified: Harden the external Swift parser integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes prompted by review of the swift-syntax switch-over. Parser resolution (`parse.rs`): `parse_bin` now resolves the `swift-syntax-parse` executable in priority order — the `CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE` override, then a copy next to the extractor executable (as a shipped extractor pack lays it out: `tools//{extractor,swift-syntax-parse}`), then a bare `PATH` lookup. This lets a packaged extractor find its parser with no environment setup. (Bundling the binary into the pack, together with its Swift runtime, is a separate follow-up.) Corpus test guard (`corpus_tests.rs`): `parser_available` previously treated *any* parser error as "unavailable" and skipped the entire corpus suite, so a parser that was present but crashed or emitted invalid JSON would silently skip the exact regressions the suite exists to catch. It now uses the new `binary_available`, which reports whether the *executable* can be launched (false only when it cannot be found, e.g. no Swift toolchain); a launchable-but-failing parser makes the suite run and fail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/parse.rs | 54 +++++++++++++++++-- unified/extractor/tests/corpus_tests.rs | 16 ++++-- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/unified/extractor/src/languages/swift/parse.rs b/unified/extractor/src/languages/swift/parse.rs index 21ef1b9ad215..c8633f1a27a5 100644 --- a/unified/extractor/src/languages/swift/parse.rs +++ b/unified/extractor/src/languages/swift/parse.rs @@ -16,9 +16,12 @@ use codeql_extractor::extractor::ParsedTree; use super::swift_adapter; /// Environment variable naming the `swift-syntax-parse` executable. When unset, -/// `swift-syntax-parse` is looked up on `PATH`. +/// the parser is resolved next to the extractor executable, then on `PATH`. const PARSE_BIN_ENV: &str = "CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE"; +/// Base name of the `swift-syntax-parse` executable as shipped / looked up. +const PARSE_BIN_NAME: &str = "swift-syntax-parse"; + /// Parse Swift `source` into a [`ParsedTree`] (a raw `yeast::Ast` plus /// side-channel `extra` tokens), ready to be desugared via `run_from_ast`. pub fn parse(source: &[u8]) -> Result { @@ -33,9 +36,54 @@ pub fn parse(source: &[u8]) -> Result { }) } -/// The `swift-syntax-parse` executable to invoke. +/// The `swift-syntax-parse` executable to invoke, resolved in priority order: +/// +/// 1. the `CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE` override, if set; +/// 2. a copy shipped next to the extractor executable — this is how the CodeQL +/// extractor pack lays it out (`tools//{extractor, +/// swift-syntax-parse}`), so a packaged extractor is self-contained with no +/// environment setup; +/// 3. a bare `swift-syntax-parse`, looked up on `PATH`. fn parse_bin() -> String { - std::env::var(PARSE_BIN_ENV).unwrap_or_else(|_| "swift-syntax-parse".to_string()) + if let Ok(bin) = std::env::var(PARSE_BIN_ENV) { + if !bin.is_empty() { + return bin; + } + } + if let Ok(exe) = std::env::current_exe() { + if let Some(sibling) = exe.parent().map(|dir| dir.join(PARSE_BIN_NAME)) { + if sibling.is_file() { + return sibling.to_string_lossy().into_owned(); + } + } + } + PARSE_BIN_NAME.to_string() +} + +/// Whether the `swift-syntax-parse` executable can be launched at all. +/// +/// This reports availability of the *executable*, deliberately not whether +/// parsing succeeds: a binary that launches but then crashes or emits invalid +/// JSON is still "available", so callers run and surface the failure rather +/// than silently skipping. Only a genuinely missing/unlaunchable binary (e.g. +/// no Swift toolchain is installed) reports `false`. +pub fn binary_available() -> bool { + match Command::new(parse_bin()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + { + Ok(mut child) => { + let _ = child.wait(); + true + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => false, + // Any other spawn failure (e.g. a permissions problem) is a genuine + // issue worth surfacing, so treat the parser as available and let the + // caller fail rather than masking it as "unavailable". + Err(_) => true, + } } /// Run the external parser, feeding `source` on stdin and returning its JSON diff --git a/unified/extractor/tests/corpus_tests.rs b/unified/extractor/tests/corpus_tests.rs index 66374e56526e..b2700b999b7c 100644 --- a/unified/extractor/tests/corpus_tests.rs +++ b/unified/extractor/tests/corpus_tests.rs @@ -20,12 +20,18 @@ fn update_mode_enabled() -> bool { .unwrap_or(false) } -/// Whether the external swift-syntax parser is available. When it is not (e.g. -/// no Swift toolchain / the `swift-syntax-parse` binary is not on `PATH` and -/// `CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE` is unset), the corpus test is -/// skipped rather than failed — it cannot run without the Swift-backed parser. +/// Whether the external swift-syntax parser is available. When the parser +/// binary genuinely cannot be found/launched (e.g. no Swift toolchain, and +/// neither `CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE` nor a `swift-syntax-parse` +/// on `PATH`), the corpus test is skipped rather than failed — it cannot run +/// without the Swift-backed parser. +/// +/// Crucially this checks only that the executable *launches*: a parser that is +/// present but crashes, emits invalid JSON, or otherwise regresses is +/// considered available, so the suite runs and fails (rather than silently +/// skipping the very failures CI needs to catch). fn parser_available() -> bool { - languages::swift_parse::parse(b"").is_ok() + languages::swift_parse::binary_available() } /// Parse a corpus `.output` file. The file holds a single test case made of From 7721ce2eba9612d50feb987e1f95729c3cc5fa18 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 24 Jul 2026 15:47:34 +0000 Subject: [PATCH 26/27] unified: Add swift_node_types.yml to the extractor's compile_data `languages::swift::adapter` embeds the swift-syntax node-types schema with `include_str!("../../../swift_node_types.yml")`, but the file was never listed in the extractor's Bazel `compile_data`. The `cargo` build finds it on disk, so this went unnoticed, but the sandboxed Bazel build cannot see it and fails to compile the extractor. List it alongside `ast_types.yml`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/extractor/BUILD.bazel | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/unified/extractor/BUILD.bazel b/unified/extractor/BUILD.bazel index 0ef7f1b68b0e..13a3b6b287bc 100644 --- a/unified/extractor/BUILD.bazel +++ b/unified/extractor/BUILD.bazel @@ -7,7 +7,10 @@ codeql_rust_binary( name = "extractor", srcs = glob(["src/**/*.rs"]), aliases = aliases(), - compile_data = ["ast_types.yml"], + compile_data = [ + "ast_types.yml", + "swift_node_types.yml", + ], proc_macro_deps = all_crate_deps( proc_macro = True, ), From e3a0822248e31f675bcc962a22e63c837c351e70 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 24 Jul 2026 15:48:00 +0000 Subject: [PATCH 27/27] unified: Package the swift-syntax parser in the extractor pack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extractor shells out to a separate `swift-syntax-parse` binary, but nothing placed it in the extractor pack, so a shipped Swift extraction failed at the first spawn. Package it next to the extractor, the same way `//swift/extractor` ships its Swift-linked binary: a small wrapper points the dynamic loader at its own directory and execs the real binary, whose Swift runtime libraries travel alongside it. - `swift-syntax-parse.sh`: wrapper that sets `LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` to its directory and execs `swift-syntax-parse.real` (mirrors `swift/extractor/extractor.sh`). - `runtime.bzl`: a `swift_runtime_libs` rule that selects just the Linux Swift runtime shared objects (`usr/lib/swift/linux/*.so`) out of the full toolchain, so only they — not the whole toolchain — travel with the binary. - `swift-syntax-rs/BUILD.bazel`: the `rust_binary` becomes `swift-syntax-parse.real` and carries the runtime libraries as runfiles on Linux; a `sh_binary` (`swift-syntax-parse`) is the wrapper; `codeql_pkg_runfiles` flattens the three (wrapper, real binary, runtime) into one directory. - `BUILD.bazel`: ship that group under `tools/{CODEQL_PLATFORM}` next to the extractor, on the platforms where swift-syntax builds (Linux/macOS). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/BUILD.bazel | 15 +++++++ unified/swift-syntax-rs/BUILD.bazel | 40 ++++++++++++++++--- unified/swift-syntax-rs/swift-syntax-parse.sh | 12 ++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100755 unified/swift-syntax-rs/swift-syntax-parse.sh diff --git a/unified/BUILD.bazel b/unified/BUILD.bazel index e702fd251594..a54080b0542d 100644 --- a/unified/BUILD.bazel +++ b/unified/BUILD.bazel @@ -1,5 +1,6 @@ load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup") load("//misc/bazel:pkg.bzl", "codeql_pack", "codeql_pkg_files") +load("//misc/bazel:utils.bzl", "select_os") package(default_visibility = ["//visibility:public"]) @@ -46,12 +47,26 @@ codeql_pkg_files( prefix = "tools/{CODEQL_PLATFORM}", ) +# The Swift front-end parser (wrapper + real binary + bundled Swift runtime), +# shipped next to the extractor. Only on platforms where swift-syntax builds +# (Linux/macOS); elsewhere the group is empty so the pack still builds (Swift +# extraction is simply unavailable there). +pkg_filegroup( + name = "swift-syntax-parse-arch", + srcs = select_os( + posix = ["//unified/swift-syntax-rs:swift-syntax-parse-pkg"], + otherwise = [], + ), + prefix = "tools/{CODEQL_PLATFORM}", +) + codeql_pack( name = "unified", srcs = [ ":codeql-extractor-yml", ":dbscheme-group", ":extractor-arch", + ":swift-syntax-parse-arch", "//unified/tools", ], ) diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 11484736ca8f..651cb09531e3 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -1,4 +1,6 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") +load("@rules_shell//shell:sh_binary.bzl", "sh_binary") +load("//misc/bazel:pkg.bzl", "codeql_pkg_runfiles") load(":swift_runtime.bzl", "swift_runtime_libs") load(":xcode_transition.bzl", "xcode_transition_swift_library") @@ -48,13 +50,19 @@ rust_library( ], ) +# The Swift front-end parser. We ship it like `//swift/extractor`: a small shell +# wrapper (`swift-syntax-parse`) sets `LD_LIBRARY_PATH`/`DYLD_LIBRARY_PATH` to its +# own directory and execs the real binary (`swift-syntax-parse.real`); the Swift +# runtime shared libraries are packaged alongside them. `parse.rs` resolves the +# wrapper as a sibling of the extractor executable. rust_binary( - name = "swift-syntax-parse", + name = "swift-syntax-parse.real", srcs = ["src/main.rs"], - # `rust_binary` doesn't copy the Swift runtime into runfiles the way - # `swift_binary` does. On Linux, ship the standalone toolchain's runtime; - # on macOS the OS provides it at `/usr/lib/swift` (rpath'd by - # `xcode_swift_toolchain`). + # Target name carries `.real` (invalid in a crate name), so set it explicitly. + crate_name = "swift_syntax_parse", + # On Linux, carry the toolchain's runtime shared libraries as runfiles so + # they get packaged next to the binary. On macOS the OS provides the Swift + # runtime, so nothing extra is bundled. data = select({ "@platforms//os:macos": [], "@platforms//os:linux": [":swift_runtime_libs"], @@ -64,6 +72,28 @@ rust_binary( deps = [":swift_syntax_rs"], ) +# `swift-syntax-parse` wrapper (see `swift-syntax-parse.sh`). Its runfiles carry +# the real binary and the runtime libraries; packaging flattens them into one +# directory. +sh_binary( + name = "swift-syntax-parse", + srcs = ["swift-syntax-parse.sh"], + data = [":swift-syntax-parse.real"], + target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, +) + +# Packaged form for the extractor pack: the wrapper (as `swift-syntax-parse`), +# the real binary, and the runtime libraries, flattened into one directory. +codeql_pkg_runfiles( + name = "swift-syntax-parse-pkg", + exes = [":swift-syntax-parse"], + # The `.sh` source is shipped as `swift-syntax-parse` (the wrapper); drop the + # original filename. + excludes = ["swift-syntax-parse.sh"], + target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, + visibility = ["//unified:__pkg__"], +) + rust_test( name = "swift_syntax_rs_test", size = "small", diff --git a/unified/swift-syntax-rs/swift-syntax-parse.sh b/unified/swift-syntax-rs/swift-syntax-parse.sh new file mode 100755 index 000000000000..811697c820c6 --- /dev/null +++ b/unified/swift-syntax-rs/swift-syntax-parse.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Wrapper that lets the shipped `swift-syntax-parse` find its Swift runtime +# libraries, which are packaged in the same directory as this script (and the +# real binary). Mirrors `swift/extractor/extractor.sh`. +if [[ "$(uname)" == Darwin ]]; then + export DYLD_LIBRARY_PATH=$(dirname "$0") +else + export LD_LIBRARY_PATH=$(dirname "$0") +fi + +exec -a "$0" "$0.real" "$@"