diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 5eb74cf99ce..b2264dd590f 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -9,19 +9,44 @@ use yeast::{manual_rule, rule, tree, ConcreteDesugarer, DesugaringConfig, PhaseK #[derive(Clone, Default)] struct SwiftContext { /// Identifier node for the property name. Set by the outer - /// `property_binding` (computed accessors / willSet-didSet) rule - /// before translating accessor children; read by - /// `computed_getter`/`computed_setter`/`computed_modify`/ - /// `willset_clause`/`didset_clause`. + /// `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`). property_name: Option, /// Translated type node for the property type. Set by the outer - /// `property_binding` rule (computed accessors variant) when - /// present; read by `computed_*` rules. + /// `property_binding` rule (computed accessors variant) and + /// `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 (e.g. visibility, attributes) to + /// attach to each child of a flattening outer rule. Set by + /// `protocol_property_declaration` (and, later, + /// `property_declaration`/`enum_entry`). + outer_modifiers: Vec, + /// True when the current child of a flattening outer rule is not + /// the first one — its inner rule should emit a + /// `chained_declaration` modifier so the original grouping can be + /// recovered downstream. + is_chained: bool, +} + +/// Build a freshly-created `chained_declaration` modifier node if +/// `ctx.is_chained`, else `None`. Used by inner declaration rules to +/// emit the chained tag for non-first children of a flattening outer +/// rule. Returns `Option` 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")) + } else { + None + } } fn translation_rules() -> Vec> { @@ -904,41 +929,61 @@ fn translation_rules() -> Vec> { name: (identifier #{name}) bound: {..bound}) ), - // Protocol property declaration: translate each accessor requirement to an - // accessor_declaration without a body, carrying the property name and type. - // Subsequent accessors get chained_declaration (same flattening as computed properties). - rule!( + // 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). + manual_rule!( (protocol_property_declaration - name: @pattern + name: (pattern bound_identifier: @name) requirements: (protocol_property_requirements accessor: _+ @accessors) type: _? @ty (modifiers)* @mods) - => - {..{ - let name_text = ctx.ast.source_text(pattern.into()); - let mod_ids: Vec = mods.iter().map(|&m| m.into()).collect(); - let ty_ids: Vec = ty.iter().map(|&t| t.into()).collect(); - let acc_ids: Vec = accessors.iter().map(|&a| a.into()).collect(); - for (i, &acc_id) in acc_ids.iter().enumerate() { - if i > 0 { - let chained = ctx.literal("modifier", "chained_declaration"); - ctx.prepend_field(acc_id, "modifier", chained); - } - for &mod_id in mod_ids.iter().rev() { - ctx.prepend_field(acc_id, "modifier", mod_id); - } - for &ty_id in ty_ids.iter().rev() { - ctx.prepend_field(acc_id, "type", ty_id); - } - let ident = ctx.literal("identifier", &name_text); - ctx.prepend_field(acc_id, "name", ident); + { + ctx.property_name = Some(tree!((identifier #{name}))); + ctx.property_type = ctx.translate_opt(ty)?; + let mut modifiers = Vec::new(); + for m in mods { + modifiers.extend(ctx.translate(m)?); } - acc_ids - }} + ctx.outer_modifiers = modifiers; + + let mut result = Vec::new(); + for (i, acc) in accessors.into_iter().enumerate() { + ctx.is_chained = i > 0; + result.extend(ctx.translate(acc)?); + } + Ok(result) + } ), // getter_specifier / setter_specifier → bodyless accessor_declaration - rule!((getter_specifier) => (accessor_declaration accessor_kind: (accessor_kind "get"))), - rule!((setter_specifier) => (accessor_declaration accessor_kind: (accessor_kind "set"))), + // 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) => {..accs}), // Computed getter → accessor_declaration (body optional). diff --git a/unified/extractor/tests/corpus/swift/types.txt b/unified/extractor/tests/corpus/swift/types.txt index ef15ad87f59..dc3eb9db305 100644 --- a/unified/extractor/tests/corpus/swift/types.txt +++ b/unified/extractor/tests/corpus/swift/types.txt @@ -924,3 +924,93 @@ top_level accessor_kind: accessor_kind "set" modifier: modifier "class" name: identifier "Box" + +=== +Protocol with read-only and read-write property requirements +=== + +protocol P { + var foo: Int { get } + var bar: String { get set } +} + +--- + +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" + +--- + +top_level + body: + block + stmt: + class_like_declaration + member: + accessor_declaration + name: identifier "foo" + type: + named_type_expr + name: identifier "Int" + accessor_kind: accessor_kind "get" + accessor_declaration + name: identifier "bar" + type: + named_type_expr + name: identifier "String" + accessor_kind: accessor_kind "get" + accessor_declaration + modifier: modifier "chained_declaration" + name: identifier "bar" + type: + named_type_expr + name: identifier "String" + accessor_kind: accessor_kind "set" + modifier: modifier "protocol" + name: identifier "P"