Skip to content

refactor(ast): converge dispatch tables onto a canonical node catalog (#1243)#1249

Merged
nickna merged 1 commit into
mainfrom
worktree-issue-1243
Jul 4, 2026
Merged

refactor(ast): converge dispatch tables onto a canonical node catalog (#1243)#1249
nickna merged 1 commit into
mainfrom
worktree-issue-1243

Conversation

@nickna

@nickna nickna commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Issue #1243 — Converge the AST dispatch tables onto a single canonical node list

Follow-up to #1143 / epic #1094.

Approach

The issue asked to "evaluate a source generator (or a single canonical table)" so a node can't
silently skip a dispatch table. I started down the source-generator path but that means introducing
Roslyn (Microsoft.CodeAnalysis.CSharp) as a build-time dependency — which this repo does not
currently use, on a preview SDK. A generator's only real advantage here is turning a missed node into
a compile error rather than a test failure, and only one table is actually hand-maintained
(the AstVisitorBase switch). NodeRegistry already derives its node set by reflection and validates
exhaustiveness at startup.

So this takes the lighter, dependency-free route: a single canonical table everything converges on.

What changed

  • New Parsing/Visitors/AstNodeCatalog.cs — the single source of truth for the AST node set,
    derived once (reflection) from the Expr/Stmt records in AST.cs using the exact predicate the
    dispatch tables used before (concrete, directly-nested, inherits the base). Exposes ExprTypes /
    StmtTypes.

  • NodeRegistryFreeze(), FreezeAsync(), and AutoRegister() now read the node set from
    AstNodeCatalog instead of each re-running its own typeof(Expr).GetNestedTypes(...) reflection.
    Three duplicated reflection sites collapse to one. Behavior is identical (same predicate, same set),
    so the interpreter/type-checker startup exhaustiveness checks — and the InterpreterRegistry
    async-handler FreezeAsync check — are unchanged except for their source of truth.

  • New SharpTS.Tests/RegistryTests/AstDispatchTests.cs — guards the one hand-maintained table.
    It walks every catalog node through AstVisitorBase.Visit and asserts none falls through to the
    default arm (the "no dispatch case" NotSupportedException). A self-check (Probe_detects_an_unhandled_node)
    proves the discriminator actually fires for an out-of-catalog node. Adding a node to AST.cs now
    auto-appears in the catalog, so forgetting its AstVisitorBase case fails this test instead of only
    blowing up at visit time.

Result — every dispatch table now converges on AstNodeCatalog

Table How a missing node is caught
NodeRegistry sync handlers (interp + checker) startup Freeze() against the catalog
NodeRegistry async expr handlers (interp) startup FreezeAsync() against the catalog
AstVisitorBase.Visit(Expr/Stmt) switch AstDispatchTests against the catalog

No new dependencies; no new projects.

Testing

  • AstDispatchTests: 4/4 pass.
  • Full xUnit suite: 15419 passed, 0 failed.
  • Test262 (interp + compiled): 22 passed, 0 failed, 4 skipped (baseline-green).
  • TypeScript conformance: 31 passed, 0 failed (baseline-green).
  • Interpreter + compiled smoke of arrays/functions/classes: identical output in both modes.

Closes #1243.

…#1243)

Introduce AstNodeCatalog as the single source of truth for the AST node
set, derived once from the Expr/Stmt records in AST.cs. NodeRegistry's
Freeze/FreezeAsync/AutoRegister now read it instead of each re-running its
own typeof(Expr).GetNestedTypes reflection, collapsing three duplicated
reflection sites to one (behavior-identical predicate/set).

Add AstDispatchTests to guard the one remaining hand-maintained table, the
AstVisitorBase Visit(Expr/Stmt) switch: every catalog node must dispatch
without falling through to the "no dispatch case" default arm, with a
self-check proving the probe fires for an out-of-catalog node. A node added
to AST.cs now auto-appears in the catalog, so omitting its visitor case
fails a test instead of only blowing up at visit time.

Evaluated the issue's source-generator option but chose the dependency-free
canonical-table route: SharpTS uses no Roslyn today, and only one table is
hand-maintained. No new dependencies or projects.

xUnit 15419/0, Test262 22/0/4skip, TSConf 31/0.

Closes #1243.
@nickna nickna merged commit 2629d1e into main Jul 4, 2026
2 checks passed
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.

Converge the AST dispatch tables onto NodeRegistry (follow-up to #1143)

1 participant