Skip to content

Unified: Add static name binding pass - #22299

Open
asgerf wants to merge 24 commits into
github:mainfrom
asgerf:unified/static-name-binding
Open

Unified: Add static name binding pass#22299
asgerf wants to merge 24 commits into
github:mainfrom
asgerf:unified/static-name-binding

Conversation

@asgerf

@asgerf asgerf commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Adds a static name-binding pass, resolving references to static members, including static-like members such as nested classes.

In addition to resolving static members, I also expect this to be responsible for instance-member lookups in classes.

Commit-by-commit review recommended. The PR also contains a lot of drive-by fixes to the AST mapping (some of it to make tests pass, some of it to make it work on real-world projects).

Name-binding graph

Static name resolution is modelled as a graph with edges between them, using the well-known value/store/read step kinds, in addition to inheritance steps.

Cyclic Dependency on Class Hierarchy

Swift allows unqualified access to inherited members, so for example, Foo below could refer to C.Foo or A.Foo.

import A
class B : C {
    let x: Foo // <-- C.Foo or A.Foo?
}

The base classes themselves can also refer to members inherited by the outer class:

import A
class B : C {
    class D : E { // <-- inherit from C.E or A.E?
        let x: Foo; // <-- E.Foo or C.Foo or A.Foo?
    }
}

Inheritance thus depends on static name resolution, and static name resolution depends on inheritance. We resolve both in a single recursive layer.

Unqualified Lookups

An unqualified identifier gives rise to a read step from each of the scopes it could potentially be found in. In the above example, we'd get three read steps into Foo, corresponding to the three places we might find Foo.

The local name-binding pass has been augmented to support "uncertain scopes" and report back which uncertain scopes a given name access might be found it. This ensures that locally declared names can shadow lookups in outer (uncertain) scopes. For example, a locally declared Foo in the B class would block the read steps corresponding to C and A, while keeping the one from E.

The read steps currently have no priority/ranking, so there is no shadowing supported between uncertain members. There are ways to sharpen the precision a bit here, but I don't expect it to matter much -- let's only do that if we see a need for it.

Cross-file imports

This PR also adds some support for import resolution for projects with Package.swift files.

Future work

  • Access levels other than public and "fully private"
  • Type-extensions
  • More Package.swift fidelity, such as cross-package imports
  • Other build system manifests, such as xcode project files
  • Support instance-member lookups

asgerf added 23 commits August 7, 2026 10:58
getValue() returns an empty string for various literals
This supports only a minimal set of features but sets up the structure
we'll be using for supporting more features.
The restriction to classes was more permanent that anticipated, since
top-level scopes instead target a TModuleScope
Scoped imports like 'import class B.C' are mapped to an AST of form

ImportDeclartion
  pattern: NamePattern "C"
  importedExpr:  MemberAccessExpr
    base: "B"
    member: "C"

The NamePattern introduces a local alias for 'C', but unlike type aliases we also resolve to the ultimate target, when it's coming through an import.
Program was not valid unless these were public
Module names can only be referenced by an import declaration, they cannot appear directly on front of a type name unless the module is also imported.
@asgerf asgerf added the no-change-note-required This PR does not need a change note label Aug 7, 2026
Comment thread unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll Dismissed
Comment thread unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll Dismissed
An `import X` declaration now does two things:
- X becomes a local name binding
- X is bulk-imported into the local scope

The AST mapping now maps it to X with a bulk-importing pattern as a sub-pattern.

Module names can no longer be referenced anywhere except as the leading qualifier of an import statement.
@asgerf
asgerf force-pushed the unified/static-name-binding branch from b3adb93 to 970e4c6 Compare August 10, 2026 08:34
@asgerf
asgerf marked this pull request as ready for review August 10, 2026 09:22
@asgerf
asgerf requested review from a team as code owners August 10, 2026 09:22
Copilot AI balanced review requested due to automatic review settings August 10, 2026 09:22
@asgerf
asgerf requested a review from a team as a code owner August 10, 2026 09:22
@asgerf
asgerf requested a review from hvitved August 10, 2026 09:23

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.

Pull request overview

Adds Swift static name binding across classes, inheritance, modules, and imports, supported by extractor mapping improvements.

Changes:

  • Adds static name-binding and uncertain-scope resolution.
  • Models SwiftPM targets and cross-file imports.
  • Improves Swift AST translation and adds regression fixtures.
Show a summary per file
File Description
unified/ql/test/library-tests/static-name-binding/unqualified-access.swift Tests inherited unqualified access.
unified/ql/test/library-tests/static-name-binding/test.swift Tests static member resolution.
unified/ql/test/library-tests/static-name-binding/test.ql Implements binding assertions.
unified/ql/test/library-tests/static-name-binding/test.expected Stores expected test results.
unified/ql/test/library-tests/static-name-binding/package1/Sources/Target2/File3.swift Defines imported declarations.
unified/ql/test/library-tests/static-name-binding/package1/Sources/Target1/File3.swift Tests bulk imports.
unified/ql/test/library-tests/static-name-binding/package1/Sources/Target1/File2.swift Tests scoped imports.
unified/ql/test/library-tests/static-name-binding/package1/Sources/Target1/File1.swift Tests module visibility.
unified/ql/test/library-tests/static-name-binding/package1/Package.swift Defines test package targets.
unified/ql/test/library-tests/static-name-binding/inheritance.swift Tests inherited static members.
unified/ql/test/library-tests/local-name-binding/test.ql Uses shared comment utilities.
unified/ql/test/library-tests/BasicTest/test.ql Tests string literal values.
unified/ql/test/library-tests/BasicTest/test.expected Updates string expectations.
unified/ql/test/library-tests/BasicTest/strings.swift Adds string fixture.
unified/ql/lib/utils/test/CommentUtil.qll Extracts inline test metadata.
unified/ql/lib/unified.dbscheme Adds name-pattern sub-pattern relation.
unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll Implements static binding graph.
unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll Adds Swift-specific binding rules.
unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll Defines language extension points.
unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll Exposes declarations and uncertain scopes.
unified/ql/lib/codeql/unified/internal/FacadeAst.qll Adds file, string, and argument helpers.
unified/ql/lib/codeql/unified/internal/dev/debugStaticNameBindingGraph.ql Adds static graph visualization.
unified/ql/lib/codeql/unified/internal/dev/debugLocalNameBindingGraph.ql Renames local graph query.
unified/ql/lib/codeql/unified/internal/Ast.qll Exposes generated sub-pattern accessors.
unified/ql/lib/codeql/files/FileSystem.qll Exposes the folder module.
unified/extractor/tests/corpus/swift/types/qualified-type.swift Adds qualified-type fixture.
unified/extractor/tests/corpus/swift/types/qualified-type.output Records qualified-type AST.
unified/extractor/tests/corpus/swift/optionals-and-errors/optional-enum-case-binding.swift Adds optional-pattern fixture.
unified/extractor/tests/corpus/swift/optionals-and-errors/optional-enum-case-binding.output Records optional-pattern AST.
unified/extractor/tests/corpus/swift/functions/leading-dot-expression-value.output Updates inferred-type location.
unified/extractor/tests/corpus/swift/functions/leading-dot-expression-call.output Updates inferred-type location.
unified/extractor/tests/corpus/swift/expressions/array-type-metatype.swift Adds array metatype fixture.
unified/extractor/tests/corpus/swift/expressions/array-type-metatype.output Records array metatype AST.
unified/extractor/tests/corpus/swift/expressions/array-type-constructor.swift Adds array constructor fixture.
unified/extractor/tests/corpus/swift/expressions/array-type-constructor.output Records array constructor AST.
unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output Updates bulk-import pattern shape.
unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output Updates dotted import shape.
unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output Updates deep import shape.
unified/extractor/tests/corpus/swift/control-flow/nested-enum-case-pattern.swift Adds nested-pattern fixture.
unified/extractor/tests/corpus/swift/control-flow/nested-enum-case-pattern.output Records nested-pattern AST.
unified/extractor/src/languages/swift/swift.rs Expands Swift AST translation.
unified/extractor/ast_types.yml Adds nested name patterns.
shared/namebinding/codeql/namebinding/LocalNameBinding.qll Adds uncertain-scope support.

Review details

Tip

Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (1)

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

  • This special case also matches ordinary nested calls, not only enum-case patterns. Because it reconstructs the inner functionCallExpr without its trailingClosure, code such as consume(xs.map { ... }) silently loses the closure. The later generic labeledExpr rule can already preserve the nested constructor pattern because captures are translated with in_pattern; remove this special case so the normal call rule handles all call fields.
                expression: (functionCallExpr
                    calledExpression: @constructor
                    arguments: _* @elements))
  • Files reviewed: 42/43 changed files
  • Comments generated: 3
  • Review effort level: Balanced

)
}

/** Gets the source folder to use if no explicit `path:` if given, typically `Sources/<Target>` */
Comment on lines +624 to +634
rule!(
(functionCallExpr
calledExpression: (arrayExpr elements: (arrayElement expression: (genericSpecializationExpr) @element))
arguments: _* @args)
=>
(call_expr
callee: (generic_type_expr
base: (named_type_expr name: (identifier "Array"))
type_argument: {element})
argument: {args})
),
Comment on lines +556 to +558
rule!((expressionPattern expression: @@e) => expr {
ctx.in_pattern = true;
ctx.translate(e)?.into_iter().next().ok_or("expression pattern has no child")?
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants