Skip to content

Commit 6d138c2

Browse files
committed
yeast: Simplify Swift rules using the new machinery
Propagates in name and type information for various property declarations, using the context mechanism. This avoids mutating already-translated nodes in-place, and is generally much easier to read.
1 parent 85c39c0 commit 6d138c2

1 file changed

Lines changed: 90 additions & 40 deletions

File tree

  • unified/extractor/src/languages/swift

unified/extractor/src/languages/swift/swift.rs

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
use codeql_extractor::extractor::simple;
2-
use yeast::{rule, tree, ConcreteDesugarer, DesugaringConfig, PhaseKind};
2+
use yeast::{manual_rule, rule, tree, ConcreteDesugarer, DesugaringConfig, PhaseKind, Rule};
33

4-
fn translation_rules() -> Vec<yeast::Rule> {
4+
/// User context propagated from outer `property_binding` rules down to the
5+
/// inner accessor-translation rules so that every `accessor_declaration`
6+
/// emitted by an inner rule is born with the property's `name` (and
7+
/// optionally its `type`) already set — no schema-invalid intermediate
8+
/// state requiring post-hoc mutation.
9+
#[derive(Clone, Default)]
10+
struct PropertyContext {
11+
/// Identifier node for the property name, to be used as the
12+
/// `accessor_declaration.name`. Set by the outer property_binding rule
13+
/// before translating accessor children.
14+
property_name: Option<yeast::Id>,
15+
/// Translated type node for the property type, to be used as the
16+
/// `accessor_declaration.type`. Set by the outer property_binding rule
17+
/// when present.
18+
property_type: Option<yeast::Id>,
19+
}
20+
21+
fn translation_rules() -> Vec<Rule<PropertyContext>> {
522
vec![
623
// ---- Top-level ----
724
// Capture all top-level statements, including unnamed tokens like `nil`.
@@ -88,27 +105,35 @@ fn translation_rules() -> Vec<yeast::Rule> {
88105
// nodes for individual declarators. The outer property_declaration rule splices these out
89106
// and attaches binding/modifiers from the parent.
90107

91-
// Computed property with explicit accessors (get/set/modify) →
92-
// a sequence of accessor_declaration nodes, each with the property name
93-
// attached. Subsequent accessors will be tagged chained_declaration by
94-
// the outer property_declaration rule.
95-
rule!(
108+
// Computed property with explicit accessors (get/set/modify) → a
109+
// sequence of `accessor_declaration` nodes. The outer rule
110+
// publishes the property's name and type into `ctx` so that each
111+
// inner accessor rule
112+
// (`computed_getter`/`computed_setter`/`computed_modify`) builds
113+
// its `accessor_declaration` with `name` and `type` set from the
114+
// start — no schema-invalid intermediate state.
115+
manual_rule!(
96116
(property_binding
97117
name: @pattern
98118
type: _? @ty
99119
computed_value: (computed_property accessor: _+ @accessors))
100-
=>
101-
{..{
102-
for &acc in &accessors {
103-
let acc_id: usize = acc.into();
104-
for &t in ty.iter().rev() {
105-
ctx.prepend_field(acc_id, "type", t.into());
106-
}
107-
let name_id = tree!((identifier #{pattern}));
108-
ctx.prepend_field(acc_id, "name", name_id);
120+
{
121+
// Translate `ty` first so the context holds an
122+
// output-schema node id.
123+
let translated_ty = ctx.translate_opt(ty)?;
124+
// Build the property-name identifier from the
125+
// (untranslated) pattern leaf.
126+
let name_id = tree!((identifier #{pattern}));
127+
128+
ctx.property_name = Some(name_id);
129+
ctx.property_type = translated_ty;
130+
131+
let mut result = Vec::new();
132+
for acc in &accessors {
133+
result.extend(ctx.translate(*acc)?);
109134
}
110-
accessors
111-
}}
135+
Ok(result)
136+
}
112137
),
113138
// Computed property: shorthand getter (no explicit get/set, just statements) →
114139
// a single accessor_declaration with kind "get".
@@ -124,31 +149,41 @@ fn translation_rules() -> Vec<yeast::Rule> {
124149
accessor_kind: (accessor_kind "get")
125150
body: (block stmt: {..body}))
126151
),
127-
// Stored property with willSet/didSet observers (initializer optional) →
128-
// variable_declaration followed by one accessor_declaration per observer,
129-
// each carrying the property name. Subsequent items are tagged
130-
// chained_declaration by the outer property_declaration rule.
131-
rule!(
152+
// Stored property with willSet/didSet observers (initializer
153+
// optional) → a `variable_declaration` followed by one
154+
// `accessor_declaration` per observer, each born with the
155+
// property name set. Manual rule: we publish the property name
156+
// into `ctx` before translating the observer children so the
157+
// inner `willset_clause` / `didset_clause` rules construct
158+
// valid `accessor_declaration` nodes from the start.
159+
manual_rule!(
132160
(property_binding
133161
name: (pattern bound_identifier: @name)
134162
type: _? @ty
135163
value: _? @val
136164
observers: (willset_didset_block willset: _? @ws didset: _? @ds))
137-
=>
138-
(variable_declaration
139-
pattern: (name_pattern identifier: (identifier #{name}))
140-
type: {..ty}
141-
value: {..val})
142-
{..{
143-
let mut obs_ids = Vec::new();
144-
for &obs in ws.iter().chain(ds.iter()) {
145-
let obs_id: usize = obs.into();
146-
let ident = tree!((identifier #{name}));
147-
ctx.prepend_field(obs_id, "name", ident);
148-
obs_ids.push(obs_id);
165+
{
166+
// Translate ty and val so the variable_declaration
167+
// below contains output-schema nodes.
168+
let translated_ty = ctx.translate_opt(ty)?;
169+
let translated_val = ctx.translate_opt(val)?;
170+
171+
let var_decl = tree!(
172+
(variable_declaration
173+
pattern: (name_pattern identifier: (identifier #{name}))
174+
type: {..translated_ty}
175+
value: {..translated_val})
176+
);
177+
178+
// Publish the property name for the observer rules.
179+
ctx.property_name = Some(tree!((identifier #{name})));
180+
181+
let mut result = vec![var_decl];
182+
for obs in ws.into_iter().chain(ds) {
183+
result.extend(ctx.translate(obs)?);
149184
}
150-
obs_ids
151-
}}
185+
Ok(result)
186+
}
152187
),
153188
// property_binding with any pattern name (identifier or destructuring)
154189
rule!(
@@ -899,10 +934,14 @@ fn translation_rules() -> Vec<yeast::Rule> {
899934
// protocol_property_requirements wrapper — should be consumed by above; fallback
900935
rule!((protocol_property_requirements accessor: _* @accs) => {..accs}),
901936
// Computed getter → accessor_declaration (body optional).
937+
// Reads `ctx.property_name`/`ctx.property_type` set by the outer
938+
// property_binding manual rule.
902939
rule!(
903940
(computed_getter body: (block statement: _* @body)?)
904941
=>
905942
(accessor_declaration
943+
name: {ctx.property_name.ok_or("computed_getter outside property_binding context")?}
944+
type: {..ctx.property_type}
906945
accessor_kind: (accessor_kind "get")
907946
body: (block stmt: {..body}))
908947
),
@@ -911,6 +950,8 @@ fn translation_rules() -> Vec<yeast::Rule> {
911950
(computed_setter parameter: @param body: (block statement: _* @body))
912951
=>
913952
(accessor_declaration
953+
name: {ctx.property_name.ok_or("computed_setter outside property_binding context")?}
954+
type: {..ctx.property_type}
914955
accessor_kind: (accessor_kind "set")
915956
parameter: (parameter pattern: (name_pattern identifier: (identifier #{param})))
916957
body: (block stmt: {..body}))
@@ -920,6 +961,8 @@ fn translation_rules() -> Vec<yeast::Rule> {
920961
(computed_setter body: (block statement: _* @body)?)
921962
=>
922963
(accessor_declaration
964+
name: {ctx.property_name.ok_or("computed_setter outside property_binding context")?}
965+
type: {..ctx.property_type}
923966
accessor_kind: (accessor_kind "set")
924967
body: (block stmt: {..body}))
925968
),
@@ -928,16 +971,22 @@ fn translation_rules() -> Vec<yeast::Rule> {
928971
(computed_modify body: (block statement: _* @body))
929972
=>
930973
(accessor_declaration
974+
name: {ctx.property_name.ok_or("computed_modify outside property_binding context")?}
975+
type: {..ctx.property_type}
931976
accessor_kind: (accessor_kind "modify")
932977
body: (block stmt: {..body}))
933978
),
934-
// willset/didset block — spread to children
979+
// willset/didset block — spread to children (only reachable as a
980+
// fallback; the outer property_binding manual rule normally
981+
// captures the willset/didset clauses directly).
935982
rule!((willset_didset_block _* @clauses) => {..clauses}),
936-
// willset clause → accessor_declaration (body optional).
983+
// willset clause → accessor_declaration (body optional). Reads
984+
// `ctx.property_name` set by the outer property_binding rule.
937985
rule!(
938986
(willset_clause body: (block statement: _* @body)?)
939987
=>
940988
(accessor_declaration
989+
name: {ctx.property_name.ok_or("willset_clause outside property_binding context")?}
941990
accessor_kind: (accessor_kind "willSet")
942991
body: (block stmt: {..body}))
943992
),
@@ -946,6 +995,7 @@ fn translation_rules() -> Vec<yeast::Rule> {
946995
(didset_clause body: (block statement: _* @body)?)
947996
=>
948997
(accessor_declaration
998+
name: {ctx.property_name.ok_or("didset_clause outside property_binding context")?}
949999
accessor_kind: (accessor_kind "didSet")
9501000
body: (block stmt: {..body}))
9511001
),
@@ -967,7 +1017,7 @@ fn translation_rules() -> Vec<yeast::Rule> {
9671017

9681018
pub fn language_spec(desugared_ast_schema: &'static str) -> simple::LanguageSpec {
9691019
let ts_language: tree_sitter::Language = tree_sitter_swift::LANGUAGE.into();
970-
let config = DesugaringConfig::new()
1020+
let config = DesugaringConfig::<PropertyContext>::new()
9711021
.add_phase("translate", PhaseKind::OneShot, translation_rules())
9721022
.with_output_node_types_yaml(desugared_ast_schema);
9731023
let desugarer = ConcreteDesugarer::new(ts_language.clone(), config)

0 commit comments

Comments
 (0)