Skip to content

refactor(compiler): data-driven statement dispatch in LLVMCodeGenerator (#170)#628

Closed
KrisSimon wants to merge 1 commit intomainfrom
migrate-open-231
Closed

refactor(compiler): data-driven statement dispatch in LLVMCodeGenerator (#170)#628
KrisSimon wants to merge 1 commit intomainfrom
migrate-open-231

Conversation

@KrisSimon
Copy link
Copy Markdown
Member

Migrated from GitLab MR !231 (open)
refactor/170-llvm-codegen-dispatch-tablemain
Originally created: 2026-04-06
Author: Kris Simon
Labels: 0.9

Summary

Closes #170. `LLVMCodeGenerator.generateStatement()` used a 20-line `switch` on concrete `Statement` subtypes. Adding a new statement kind meant editing the switch, and the dispatch logic could not be tested in isolation without compiling a full program.

What changed

  • `Sources/AROCompiler/LLVMC/LLVMCodeGenerator.swift` — introduce `StatementHandler = (LLVMCodeGenerator) -> (Statement, Int, BasicBlock) -> Void` and a static `[ObjectIdentifier: StatementHandler]` table keyed by each concrete `Statement` metatype. Each handler takes a generator instance and returns a closure that performs the checked downcast and forwards to the corresponding `generate...` instance method — no captured `self`, no reference cycles, no per-instance setup. `generateStatement` becomes a two-line lookup with a single fall-through error path.
  • Expose an `internal static var supportedStatementTypeIdentifiers: Set` hook so the test suite can pin the expected coverage.
  • `Tests/AROCompilerTests/LLVMCodeGeneratorTests.swift` — new `testStatementDispatchTableCoversSupportedTypes` regression test that locks in the set of supported `Statement` subtypes (`AROStatement`, `MatchStatement`, `ForEachLoop`, `RangeLoop`, `WhileLoop`, `BreakStatement`, `PublishStatement`, `RequireStatement`). Accidental drift — e.g. removing a handler, or adding a new `Statement` subtype to the AST without wiring it into code-gen — now fails loudly at test time instead of silently taking the error path at first use.

Why this shape

Using a static method-reference table (`(LLVMCodeGenerator) -> (Statement, ...) -> Void`) keeps the handlers in a stored `static let` with immutable shared state — annotated `nonisolated(unsafe)` because the closures are pure forwarders with no captured mutable state, which is sound under Swift 6 strict concurrency. This gives us data-driven dispatch without the overhead or reference-cycle hazards of per-instance closure tables captured in `init`.

Behavior preservation

Pure refactor: `PipelineStatement` and `ErrorStatement` continue to fall through to the default error branch exactly as before — the refactor deliberately does not change the set of supported statement kinds. Any follow-up that adds a new supported kind is a one-line table entry + a one-line test expectation.

Test plan

  • `swift build`
  • `swift test` — 1576/1576 pass
  • `swift test --filter LLVMCodeGeneratorTests` — all 9 tests (including the new regression test) pass
  • `swift run aro build Examples/HelloWorld` + run — OK
  • `swift run aro build Examples/Iteration` + run — OK (exercises `ForEachLoop` codegen)

@KrisSimon KrisSimon closed this Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant