Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion unified/extractor/ast_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -529,15 +529,23 @@ named:
# downstream. Stored properties with observers are emitted as a
# variable_declaration followed by one accessor_declaration per observer
# (each observer also tagged with `chained_declaration`).
#
# An accessor of kind `setter_method` is a setter invoked via call syntax,
# such as the setter of a Swift subscript. A call `x.foo(...)` that is the
# target of an assignment, or is otherwise updated in place, invokes the
# `setter_method` accessor named `foo` (whereas a call in a read position
# invokes the ordinary method `foo`). The setter takes the new value as an
# extra trailing positional parameter.
accessor_declaration:
modifier*: modifier
name_node: identifier
accessor_kind: accessor_kind
type_parameter*: type_parameter
parameter*: parameter
return_type?: expr
body?: block

# "get", "set", or a language-specific kind like "didSet"
# "get", "set", "setter_method", or a language-specific kind like "didSet"
accessor_kind:

# Static or instance initializer block. That is, code that runs at initialization time of either the class or an instance.
Expand Down
220 changes: 210 additions & 10 deletions unified/extractor/src/languages/swift/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ struct SwiftContext {
/// True while translating a pattern. Optional chaining in this context is
/// represented as an `Optional.some` call rather than being unwrapped.
in_pattern: bool,
/// The enclosing `subscriptDecl`, set while translating its accessor
/// block so the inner `accessorDecl` rule emits `subscript` methods
/// rather than property accessors.
subscript: Option<SubscriptContext>,
/// True while translating the parameters of a subscript. Unlike function
/// parameters, a subscript parameter with a single name has no argument
/// label.
in_subscript_parameters: bool,
}

/// The parts of a `subscriptDecl` shared by its getter and setter. These are
/// kept untranslated so that each emitted method gets its own copy.
#[derive(Clone)]
struct SubscriptContext {
keyword: yeast::Id,
modifiers: Vec<yeast::Id>,
type_params: Vec<yeast::Id>,
params: Vec<yeast::Id>,
return_type: yeast::Id,
}

impl SwiftContext {
Expand Down Expand Up @@ -141,6 +160,77 @@ fn member_chain(
)
}

/// The accessor-specific parts of a subscript getter or setter.
struct SubscriptAccessor {
/// Modifiers written on the accessor itself, such as `nonmutating`.
modifiers: Vec<yeast::OutputId>,
/// For a setter, the explicit name of its new-value parameter (`set(v)`),
/// if any, and the token at which to locate an implicit `newValue`.
setter: Option<(Option<yeast::Id>, yeast::Id)>,
/// The raw accessor body, if any.
body: Option<yeast::Id>,
}

/// Emit the declaration for one accessor of a subscript: the getter is a
/// `function_declaration` named `subscript`, and the setter an
/// `accessor_declaration` of kind `setter_method`. The shared parts of the subscript (modifiers, generic and value
/// parameters and the element type) are translated afresh for each accessor,
/// so a getter/setter pair each get their own copy. A setter takes the new
/// value as an extra trailing positional parameter and has no return type.
fn subscript_method(
ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>,
sub: SubscriptContext,
accessor: SubscriptAccessor,
) -> yeast::OutputId {
let is_chained = ctx.is_chained;
let (mods, type_params, params, element_type, body) = ctx.scoped(|ctx| {
ctx.reset();
let mods = ctx.translate(sub.modifiers);
let type_params = ctx.translate(sub.type_params);
let element_type = ctx.translate(sub.return_type).into_iter().next();
let body = ctx.translate(accessor.body).into_iter().next();
ctx.in_subscript_parameters = true;
let params = ctx.translate(sub.params);
(mods, type_params, params, element_type, body)
});
let body = body.unwrap_or_else(|| tree!((block)));
let kw = sub.keyword;
match accessor.setter {
Some((param_name, spec)) => {
let implicit_param_name = param_name.is_none().then_some("newValue");
tree!((accessor_declaration
modifier: {mods}
modifier: {accessor.modifiers}
modifier: [(modifier "chained_declaration") if { is_chained }]
name_node: (identifier #{kw})
accessor_kind: (accessor_kind "setter_method" location: {spec})
type_parameter: {type_params}
parameter: {params}
parameter: [
(parameter
pattern: (identifier #{param_name})
type: {element_type}
location: {param_name})
(parameter
modifier: (modifier "generated")
pattern: (identifier #{implicit_param_name} location: {spec})
type: {element_type}
location: {spec})
]
body: {body}))
}
None => tree!((function_declaration
modifier: {mods}
modifier: {accessor.modifiers}
modifier: [(modifier "chained_declaration") if { is_chained }]
name_node: (identifier #{kw})
type_parameter: {type_params}
parameter: {params}
return_type: {element_type}
body: {body})),
}
}

fn translation_rules() -> Vec<Rule<SwiftContext>> {
yeast::rules! {
input: "swift_node_types.yml",
Expand Down Expand Up @@ -402,6 +492,89 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
result
}
),
// ---- Subscripts ----
// A subscript declaration becomes a `function_declaration` named
// `subscript` for its getter and, if it has a setter, an
// `accessor_declaration` of kind `setter_method` named `subscript`.
// A subscript with an explicit accessor block publishes its shared parts
// in `ctx` and lets the `accessorDecl` rule below emit one method per
// accessor; each one after the first is tagged `chained_declaration`.
rule!(
(subscriptDecl
modifiers: _* @@mods
subscriptKeyword: @@kw
genericParameterClause: (genericParameterClause parameters: _* @@type_params)?
parameterClause: (functionParameterClause parameters: _* @@params)
returnClause: (returnClause type: @@ret)
accessorBlock: (accessorBlock accessors: (accessorDecl)+ @@accessors))
=>
member* {
ctx.subscript = Some(SubscriptContext {
keyword: kw,
modifiers: mods,
type_params,
params,
return_type: ret,
});
let mut result = Vec::new();
for acc in accessors.into_iter() {
ctx.is_chained = !result.is_empty();
result.extend(ctx.translate(acc));
}
result
}
),
// A subscript with a getter-only body (`subscript(i: Int) -> T { … }`).
rule!(
(subscriptDecl
modifiers: _* @@mods
subscriptKeyword: @@kw
genericParameterClause: (genericParameterClause parameters: _* @@type_params)?
parameterClause: (functionParameterClause parameters: _* @@params)
returnClause: (returnClause type: @@ret)
accessorBlock: _? @@body)
=>
function_declaration {
let sub = SubscriptContext {
keyword: kw,
modifiers: mods,
type_params,
params,
return_type: ret,
};
ctx.is_chained = false;
subscript_method(&mut ctx, sub, SubscriptAccessor {
modifiers: Vec::new(),
setter: None,
body,
})
}
),
// An accessor of a subscript (see above). Accessor kinds other than
// `get` and `set` (such as `_modify`) are not supported.
rule!(
(accessorDecl
modifiers: _* @acc_mods
accessorSpecifier: @@spec
parameters: (accessorParameters name: @@param_name)?
body: _? @@body)
where ctx.subscript.is_some()
=>
member {
let sub = ctx.subscript.clone().expect("checked by guard");
let spec_text = ctx.source_text(spec);
if spec_text == "get" || spec_text == "set" {
let setter = (spec_text == "set").then_some((param_name, spec));
subscript_method(&mut ctx, sub, SubscriptAccessor {
modifiers: acc_mods,
setter,
body,
})
} else {
tree!((unsupported_node ""))
}
}
),
// Each `accessorDecl` becomes an `accessor_declaration`, reading the
// property name/type and the modifiers from `ctx`. The
// accessor kind comes straight from the specifier keyword
Expand Down Expand Up @@ -668,6 +841,27 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
return_type: {ret}
body: (block))
),
// A subscript parameter with a single name has no argument label
// (`subscript(i: Int)` is called as `x[0]`). It is translated in a reset
// context so its type and default value are not affected.
rule!(
(functionParameter
firstName: @@first
secondName: _? @@second
type: @@ty
defaultValue: (initializerClause value: @@val)?)
where ctx.in_subscript_parameters && second.is_none()
=>
parameter {
ctx.reset();
let ty = ctx.translate(ty).into_iter().next();
let val = ctx.translate(val).into_iter().next();
tree!((parameter
pattern: {parameter_pattern(&mut ctx, first)}
type: {ty}
default: {val}))
}
),
// 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 both the external and internal name.
Expand Down Expand Up @@ -1054,22 +1248,29 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(key_value_pair key: {key} value: {value})
),
// A subscript access (`xs[0]`) is modelled as a call. swift-syntax does
// report a distinct `subscriptCallExpr`, so giving
// subscripts their own shape needs only a `subscript_expr` node in
// ast_types.yml and a remap here.
// A subscript access (`xs[0]`) is modelled as a call to a member named
// `subscript` on the subscripted value, i.e. `xs.subscript(0)`. This
// matches the `subscript` methods emitted for `subscriptDecl`s.
rule!(
(subscriptCallExpr
calledExpression: @callee
leftSquare: @@left_square
arguments: _* @args
trailingClosure: _? @trailing
additionalTrailingClosures: _* @additional)
=>
(call_expr
callee: {callee}
argument: {args}
argument: {trailing.map(|trailing| tree!((argument value: {trailing})))}
argument: {additional})
call_expr {
let member = tree!((member_access_expr
base: {callee}
member_name_node: (identifier "subscript" location: {left_square})
location: {callee}
location: {left_square}));
tree!((call_expr
callee: {member}
argument: {args}
argument: {trailing.map(|trailing| tree!((argument value: {trailing})))}
argument: {additional}))
}
),
// ---- Optionals and errors ----
// Optional chaining — unwrap the marker
Expand Down Expand Up @@ -1569,7 +1770,6 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
rule!((poundSourceLocation) => (unsupported_node "")),
rule!((precedenceGroupDecl) => (unsupported_node "")),
rule!((someOrAnyType) => (unsupported_node "")),
rule!((subscriptDecl) => (unsupported_node "")),
rule!((suppressedType) => (unsupported_node "")),
rule!((typeExpr) => (unsupported_node "")),
rule!((unsafeExpr) => (unsupported_node "")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ top_level source="⟨body⟩"
modifier: modifier "let" source="let"
pattern: identifier "v" source="v"
value:
call_expr source="⟨callee⟩[⟨argument⟩]"
callee: identifier "d" source="d"
call_expr source="⟨callee⟩⟨argument⟩]"
callee:
member_access_expr source="⟨base⟩⟨member_name_node⟩"
base: identifier "d" source="d"
member_name_node: identifier "subscript" source="["
argument:
argument source="⟨value⟩"
value: string_literal "\"key\"" source="\"key\""
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ top_level source="⟨body⟩"
modifier: modifier "let" source="let"
pattern: identifier "first" source="first"
value:
call_expr source="⟨callee⟩[⟨argument⟩]"
callee: identifier "xs" source="xs"
call_expr source="⟨callee⟩⟨argument⟩]"
callee:
member_access_expr source="⟨base⟩⟨member_name_node⟩"
base: identifier "xs" source="xs"
member_name_node: identifier "subscript" source="["
argument:
argument source="⟨value⟩"
value: int_literal "0" source="0"
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,19 @@ top_level source="⟨body⟩"
body:
block source="⟨stmt⟩\n\n⟨stmt⟩"
stmt:
call_expr source="⟨callee⟩[⟨argument⟩]"
callee: identifier "handlers" source="handlers"
call_expr source="⟨callee⟩⟨argument⟩]"
callee:
member_access_expr source="⟨base⟩⟨member_name_node⟩"
base: identifier "handlers" source="handlers"
member_name_node: identifier "subscript" source="["
argument:
argument source="⟨value⟩"
value: int_literal "0" source="0"
call_expr source="⟨callee⟩[⟨argument⟩] ⟨argument⟩ ⟨argument⟩ ⟨argument⟩"
callee: identifier "handlers" source="handlers"
call_expr source="⟨callee⟩⟨argument⟩] ⟨argument⟩ ⟨argument⟩ ⟨argument⟩"
callee:
member_access_expr source="⟨base⟩⟨member_name_node⟩"
base: identifier "handlers" source="handlers"
member_name_node: identifier "subscript" source="["
argument:
argument source="⟨value⟩"
value: int_literal "0" source="0"
Expand Down
Loading