Skip to content

Commit 9463574

Browse files
committed
refactor(generator): overhaul incremental pipeline and core models
* add EmbeddedAttribute generation and IsExternalInit shim * replace core symbol-heavy logic with value-based models and window/matching refactors * rework MethodOverloadsGenerator pipeline, input building, and option extraction * remove legacy collection core file and adjust emission/helpers/matching logic
1 parent 0342311 commit 9463574

14 files changed

+1920
-1228
lines changed

docs/source-generator-guide.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Source Generator Performance Guide (LLM-Optimized)
2+
3+
This is an **executive checklist** format optimized for LLM parsing: short, deterministic rules with explicit acceptance criteria (DoD). Each rule is traced to the local design docs with inline citations.
4+
5+
Sources:
6+
- `docs/incremental-generators.md`
7+
- `docs/incremental-generators.cookbook.md`
8+
9+
---
10+
11+
## Mental Model (Short)
12+
13+
1) **Pipelines are immutable, cached DAGs**. You describe transformations once in `Initialize`. The host executes them only when inputs change; every transformation is a cache boundary. (source: `docs/incremental-generators.md` — Pipeline based execution, Caching)
14+
15+
2) **Equality drives caching**. Only value-equatable models allow reuse; symbols/syntax nodes defeat caching and can retain memory. (source: `docs/incremental-generators.md` — Caching, Comparing Items; `docs/incremental-generators.cookbook.md` — Pipeline model design)
16+
17+
3) **Syntactic filtering first, semantic work later**. Predicates run only on changed trees; transforms rerun for all matched nodes. Filter hard before semantic work. (source: `docs/incremental-generators.md` — SyntaxValueProvider, CreateSyntaxProvider)
18+
19+
4) **Attribute-driven opt-in is the fastest path**. `ForAttributeWithMetadataName` (FAWMN) uses attribute indexing to skip most syntax nodes. (source: `docs/incremental-generators.md` — FAWMN; `docs/incremental-generators.cookbook.md` — Use ForAttributeWithMetadataName)
20+
21+
5) **Additive and deterministic outputs only**. Generators add source; no rewriting; determinism is required for cache correctness. (source: `docs/incremental-generators.cookbook.md` — Proposal, Out of scope designs; `docs/incremental-generators.md` — Caching)
22+
23+
---
24+
25+
## Rulebook with DoD (Acceptance Criteria)
26+
27+
### A) Pipeline Construction & Caching
28+
29+
**Rule A1 — Define all work in `Initialize`.**
30+
DoD:
31+
- All generator logic is expressed as pipeline transformations.
32+
- No ad-hoc compilation scanning outside pipeline lambdas.
33+
Source: `docs/incremental-generators.md` — Pipeline based execution.
34+
35+
**Rule A2 — Split work into multiple transforms.**
36+
DoD:
37+
- The pipeline has multiple checkpoints (not a single monolithic transform).
38+
- Each stage emits a smaller, more comparable value.
39+
Source: `docs/incremental-generators.md` — Authoring a cache friendly generator.
40+
41+
**Rule A3 — Extract minimal values before `Combine`.**
42+
DoD:
43+
- `Combine` operates on extracted scalars (e.g., assembly name) instead of full `Compilation`.
44+
- No frequent inputs are combined unless required.
45+
Source: `docs/incremental-generators.md` — Consider the order of combines.
46+
47+
**Rule A4 — Use `WithComparer` for custom equality.**
48+
DoD:
49+
- Any non-trivial model that lacks natural equality has an explicit comparer.
50+
Source: `docs/incremental-generators.md` — Comparing Items.
51+
52+
### B) Models & Equality
53+
54+
**Rule B1 — Do not store `ISymbol` in models.**
55+
DoD:
56+
- Models contain no `ISymbol` or derived types.
57+
- Symbol info is projected to value types (strings, ints, etc.).
58+
Source: `docs/incremental-generators.cookbook.md` — Pipeline model design.
59+
60+
**Rule B2 — Remove `SyntaxNode`/`Location` ASAP.**
61+
DoD:
62+
- `SyntaxNode` is only used in the transform step when required.
63+
- No model retains `SyntaxNode`/`Location` beyond transformation.
64+
Source: `docs/incremental-generators.cookbook.md` — Pipeline model design; `docs/incremental-generators.md` — SyntaxValueProvider.
65+
66+
**Rule B3 — Use value-equatable models.**
67+
DoD:
68+
- Models are `record` types or implement value equality.
69+
Source: `docs/incremental-generators.cookbook.md` — Pipeline model design.
70+
71+
**Rule B4 — Collections must be value-equatable.**
72+
DoD:
73+
- Arrays/lists are wrapped to provide value equality (or replaced by value-eq types).
74+
Source: `docs/incremental-generators.cookbook.md` — Pipeline model design.
75+
76+
### C) Syntax & Semantics
77+
78+
**Rule C1 — Prefer FAWMN for attribute-driven scenarios.**
79+
DoD:
80+
- Attribute-driven generators use `ForAttributeWithMetadataName`.
81+
Source: `docs/incremental-generators.md` — FAWMN; `docs/incremental-generators.cookbook.md` — Use ForAttributeWithMetadataName.
82+
83+
**Rule C2 — Keep predicates cheap and syntactic.**
84+
DoD:
85+
- `predicate` is syntactic-only (e.g., `node is X`) or `true`.
86+
Source: `docs/incremental-generators.md` — FAWMN, CreateSyntaxProvider.
87+
88+
**Rule C3 — Semantic work only in `transform`.**
89+
DoD:
90+
- Semantic APIs run only in `transform`.
91+
- Semantic outputs are immediately projected to value models.
92+
Source: `docs/incremental-generators.md` — CreateSyntaxProvider.
93+
94+
### D) Output Generation
95+
96+
**Rule D1 — Generate text, not syntax trees.**
97+
DoD:
98+
- Source is built via `StringBuilder`/indented writer.
99+
- No `NormalizeWhitespace` or `SyntaxFactory` generation.
100+
Source: `docs/incremental-generators.cookbook.md` — Use an indented text writer, not SyntaxNodes, for generation.
101+
102+
**Rule D2 — Deterministic, additive output only.**
103+
DoD:
104+
- Outputs are identical for identical inputs.
105+
- No mutation of user code.
106+
Source: `docs/incremental-generators.cookbook.md` — Proposal; Out of scope designs; `docs/incremental-generators.md` — Caching.
107+
108+
**Rule D3 — Deterministic hint names.**
109+
DoD:
110+
- `AddSource` hint names are deterministic.
111+
- Hint names avoid collisions (include path segments if needed).
112+
Source: `docs/incremental-generators.cookbook.md` — Additional file transformation (note on hint names).
113+
114+
**Rule D4 — Use correct output registration.**
115+
DoD:
116+
- User-visible code uses `RegisterSourceOutput`.
117+
- Non-semantic code uses `RegisterImplementationSourceOutput`.
118+
Source: `docs/incremental-generators.md` — Outputting values.
119+
120+
**Rule D5 — Post-init is only for fixed code.**
121+
DoD:
122+
- `RegisterPostInitializationOutput` emits only fixed declarations (e.g., marker attributes).
123+
- No user-input-dependent code in post-init.
124+
Source: `docs/incremental-generators.md` — RegisterPostInitializationOutput; `docs/incremental-generators.cookbook.md` — Generated class.
125+
126+
### E) Attribute Marker Patterns
127+
128+
**Rule E1 — Embed marker attributes.**
129+
DoD:
130+
- Generated marker attributes have `[Microsoft.CodeAnalysis.Embedded]`.
131+
- `AddEmbeddedAttributeDefinition()` is invoked in post-init.
132+
Source: `docs/incremental-generators.cookbook.md` — Put Microsoft.CodeAnalysis.EmbeddedAttribute on generated marker types.
133+
134+
**Rule E2 — Avoid indirect marker scans.**
135+
DoD:
136+
- No scans for indirect interface/base type inheritance.
137+
- Marker attributes are sealed and used directly.
138+
Source: `docs/incremental-generators.cookbook.md` — Do not scan for types that indirectly implement interfaces...
139+
140+
### F) Additional Files & Configuration
141+
142+
**Rule F1 — Filter additional files early.**
143+
DoD:
144+
- `AdditionalTextsProvider.Where(...)` filters by extension/path first.
145+
- `GetText(cancellationToken)` is called only after filtering.
146+
Source: `docs/incremental-generators.cookbook.md` — Additional file transformation.
147+
148+
**Rule F2 — Use analyzer config provider for options.**
149+
DoD:
150+
- Global options are read via `AnalyzerConfigOptionsProvider`.
151+
- Per-file options use `GetOptions(additionalText)`.
152+
Source: `docs/incremental-generators.cookbook.md` — Access Analyzer Config properties.
153+
154+
**Rule F3 — MSBuild properties/metadata must be compiler-visible.**
155+
DoD:
156+
- Required properties are listed in `CompilerVisibleProperty`.
157+
- Per-item metadata is listed in `CompilerVisibleItemMetadata`.
158+
Source: `docs/incremental-generators.cookbook.md` — Consume MSBuild properties and metadata.
159+
160+
### G) Cancellation & Host Responsiveness
161+
162+
**Rule G1 — Always respect cancellation.**
163+
DoD:
164+
- All Roslyn calls accept and receive `CancellationToken`.
165+
- Long loops call `ThrowIfCancellationRequested`.
166+
Source: `docs/incremental-generators.md` — Handling Cancellation.
167+
168+
---
169+
170+
## Deep Dive: ForAttributeWithMetadataName (FAWMN)
171+
172+
**What it does**
173+
Efficiently finds attributed syntax nodes via a metadata-name index and avoids realizing most nodes.
174+
Source: `docs/incremental-generators.md` — FAWMN.
175+
176+
**Why it’s faster**
177+
- Internal attribute index eliminates most nodes without semantic checks.
178+
- The index can have false positives, not false negatives; it’s safe to skip most nodes.
179+
Source: `docs/incremental-generators.md` — FAWMN.
180+
181+
**Correct usage**
182+
**DoD:**
183+
- `fullyQualifiedMetadataName` uses metadata name only (no assembly), e.g., `My.Namespace.MyAttribute` or ``My.Namespace.MyAttribute`1``.
184+
- `predicate` is syntactic and cheap.
185+
- `transform` does semantic work and returns a value model.
186+
Source: `docs/incremental-generators.md` — FAWMN; `docs/incremental-generators.cookbook.md` — Use ForAttributeWithMetadataName.
187+
188+
---
189+
190+
## Quick DoD Checklist (copy/paste)
191+
192+
- Pipeline defined only in `Initialize`.
193+
- Multiple transform steps; no monolithic work.
194+
- Models are value-equatable (records or explicit equality).
195+
- No `ISymbol` or `SyntaxNode` in models past transform step.
196+
- Attribute-driven generators use FAWMN.
197+
- `fullyQualifiedMetadataName` is metadata name only.
198+
- Predicate is cheap and syntactic; semantic work only in `transform`.
199+
- Generator output is additive and deterministic.
200+
- Generated code uses text/indented writer (no syntax trees).
201+
- Marker attributes are `[Embedded]` and added in post-init.
202+
- No indirect base/interface scans or attribute inheritance hacks.
203+
- `Combine` uses minimal extracted values.
204+
- Cancellation tokens are passed and checked.
205+
- Analyzer config via `AnalyzerConfigOptionsProvider`.
206+
- MSBuild props/metadata exposed via compiler-visible items.
207+

src/Tenekon.MethodOverloads.SourceGenerator/GeneratorAttributesSource.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ namespace Tenekon.MethodOverloads.SourceGenerator;
22

33
internal static class GeneratorAttributesSource
44
{
5-
public const string GenerateOverloadsAttribute = """
5+
public const string GenerateOverloadsAttribute = """
66
#nullable enable
77
namespace Tenekon.MethodOverloads.SourceGenerator;
88
99
[global::System.AttributeUsage(global::System.AttributeTargets.Method)]
10+
[global::Microsoft.CodeAnalysis.Embedded]
1011
public sealed class GenerateOverloadsAttribute : global::System.Attribute
1112
{
1213
public GenerateOverloadsAttribute()
@@ -43,11 +44,12 @@ public GenerateOverloadsAttribute(string beginEnd)
4344
}
4445
""";
4546

46-
public const string GenerateMethodOverloadsAttribute = """
47+
public const string GenerateMethodOverloadsAttribute = """
4748
#nullable enable
4849
namespace Tenekon.MethodOverloads.SourceGenerator;
4950
5051
[global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct | global::System.AttributeTargets.Interface)]
52+
[global::Microsoft.CodeAnalysis.Embedded]
5153
public sealed class GenerateMethodOverloadsAttribute : global::System.Attribute
5254
{
5355
public global::System.Type[]? Matchers { get; set; }
@@ -79,6 +81,7 @@ public enum OverloadVisibility
7981
}
8082
8183
[global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct | global::System.AttributeTargets.Interface | global::System.AttributeTargets.Method)]
84+
[global::Microsoft.CodeAnalysis.Embedded]
8285
public sealed class OverloadGenerationOptionsAttribute : global::System.Attribute
8386
{
8487
public RangeAnchorMatchMode RangeAnchorMatchMode { get; set; }
@@ -92,12 +95,26 @@ public sealed class OverloadGenerationOptionsAttribute : global::System.Attribut
9295
namespace Tenekon.MethodOverloads.SourceGenerator;
9396
9497
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
98+
[global::Microsoft.CodeAnalysis.Embedded]
9599
internal sealed class MatcherUsageAttribute : global::System.Attribute
96100
{
97101
public MatcherUsageAttribute(global::System.Type matcherType, string methodName)
98102
{
99103
}
100104
}
105+
""";
106+
107+
public const string EmbeddedAttribute = """
108+
#nullable enable
109+
namespace Microsoft.CodeAnalysis;
110+
111+
[global::System.AttributeUsage(global::System.AttributeTargets.All, Inherited = false, AllowMultiple = false)]
112+
internal sealed class EmbeddedAttribute : global::System.Attribute
113+
{
114+
public EmbeddedAttribute()
115+
{
116+
}
117+
}
101118
""";
102119
}
103120

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace System.Runtime.CompilerServices;
2+
3+
internal static class IsExternalInit
4+
{
5+
}

0 commit comments

Comments
 (0)