Skip to content

GH-990: source generator infers a handler's emitted events into an assembly manifest - #991

Merged
jeremydmiller merged 1 commit into
mainfrom
GH-990-emitted-events-manifest
Oct 9, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
GH-990-emitted-events-manifest

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #990.

Summary

Pass 2 bug fix. The syntax filter now admits any method with an attributed parameter, and the IRefersToAggregate check decides. Before this, [WriteModel] / [ReadModel] / [DcbModel] and user aliases never reached the semantic check.

New JasperFx.Events types

  • ICarriesEvents: a marker interface for return types that carry events without naming them.
  • EmittedEventsAttribute(Type handlerType, string methodName, params Type[] eventTypes): assembly-level, and allows multiple applications so a double-loaded analyzer doesn't fail the build.

New generator pass (EmittedEventsAnalyzer.cs)

  • Candidates: a method taking an IEventStream<T> parameter or an IRefersToAggregate-attributed parameter, or returning an ICarriesEvents type. That includes tuple elements and looks inside Task<T>/ValueTask<T>.
  • Collected from: AppendOne/AppendMany; constructors and static factories returning an ICarriesEvents type; collection initializers; Add-style calls on an ICarriesEvents value.
  • Event slots: only arguments passed to parameters typed object, object[] or IEnumerable<object> count, so a Guid or string stream id is never mistaken for an event, wherever it sits.
  • Skipped silently: object-typed values, anonymous types, and types a typeof at assembly scope can't name.
  • Output: one JasperFxEmittedEvents.g.cs, emitted only when the compilation can see EmittedEventsAttribute. Each method's result is compared by value, so unrelated edits stay cached.

Decisions worth a look

  • Overloads: the manifest is keyed on method name, so overloads of one method merge into one entry.
  • Name matching: methods that add to an ICarriesEvents value are matched by name (Add, AddRange, Insert, InsertRange, Append, AppendOne, AppendMany). Matching on parameter shape alone would also pick up Contains(object).
  • Abstract base types: a value typed as an abstract base event is recorded under that base type. Only object is excluded.

Tests

EmittedEventsManifestTests.cs covers:

  • every case listed in the issue;
  • no manifest when JasperFx.Events isn't referenced;
  • incrementality, using Roslyn's step tracking;
  • the pass 2 regression, which fails against the old name filter.

All 96 source generator tests pass, and jasperfx.slnx builds with 0 errors.

Wolverine follow-up: JasperFx/wolverine#4914.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8KxC18Q51z5w4u6YARwe4

…sembly manifest

- New JasperFx.Events.ICarriesEvents marker and EmittedEventsAttribute (assembly, AllowMultiple).
- New generator pipeline: a method that takes an IEventStream<T> or an IRefersToAggregate
  parameter, or returns an ICarriesEvents type (incl. tuple / Task<T>), has its body read for
  AppendOne/AppendMany, ICarriesEvents factory/ctor args, collection initializers and Add calls.
  Only object-typed parameter slots count as events; object-typed values are skipped.
  Per-method results are value-equatable, so unrelated edits stay cached.
- Pass 2's syntax filter no longer requires "Aggregate" in the attribute name; the
  IRefersToAggregate check decides, so [WriteModel]/[ReadModel]/[DcbModel] are analyzed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8KxC18Q51z5w4u6YARwe4
@jeremydmiller
jeremydmiller merged commit aa5bf79 into main Oct 9, 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

1 participant