Skip to content

Marten#4486: Dispatcher-coverage regression guard in Marten.AotSmoke#4487

Merged
jeremydmiller merged 1 commit into
masterfrom
feat/4486-aotsmoke-dispatcher-coverage
May 19, 2026
Merged

Marten#4486: Dispatcher-coverage regression guard in Marten.AotSmoke#4487
jeremydmiller merged 1 commit into
masterfrom
feat/4486-aotsmoke-dispatcher-coverage

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Summary

Closes #4486. Closes #4471 (companion to PR #4485 which landed the static inventory).

Extends Marten.AotSmoke from "AOT-clean-surface smoke" into the dispatcher-coverage sentinel #4486 carves out of #4471. The static inventory at audit/projections-inventory.md (#4485) documents the existing state; this PR is the regression guard that catches a future SG discovery-rule break before CI's downstream tests even start.

What gets exercised

Three new fixture types alongside the existing SmokeProjection, covering every canonical SG emission pattern × Apply-vs-Evolve method convention:

Type Pattern Method convention Expected SG emission
SmokeProjection (existing) A — projection subclass explicit Evolve override none (deliberate bypass)
ApplyProjection (new) A — projection subclass conventional Apply partial-class merge of Evolve(...)
SelfAggregatingApply (new) B — self-aggregating aggregate conventional Apply sibling SelfAggregatingApplyEvolver class + [assembly: GeneratedEvolver(...)]
SelfAggregatingEvolve (new) B — self-aggregating aggregate explicit Evolve on aggregate sibling SelfAggregatingEvolveEvolver class + [assembly: GeneratedEvolver(...)]

How the sentinel works

ProjectionOptions.SingleStreamProjection<T>() calls source.AssembleAndAssertValidity() at registration time. Post-#276 that throws InvalidProjectionException whenever the SG didn't emit a dispatcher for the shape. So if a future SG change drops support for one of these canonical patterns, dotnet run --project Marten.AotSmoke exits non-zero and CI catches it.

The build is not the sentinel. Sanity-checked locally: stripping partial from ApplyProjection lets the build pass (the SG silently skips emission when its preconditions don't hold — no CS0260 fires) but the runtime then throws at Projections.Add<ApplyProjection>(...) with the expected "No source-generated dispatcher found for ApplyProjection" message and the process exits 134.

Csproj change

Marten.AotSmoke.csproj needed an Analyzer-only PackageReference to JasperFx.Events.SourceGenerator. Marten.csproj sets PrivateAssets="all" on the SG so the analyzer doesn't flow transitively — same wiring CoreTests / DocumentDbTests / MultiTenancyTests / ValueTypeTests use for their own SG-emit-expected types.

AOT contract preserved

IsAotCompatible=true, TrimMode=full, and the promoted-to-error IL warning list (IL2026;IL2046;IL2055;IL2065;IL2067;IL2070;IL2072;IL2075;IL2090;IL2091;IL2111;IL3050;IL3051) all keep holding with the new fixtures and registrations.

Test plan

  • Clean rebuild on net9.0 and net10.0 — 0 errors
  • dotnet run --project Marten.AotSmoke -f net9.0 → exit 0, expected "Marten AOT smoke OK" line
  • dotnet run --project Marten.AotSmoke -f net10.0 → exit 0, same
  • Sanity: strip partial from ApplyProjection → build still passes (SG silently skips) → runtime throws InvalidProjectionException and exits 134. Restored.

🤖 Generated with Claude Code

Extends Marten.AotSmoke from the AOT-clean-surface smoke into the
dispatcher-coverage sentinel #4486 carves out of #4471.

Adds three new fixture projection shapes alongside the existing
SmokeProjection so AotSmoke now exercises every canonical SG
emission pattern:

  Pattern A — projection subclass; SG emits a partial declaration
    merged into the user's class:
      SmokeProjection            — explicit Evolve override (deliberate bypass)
      ApplyProjection (new)      — conventional Apply method

  Pattern B — self-aggregating aggregate; SG emits a sibling
    XEvolver class + [assembly: GeneratedEvolver(typeof(X),
    typeof(XEvolver))] registration. Aggregate type does NOT need
    to be partial:
      SelfAggregatingApply (new) — conventional Apply on the aggregate
      SelfAggregatingEvolve(new) — explicit Evolve on the aggregate

ProjectionOptions.SingleStreamProjection<T>() calls
AssembleAndAssertValidity() at registration time; post-#276 that
throws InvalidProjectionException whenever the SG didn't emit for
a shape, so any regression in the SG's discovery rules trips the
exit code here. Sanity-checked: stripping `partial` from
ApplyProjection makes `dotnet run` exit 134 with the expected
"No source-generated dispatcher found for ApplyProjection" message.

The build is NOT the sentinel — the SG silently skips emission
when its preconditions (e.g. partial on Pattern A) aren't met, so
no CS0260 fires. The runtime fail-fast catches it.

Marten.AotSmoke.csproj needed an Analyzer-only PackageReference
to JasperFx.Events.SourceGenerator. Marten.csproj sets
PrivateAssets="all" on the SG so the analyzer doesn't flow
transitively — same wiring CoreTests / DocumentDbTests /
MultiTenancyTests / ValueTypeTests use for their own
SG-emit-expected types.

AOT contract preserved — IsAotCompatible=true, TrimMode=full, and
the promoted IL warnings all keep holding. Verified: build is clean
on net9.0 and net10.0; `dotnet run` exits 0 with the expected
"Marten AOT smoke OK" line on both frameworks.

Closes #4486. Closes #4471.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 72e8b65 into master May 19, 2026
6 checks passed
@jeremydmiller jeremydmiller deleted the feat/4486-aotsmoke-dispatcher-coverage branch May 19, 2026 12:04
@jeremydmiller jeremydmiller mentioned this pull request May 19, 2026
38 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant