⚡ Sangsakala Proxy 🏠 Home
Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Unified: Extract Swift conditional compilation blocks
Extract conditional compilation blocks as if expressions.

Members can now be nested inside conditional compilation blocks.
Rename `member` to `direct_member` as this is now usually the wrong
thing to access. A new predicate on the QL side finds all members, both
direct and indirect ones through conditional compilation blocks.
  • Loading branch information
paldepind committed Nov 3, 2026
commit fe0d3be191e49319d23bea1136226bb0978edd27
8 changes: 7 additions & 1 deletion unified/extractor/ast_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ supertypes:
- do_while_stmt
- defer_stmt
- labeled_stmt
- associated_type_declaration
- initializer_declaration
callable:
subtypes:
- top_level
Expand All @@ -93,6 +95,7 @@ supertypes:
- class_like_declaration
- type_alias_declaration
- associated_type_declaration
- if_expr # For conditional compilation
- unsupported_node
- unhandled_node
fields:
Expand Down Expand Up @@ -261,7 +264,10 @@ named:
block:
stmt*: stmt

# An if expression or a conditional compilation directive (which we view as a
# compile-time evaluated if expression).
if_expr:
modifier*: modifier
condition: expr
then?: expr
else?: expr
Expand Down Expand Up @@ -499,7 +505,7 @@ named:
type_parameter*: type_parameter
type_constraint*: type_constraint
base_type*: base_type
member*: member
direct_member*: member

# One of the base types of a class declaration.
#
Expand Down
95 changes: 81 additions & 14 deletions unified/extractor/src/languages/swift/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ 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,
/// When iterating over the clauses of a conditional compilation block, this
/// holds the tail of the clauses that have already been translated.
conditional_compilation_tail: Option<yeast::OutputId>,
}

impl SwiftContext {
Expand All @@ -64,11 +67,29 @@ fn implicit_parameterless_initializer(
ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>,
members: &[yeast::OutputId],
) -> Option<yeast::OutputId> {
let has_explicit_initializer = members.iter().any(|member| {
ctx.output
.get_node(*member)
.is_some_and(|node| node.kind_name() == "constructor_declaration")
});
// Search for an initializer while recursing into nodes for conditional
// compilation blocks.
fn find_initializer(ast: &yeast::OutputAst, id: yeast::OutputId) -> Option<yeast::OutputId> {
let node = ast.get_node(id)?;
match node.kind_name() {
"constructor_declaration" => Some(id),
name => {
let fields: &[&str] = match name {
"if_expr" => &["then", "else"], // look into conditional compilation
"block" => &["stmt"], // look into blocks in branches
_ => &[],
};
fields.iter().find_map(|field| {
node.field_children(ast.field_id_for_name(field)?)
.iter()
.find_map(|child| find_initializer(ast, *child))
})
}
}
}
let has_explicit_initializer = members
.iter()
.any(|member| find_initializer(ctx.output, *member).is_some());
Comment on lines +90 to +92
(!has_explicit_initializer).then(|| {
tree!((constructor_declaration
modifier: (modifier "generated")
Expand Down Expand Up @@ -529,7 +550,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
modifier: [(modifier "chained_declaration") if { ctx.is_chained }]
modifier: (modifier "enum_case")
name_node: (identifier #{name})
member: {constructor}))
direct_member: {constructor}))
}
),
rule!(
Expand Down Expand Up @@ -872,6 +893,53 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
=>
(parameter pattern: (identifier #{name}))
),
// ---- Conditioal compilation blocks ----
// Conditional compilation blocks (`#if … #elseif … #else … #endif`) are
// represented with a `ifConfigDecl` node for the entire block and an
// `ifConfigClause` for each branch. We translate them into nested
// `if_expr` nodes.

// When a conditional compilation block contains a switch case it
// appears within a `switch` -- we do not support this yet.
rule!(
(ifConfigDecl clauses: (ifConfigClause elements: (switchCase)))
=>
(unsupported_node)
),
// A conditional compilation block is translated into `if_expr` nodes
// with branches occurring nested within the else branch.
rule!(
(ifConfigDecl clauses: _* @@clauses) @@declaration
=>
expr {
ctx.conditional_compilation_tail = None;
for clause in clauses.into_iter().rev() {
ctx.conditional_compilation_tail = ctx.translate(clause)?.into_iter().next();
}
let result = ctx.conditional_compilation_tail.ok_or("conditional compilation has no clauses")?;
ctx.set_node_source_range(result, ctx.source_range(declaration))
}
),
// A single clause is mapped to an if expression with the tail of
// already processed clauses (stored in `conditional_compilation_tail`)
// inserted into the else branch.
rule!(
(ifConfigClause condition: @condition elements: _* @items)
=>
(if_expr
// Mark this `if_expr` as coming from conditional compilation.
modifier: (modifier "conditional_compilation")
condition: {condition}
then: (block stmt: {items})
else: {ctx.conditional_compilation_tail})
),
// A conditional compilation clause without a condition (a final `#else`
// block).
rule!(
(ifConfigClause elements: _* @items)
=>
(block stmt: {items})
),
// ---- Control flow ----
// An `if`/`else` expression. Conditions are joined via `and_chain`; the
// then-body and optional else-body (another block, or an `ifExpr` for an
Expand Down Expand Up @@ -1333,8 +1401,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
type_constraint: {parameter_constraints}
type_constraint: {declaration_constraints}
base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))}
member: {members}
member: {implicit_initializer}))
direct_member: {members}
direct_member: {implicit_initializer}))
}
),
// Enum class declaration: same as a regular class but with an enum body.
Expand All @@ -1358,7 +1426,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
type_constraint: {parameter_constraints}
type_constraint: {declaration_constraints}
base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))}
member: {members})
direct_member: {members})
),
// A `struct` declaration, including an implicit parameter-less `init`
// when no explicit initializer is present.
Expand All @@ -1384,8 +1452,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
type_constraint: {parameter_constraints}
type_constraint: {declaration_constraints}
base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))}
member: {members}
member: {implicit_initializer}))
direct_member: {members}
direct_member: {implicit_initializer}))
}
),
// Protocol declaration
Expand All @@ -1406,7 +1474,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
type_parameter: {params}
type_constraint: {declaration_constraints}
base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))}
member: {members})
direct_member: {members})
),
// An `extension Foo.Bar { … }` is likewise a `class_like_declaration`.
rule!(
Expand All @@ -1422,7 +1490,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
modifier: {mods}
extension_target: {extendedType}
base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))}
member: {members})
direct_member: {members})
),
// A member of a type declaration unwraps to the contained declaration.
rule!((memberBlockItem decl: _* @d) => member* { d }),
Expand Down Expand Up @@ -1504,7 +1572,6 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
rule!((copyExpr) => (unsupported_node)),
rule!((discardStmt) => (unsupported_node)),
rule!((fallThroughStmt) => (unsupported_node)),
rule!((ifConfigDecl) => (unsupported_node)),
rule!((implicitlyUnwrappedOptionalType) => (unsupported_node)),
rule!((inlineArrayType) => (unsupported_node)),
rule!((keyPathExpr) => (unsupported_node)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ top_level source="⟨body⟩"
variable_declaration source="⟨modifier⟩\n⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩"
modifier:
modifier "let" source="let"
unsupported_node "#if DEBUG\n @available(*, deprecated)\n#endif" source="#if DEBUG\n @available(*, deprecated)\n#endif" <-- ERROR: The field variable_declaration.modifier should contain modifier, but got unsupported_node
if_expr source="#if ⟨condition⟩\n ⟨then⟩\n#endif" <-- ERROR: The field variable_declaration.modifier should contain modifier, but got if_expr
modifier: modifier "conditional_compilation" source=""
condition: identifier "DEBUG" source="DEBUG"
then:
block source="⟨stmt⟩"
stmt: modifier "@available(*, deprecated)" source="@available(*, deprecated)" <-- ERROR: The field block.stmt should contain stmt, but got modifier
pattern: identifier "x" source="x"
value: int_literal "1" source="1"
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,23 @@ top_level source="⟨body⟩"
body:
block source="⟨stmt⟩"
stmt:
class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨member⟩\n}"
class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨direct_member⟩\n}"
modifier: modifier "class" source="class"
name_node: identifier "C" source="C"
member:
unsupported_node "#if DEBUG\n init(x: Int) {}\n deinit {}\n #endif" source="#if DEBUG\n init(x: Int) {}\n deinit {}\n #endif"
constructor_declaration source=""
modifier: modifier "generated" source=""
name_node: identifier "init" source=""
body: block source=""
direct_member:
if_expr source="#if ⟨condition⟩\n ⟨then⟩\n #endif"
modifier: modifier "conditional_compilation" source=""
condition: identifier "DEBUG" source="DEBUG"
then:
block source="⟨stmt⟩\n ⟨stmt⟩"
stmt:
constructor_declaration source="⟨name_node⟩(⟨parameter⟩) ⟨body⟩"
name_node: identifier "init" source="init"
parameter:
parameter source="⟨external_name_node⟩⟨pattern⟩: ⟨type⟩"
external_name_node: identifier "x" source="x"
type: identifier "Int" source="Int"
pattern: identifier "x" source="x"
body: block "{}" source="{}"
destructor_declaration source="deinit ⟨body⟩"
body: block "{}" source="{}"
Original file line number Diff line number Diff line change
Expand Up @@ -273,17 +273,76 @@ top_level source="⟨body⟩"
body:
block source="⟨stmt⟩\n\n⟨stmt⟩\n\n⟨stmt⟩"
stmt:
unsupported_node "#if DEBUG\n let first = 1\n let second = 2\n #if EXTRA\n func f() {}\n #elseif OTHER\n #else\n let third = 3\n #endif\n#else\n#endif" source="#if DEBUG\n let first = 1\n let second = 2\n #if EXTRA\n func f() {}\n #elseif OTHER\n #else\n let third = 3\n #endif\n#else\n#endif"
class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨member⟩\n}"
if_expr source="#if ⟨condition⟩\n ⟨then⟩\n⟨else⟩\n#endif"
modifier: modifier "conditional_compilation" source=""
condition: identifier "DEBUG" source="DEBUG"
then:
block source="⟨stmt⟩\n ⟨stmt⟩\n ⟨stmt⟩"
stmt:
variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩"
modifier: modifier "let" source="let"
pattern: identifier "first" source="first"
value: int_literal "1" source="1"
variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩"
modifier: modifier "let" source="let"
pattern: identifier "second" source="second"
value: int_literal "2" source="2"
if_expr source="#if ⟨condition⟩\n ⟨then⟩\n ⟨else⟩\n #endif"
modifier: modifier "conditional_compilation" source=""
condition: identifier "EXTRA" source="EXTRA"
then:
block source="⟨stmt⟩"
stmt:
function_declaration source="func ⟨name_node⟩() ⟨body⟩"
name_node: identifier "f" source="f"
body: block "{}" source="{}"
else:
if_expr source="#elseif ⟨condition⟩\n ⟨else⟩"
modifier: modifier "conditional_compilation" source=""
condition: identifier "OTHER" source="OTHER"
then: block source=""
else:
block source="#else\n ⟨stmt⟩"
stmt:
variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩"
modifier: modifier "let" source="let"
pattern: identifier "third" source="third"
value: int_literal "3" source="3"
else: block "#else" source="#else"
class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨direct_member⟩\n}"
modifier: modifier "protocol" source="protocol"
name_node: identifier "P" source="P"
member: unsupported_node "#if DEBUG\n associatedtype T\n #endif" source="#if DEBUG\n associatedtype T\n #endif"
direct_member:
if_expr source="#if ⟨condition⟩\n ⟨then⟩\n #endif"
modifier: modifier "conditional_compilation" source=""
condition: identifier "DEBUG" source="DEBUG"
then:
block source="⟨stmt⟩"
stmt:
associated_type_declaration source="associatedtype ⟨name_node⟩"
name_node: identifier "T" source="T"
function_declaration source="func ⟨name_node⟩() ⟨body⟩"
name_node: identifier "use" source="use"
body:
block source="{\n ⟨stmt⟩\n ⟨stmt⟩\n}"
stmt:
unsupported_node "#if DEBUG\n let value = 1\n #else\n let value = 2\n #endif" source="#if DEBUG\n let value = 1\n #else\n let value = 2\n #endif"
if_expr source="#if ⟨condition⟩\n ⟨then⟩\n ⟨else⟩\n #endif"
modifier: modifier "conditional_compilation" source=""
condition: identifier "DEBUG" source="DEBUG"
then:
block source="⟨stmt⟩"
stmt:
variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩"
modifier: modifier "let" source="let"
pattern: identifier "value" source="value"
value: int_literal "1" source="1"
else:
block source="#else\n ⟨stmt⟩"
stmt:
variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩"
modifier: modifier "let" source="let"
pattern: identifier "value" source="value"
value: int_literal "2" source="2"
if_expr source="if ⟨condition⟩ ⟨then⟩"
condition: boolean_literal "true" source="true"
then:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,42 @@ top_level source="⟨body⟩"
body:
block source="⟨stmt⟩"
stmt:
class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨member⟩\n}"
class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨direct_member⟩\n}"
modifier: modifier "class" source="class"
name_node: identifier "C" source="C"
member:
unsupported_node "#if DEBUG\n init(x: Int) {}\n #elseif RELEASE\n init(x: String) {}\n #else\n init() {}\n #endif" source="#if DEBUG\n init(x: Int) {}\n #elseif RELEASE\n init(x: String) {}\n #else\n init() {}\n #endif"
constructor_declaration source=""
modifier: modifier "generated" source=""
name_node: identifier "init" source=""
body: block source=""
direct_member:
if_expr source="#if ⟨condition⟩\n ⟨then⟩\n ⟨else⟩\n #endif"
modifier: modifier "conditional_compilation" source=""
condition: identifier "DEBUG" source="DEBUG"
then:
block source="⟨stmt⟩"
stmt:
constructor_declaration source="⟨name_node⟩(⟨parameter⟩) ⟨body⟩"
name_node: identifier "init" source="init"
parameter:
parameter source="⟨external_name_node⟩⟨pattern⟩: ⟨type⟩"
external_name_node: identifier "x" source="x"
type: identifier "Int" source="Int"
pattern: identifier "x" source="x"
body: block "{}" source="{}"
else:
if_expr source="#elseif ⟨condition⟩\n ⟨then⟩\n ⟨else⟩"
modifier: modifier "conditional_compilation" source=""
condition: identifier "RELEASE" source="RELEASE"
then:
block source="⟨stmt⟩"
stmt:
constructor_declaration source="⟨name_node⟩(⟨parameter⟩) ⟨body⟩"
name_node: identifier "init" source="init"
parameter:
parameter source="⟨external_name_node⟩⟨pattern⟩: ⟨type⟩"
external_name_node: identifier "x" source="x"
type: identifier "String" source="String"
pattern: identifier "x" source="x"
body: block "{}" source="{}"
else:
block source="#else\n ⟨stmt⟩"
stmt:
constructor_declaration source="⟨name_node⟩() ⟨body⟩"
name_node: identifier "init" source="init"
body: block "{}" source="{}"
Loading