perf(sourcegen): real InterfaceCache + single-pass attribute classification (original) (raw)
Summary
TUnit.Core.SourceGenerator has a no-op "cache" and repeated LINQ passes over the same attribute arrays per test method. Build-time, but affects IDE responsiveness on every keystroke that touches a test. Found during a hot-path modernization sweep.
Findings
1. InterfaceCache caches nothing — HIGH
TUnit.Core.SourceGenerator/Helpers/InterfaceCache.cs:15-84
Despite the name, ImplementsInterface/GetGenericInterface/IsAsyncEnumerable/IsEnumerable all walk type.AllInterfaces (allocates ImmutableArray) on every call, plus .Any(lambda) closures. Called per-attribute per test method — same shared attribute symbols re-walked repeatedly.
Add a ConcurrentDictionary<ITypeSymbol, ImmutableHashSet<string>> keyed on SymbolEqualityComparer.Default. Note: clear between compilations / key on Compilation to avoid cross-compilation contamination in long IDE sessions.
2. TestMetadataGenerator.CollectConcreteInstantiations — 6-8 LINQ passes over same MethodAttributes — HIGH
TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs:623-790
The same MethodAttributes array is .Where(...).ToArray()-scanned 6-8 times (Arguments, MethodDataSource ×2, data-source, generic ×2). Replace with a single classification foreach partitioning into named buckets (null-init lists).
3. MethodExtensions.GetTestAttribute lambda alloc per method — LOW
TUnit.Core.SourceGenerator/Extensions/MethodExtensions.cs:22 — FirstOrDefault(lambda) per test method. foreach + early return.
4. CodeWriter.GetIndentation Enumerable.Repeat — LOW
TUnit.Core.SourceGenerator/CodeWriter.cs:51 — cache-miss path only (static cache warms after ~9 calls). new string(' ', n). Cosmetic.
Notes
GroupMethodsByClassafter.Collect()re-runs on every test edit — acknowledged fan-in limitation; only worth reducing internal LINQ allocs, not restructurable.- Verified: pipeline models that store
ISymbolare post-.Collect()RegisterSourceOutputconsumers, not incremental nodes — caching not broken.
Acceptance
InterfaceCacheactually caches; snapshot tests unchanged- Single-pass attribute classification; generated output byte-identical (verified snapshots)
- No
.received.txtcommitted