⚡ Sangsakala Proxy 🏠 Home
Skip to content

Unified: Support Swift conditional compilation blocks - #22992

Open
paldepind wants to merge 4 commits into
mainfrom
unified/conditional-compilation
Open

paldepind wants to merge 4 commits into
mainfrom
unified/conditional-compilation

Conversation

@paldepind

@paldepind paldepind commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Extract Swift conditional compilation blocks and handle them in local name binding.

Some of the changes:

  • Extract #if/#elseif/#else as nested if expressions.

  • Separate direct class members from compilation-expanded members, exposed through getAMember()/getMember(i).

  • Extend the shared local name binding library to handle conditional-compilation blocks. The comments in QL should explain the changes for this.

NB: This PR does not adapt static name bindings to handle conditional compilation. I thought the PR was big enough as-is, so I'll do that in follow up work.

I suggest per-commit review.

@paldepind
paldepind force-pushed the unified/conditional-compilation branch from bc24bfb to b3bde8e Compare November 3, 2026 10:46
@paldepind paldepind changed the title Unified: support Swift conditional compilation Unified: Support Swift conditional compilation blocks Nov 3, 2026
@paldepind
paldepind marked this pull request as ready for review November 3, 2026 11:26
Copilot AI auto review requested due to automatic review settings November 3, 2026 11:26
@paldepind
paldepind requested review from a team as code owners November 3, 2026 11:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Conditional-compilation handling still drops valid synthesized initializers, mishandles name binding for flags and branch declarations, and causes false unused-variable reports.

4 open issues
What changed in this PR

This PR adds Swift conditional-compilation blocks to the Unified extractor and extends local name binding to follow declarations across compilation alternatives. Full static name-binding support remains follow-up work.

Changes:

  • Represent #if/#elseif/#else blocks as nested conditional expressions.
  • Separate direct class members from compilation-expanded members and expose semantic top-level statements.
  • Extend local scoping and add extraction, constructor, and control-flow coverage.
File Description
unified/​ql/​test/​library-tests/​local-name-binding/​conditional-compilation.swift Tests conditional bindings, shadowing, and captures.
unified/​ql/​test/​library-tests/​controlflow/​cfg.swift Adds conditional-compilation flow assertions.
unified/​ql/​test/​library-tests/​controlflow/​cfg.expected Records new control-flow results.
unified/​ql/​test/​library-tests/​controlflow/​basicblock-slices.expected Records conditional basic blocks.
unified/​ql/​test/​library-tests/​constructors/​test.expected Records conditional constructor and field results.
unified/​ql/​test/​library-tests/​constructors/​constructors.swift Adds conditional constructors and fields.
unified/​ql/​lib/​unified.dbscheme Updates member relations and conditional node schema.
unified/​ql/​lib/​codeql/​unified/​internal/​StaticNameBinding.qll Uses compilation-expanded top-level statements.
unified/​ql/​lib/​codeql/​unified/​internal/​NameBindingPluginSwift.qll Recognizes conditional top-level private declarations.
unified/​ql/​lib/​codeql/​unified/​internal/​NameBindingPlugin.qll Expands top-level static-member classification.
unified/​ql/​lib/​codeql/​unified/​internal/​LocalNameBinding.qll Marks compilation conditionals as unscoped.
unified/​ql/​lib/​codeql/​unified/​internal/​FacadeAst.qll Exposes semantic statements and expanded members.
unified/​ql/​lib/​codeql/​unified/​internal/​AstExtra.qll Expands top-level statement classification.
unified/​ql/​lib/​codeql/​unified/​internal/​Ast.qll Updates generated AST accessors and types.
unified/​extractor/​tests/​corpus/​swift/​variables/​property-with-willset-and-didset-observers.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​struct.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​static-function.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​qualified-type.output Updates nested direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​protocol-with-read-only-and-read-write-property-requirements.output Updates protocol direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​protocol-declaration.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​property-with-getter-and-setter.output Updates accessor direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​noncopyable-type.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​generic-class-parameters-and-constraints.output Updates generated constructor placement.
unified/​extractor/​tests/​corpus/​swift/​types/​extension.output Updates extension direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​enum-with-comma-separated-cases-chained-declaration.output Updates enum direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​enum-with-cases.output Updates enum direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​enum-with-associated-values.output Updates enum-case constructor placement.
unified/​extractor/​tests/​corpus/​swift/​types/​empty-class.output Updates generated constructor placement.
unified/​extractor/​tests/​corpus/​swift/​types/​constructor-with-parameters.output Updates constructor direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​conditional-compilation-in-class-body.swift Removes the old unsupported-block fixture.
unified/​extractor/​tests/​corpus/​swift/​types/​computed-property.output Updates property direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​class-with-stored-properties.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​class-with-multiple-base-types.output Updates generated constructor placement.
unified/​extractor/​tests/​corpus/​swift/​types/​class-with-method.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​class-with-initializer.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​types/​class-inheritance.output Updates generated constructor placement.
unified/​extractor/​tests/​corpus/​swift/​types/​class-function.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​functions/​constructor-call-with-type-arguments.output Updates generated constructor placement.
unified/​extractor/​tests/​corpus/​swift/​expressions/​super-expression.output Updates class direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​control-flow/​discard-statement.output Updates direct-member expectations.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-with-operators.swift Adds compound compilation conditions.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-with-operators.output Records condition operator extraction.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-with-elseif.swift Adds an #elseif chain.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-with-elseif.output Records nested conditional expressions.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-with-elseif-in-class-body.swift Adds conditional initializer alternatives.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-with-elseif-in-class-body.output Records conditional class members.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-postfix-expression.swift Adds a postfix conditional fixture.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-postfix-expression.output Records unsupported postfix extraction.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-nested.swift Adds nested and empty compilation branches.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-nested.output Records nested branch extraction.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-in-switch-cases.swift Adds conditional switch cases.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-in-switch-cases.output Records unsupported switch-case extraction.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-in-class-body.swift Adds the relocated class-body fixture.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-in-class-body.output Records extracted initializers and destructors.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-in-attributes.swift Adds conditional attribute fixtures.
unified/​extractor/​tests/​corpus/​swift/​conditional-compilation/​conditional-compilation-in-attributes.output Records current attribute extraction limitations.
unified/​extractor/​src/​languages/​swift/​swift.rs Lowers compilation blocks and searches conditional initializers.
unified/​extractor/​ast_types.yml Defines conditional modifiers and direct members.
shared/​namebinding/​codeql/​namebinding/​LocalNameBinding.qll Propagates shadowing declarations across unscoped alternatives.

Review effort: Balanced (auto)

Comment on lines +260 to +263
private predicate lastDeclAfter(AstNode node, SiblingShadowingDecl decl) {
node = decl
or
node = any(Conditional cond | isUnscopedConditional(cond) and lastDeclInBranch(cond, decl))
Comment on lines +90 to +92
let has_explicit_initializer = members
.iter()
.any(|member| find_initializer(ctx.output, *member).is_some());
(if_expr
// Mark this `if_expr` as coming from conditional compilation.
modifier: (modifier "conditional_compilation")
condition: {condition}
/** A `Stmt` at the top-level. */
final class TopLevelStmt extends Stmt {
TopLevelStmt() { this = any(TopLevel t).getBody().getAStmt() }
TopLevelStmt() { this = any(TopLevel t).getBody().getASemanticStmt() }
@paldepind paldepind added the no-change-note-required This PR does not need a change note label Nov 3, 2026

@asgerf asgerf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some partial comments so far. I still need to review the local scoping changes.

/** A block statement. */
class Block extends G::Block {
/** Gets a statement, looking through conditional-compilation branches. */
Stmt getASemanticStmt() { result = compilationExpandedStmt(this.getAStmt()) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API we get after these changes:

  • Classes:
    • getADirectMember() -- raw AST
    • getAMember() -- include members in conditionals
    • getAMemberIncludingExtensions() -- include members in extensions and conditionals
  • Blocks:
    • getAStmt() -- raw AST
    • getASemanticStmt() -- include members in conditionals

This is not a very consistent naming scheme. We can do better.

I'd suggest the following API. Boring but consistent.

  • Classes:
    • getAMember() -- raw AST
    • getAMemberIncludingConditionals() -- include members in conditionals (but not extensions)
    • getAMemberIncludingExtensions() -- include members in extensions and conditionals
  • Blocks:
    • getAStmt() -- raw AST
    • getAStmtIncludingConditionals() -- renamed version of getASemanticStmt()

getAStmtIncludingConditionals() could possibly be removed and rolled into TopLevelStmt as it's the only current use-case (the other call sites can just use TopLevelStmt) but I admit it's hard to say if more use-cases will emerge.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts:

  • I think it's a downside that the "wrong" predicate (the raw member version for classes, which is rarely what you want) gets the shorter nicer name. That's why I renamed getAMember to getADirectMember, to make the probably-not-what-you-want predicate to have a longer name.

    For blocks it's different. Often get getASemanticStmt isn't what you want, so I was ok with getAStmt having the short name.

  • From the name it's not clear that getAMemberIncludingExtensions also includes members in conditional compilation blocks.

  • The word "semantic" is more vague, but also open to other things getting included in the future.

I don't have a strong preference though, so the suggested change (or other variations) are fine by me.

* Gets the `i`th member in source order across all conditional-compilation alternatives,
* indexed from zero.
*/
Member getMember(int i) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should provide indexed-access for "flattening accessors" like this. There should just be the plural version.

paldepind and others added 4 commits November 3, 2026 15:19
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.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3fac01-33b2-423c-b823-11035cf44b84
@paldepind
paldepind force-pushed the unified/conditional-compilation branch from 901c3b7 to ec3a837 Compare November 3, 2026 14:20

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-change-note-required This PR does not need a change note Unified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants