perf(templateengine): memoise CollisionDetector's composed-module walk per check (review 2026-10-21 04/P8b)

This commit is contained in:
Joseph Doherty
2026-10-22 06:30:27 -04:00
parent 52aab37299
commit 0d4f7d33b9
3 changed files with 153 additions and 26 deletions
@@ -258,4 +258,55 @@ public class CollisionDetectorTests
var collisions = CollisionDetector.DetectCollisions(a, all);
Assert.Empty(collisions);
}
// ========================================================================
// Review 2026-10-21 04/P8(b): a module reached through several slots is walked once
// ========================================================================
/// <summary>
/// Verifies that a module composed into several slots (a diamond) has its subtree
/// walked once per collision check — every further slot reuses the memoised member
/// list under its own prefix — while still producing every canonical name.
/// </summary>
[Fact]
public void DetectCollisions_DiamondComposition_WalksSharedModuleOnce()
{
var leaf = new Template("Leaf") { Id = 3 };
leaf.Attributes.Add(new TemplateAttribute("V") { Id = 30, TemplateId = 3, DataType = DataType.Float });
var mid = new Template("Mid") { Id = 2 };
var midCompositions = new CountingCollection<TemplateComposition>
{
new TemplateComposition("x") { Id = 20, TemplateId = 2, ComposedTemplateId = 3 },
new TemplateComposition("y") { Id = 21, TemplateId = 2, ComposedTemplateId = 3 },
};
mid.Compositions = midCompositions;
var root = new Template("Root") { Id = 1 };
root.Compositions.Add(new TemplateComposition("a") { Id = 10, TemplateId = 1, ComposedTemplateId = 2 });
root.Compositions.Add(new TemplateComposition("b") { Id = 11, TemplateId = 1, ComposedTemplateId = 2 });
root.Compositions.Add(new TemplateComposition("c") { Id = 12, TemplateId = 1, ComposedTemplateId = 2 });
// A genuine collision reachable only through the third slot of the shared module.
root.Attributes.Add(new TemplateAttribute("c.y.V") { Id = 40, TemplateId = 1, DataType = DataType.Float });
midCompositions.Enumerations = 0;
var collisions = CollisionDetector.DetectCollisions(root, new List<Template> { root, mid, leaf });
Assert.Equal(1, midCompositions.Enumerations); // pre-fix: 3 (once per slot)
var collision = Assert.Single(collisions);
Assert.Contains("'c.y.V'", collision);
Assert.Contains("module 'c.y'", collision);
}
/// <summary>An <see cref="ICollection{T}"/> that counts how often it is enumerated.</summary>
private sealed class CountingCollection<T> : List<T>, ICollection<T>
{
public int Enumerations { get; set; }
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
Enumerations++;
return GetEnumerator();
}
}
}