-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Context
The Emitter (SourceGenerator.Emitter.cs) is responsible for generating type-specific C# code for parsing and formatting composite key properties. It uses switch statements on ParseType and FormatType enums to select the correct code generation logic for each supported type (e.g., parsing a Guid vs a string vs an enum).
Currently, these switch statements are duplicated across 4 separate sites in the Emitter, totalling approximately 129 lines of type-dispatching code:
| # | Method / Site | Lines | Purpose |
|---|---|---|---|
| 1 | WriteParsePropertiesImplementation switch |
Emitter:467-505 (39 lines) | Emit parse code for a single property value |
| 2 | WriteRepeatingItemParse switch |
Emitter:593-629 (37 lines) | Emit parse code for one item within a repeating/collection property |
| 3 | WriteFormatMethodBodyForKeyParts optimised string.Create path |
Emitter:722-753 (32 lines) | Emit span-based formatting using string.Create with exact-length buffer |
| 4 | WriteFormatMethodBodyForKeyParts interpolation path |
Emitter:819-839 (21 lines) | Emit formatting using DefaultInterpolatedStringHandler (fallback path) |
The ParseType enum defines the parse strategies: Guid, String, Enum, SpanParsable.
The FormatType enum defines the format strategies: Guid, String, Enum, SpanFormattable.
Problem
Adding a new supported type (e.g., DateOnly, int, or a custom type) currently requires modifying all 4 switch sites in the Emitter. Each site has subtly different parameters and code shape, making this error-prone and hard to review. The type-specific logic is scattered rather than cohesive.
Solution
Introduce per-type strategy objects that encapsulate all type-specific code generation behind a common interface. This reduces the "add a new type" change from modifying 4 Emitter switch sites to creating 1 new strategy class. The Emitter dispatch sites collapse from switch statements into single strategy method calls.
All strategies are stateless singletons (static readonly instances) accessed via a static dictionary, ensuring zero allocation overhead at generation time.
Blocked by
Tasks
Define ITypeStrategy interface
- Create
internal interface ITypeStrategywith four methods corresponding to the 4 switch sites:EmitSingleParse(SourceWriter, inputVar, outputVar, PropertyKeyPart, shouldThrow)— replaces site 1 (single-value parse code)EmitRepeatingItemParse(SourceWriter, inputVar, itemVar, listVar, PropertyKeyPart, shouldThrow)— replaces site 2 (collection item parse code)TryEmitSpanFormat(SourceWriter, PropertyKeyPart, positionVar, invariantFormatting) → bool— replaces site 3 (optimisedstring.Createpath; returns false if type doesn't support this path)EmitInterpolationFormat(SourceWriter, PropertyKeyPart)— replaces site 4 (DefaultInterpolatedStringHandlerpath)
Implement concrete strategies
- Implement four singleton strategies corresponding to each
ParseType/FormatTypepair:GuidTypeStrategy—Guid.TryParseExactfor parse,ISpanFormattable.TryFormatfor span format,AppendFormattedwith format string for interpolationStringTypeStrategy— length-check +ToString()for parse,CopyTofor span format,AppendFormattedfor interpolationEnumTypeStrategy— delegates to the generated{EnumName}Helper.TryParse/TryFormat/GetFormattedLengthmethods for parse and format. Implementation note: this strategy will need to call intoEnumGenerationHelper.cs, which generates optimised per-enum helper classes (TryParse,TryFormat,GetFormattedLength) with array-lookup fast paths for sequential-from-zero enums. The strategy emits calls to these helpers rather than containing enum logic directly.SpanParsableTypeStrategy—T.TryParsefor parse,AppendFormattedfor interpolation (span format returns false, falling back to interpolation path)
- Each strategy encapsulates the type-specific code currently spread across the 4 switch sites listed above
- Strategies accessed via a static dictionary keyed by
ParseType(e.g.,Dictionary<ParseType, ITypeStrategy>) - All strategies are stateless singletons (
static readonly) for zero allocation overhead — no per-call or per-property allocation
Refactor Emitter to use strategies
- Replace the 4 switch statement sites with strategy dispatch:
var strategy = TypeStrategies[propertyKeyPart.ParseType]; strategy.EmitSingleParse(writer, inputVar, outputVar, propertyKeyPart, shouldThrow);
- Single/repeating distinction handled by checking
CollectionSemanticson thePropertyKeyPart:if (propertyKeyPart.CollectionSemantics is not null) strategy.EmitRepeatingItemParse(...) else strategy.EmitSingleParse(...)
Note: After refactor: Unify model layer and type resolution #43 (the unified model refactoring),
PropertyKeyPartcarries aCollectionSemantics?field —nullfor single properties, non-null for repeating/collection properties. This replaces the current separateRepeatingPropertyKeyPartsubclass and is what the strategy dispatch uses to determine which emit method to call. - The optimised
string.Createpath (site 3) usesTryEmitSpanFormat— if the strategy returnsfalse, the Emitter falls back to the interpolation path automatically
Verification
- All existing snapshot tests pass with identical generated output — this is a pure refactoring with no behavioural change
- Run benchmarks to confirm strategy dispatch does not introduce allocations or measurable overhead vs the current inline switches
- Verify that adding a hypothetical new type requires only creating a new
ITypeStrategyimplementation and registering it in the dictionary (no Emitter switch modifications)