Repository navigation
Description
Package
CodoMetis.TypeKit.Generators
Version
2.3.0, and every release that generates comparison
How does it fail?
Silently in EF Core, and with a crash (stack overflow) on the first < or CompareTo(object)
What you ran
A base record declares CompareTo(TSelf), the comparison seam, instead of the value object:
public abstract record ReversedBase<TSelf> where TSelf : ReversedBase<TSelf>
{
public int CompareTo(TSelf? other) => other is null ? 1 : Raw(other).CompareTo(Raw((TSelf)this));
private static int Raw(TSelf value) => ((IValueObject<Priority, int>)(object)value).Value;
}
public sealed partial record Priority : ReversedBase<Priority>, IValue<int>;
new[] { Priority.From(1), Priority.From(2) }.Order(); // [2, 1]
db.Jobs.OrderBy(j => j.Priority); // SQLite: [1, 2], not refused
Priority.From(1) < Priority.From(2); // stack overflow
((IComparable)Priority.From(1)).CompareTo(Priority.From(2)); // stack overflowWhat you got
Measured on release/2.4.0 at 97c87c1, 2026-10-30:
ValueObjectComparableAspect.DeclaresCompareToreads the value object's own methods, so the inherited seam is not seen: no[HandWrittenOrdering], and an EF Core query orders by the column ([1, 2]) where memory orders[2, 1].- The generated
CompareTo(TSelf)is not introduced (OverrideStrategy.Ignoresees the inherited one), and the generated operators andCompareTo(object)callleft.CompareTo(right). C# prefers the method declared on the more derived type, the generatedCompareTo(object), over the base'sCompareTo(Priority?), soCompareTo(object)calls itself until the stack overflows and the process ends.
What you expected
CMTK1008 naming the inherited member: the seam is declared on the value object (decision 28), so declare CompareTo(Priority) there, calling the base's if that is the order you mean. Reported from an outside review of #193.
Environment
.NET SDK 10.0.4xx, Metalama 2026.1; any consumer.
Scope as fixed in #203
The class is a seam a base record declares, not only CompareTo(TSelf). Measured on #201's branch: a base's TryFrom(T) and Revalidate() were the value object's the same way (the generated ones, which run Create, were not introduced), and a base's CompareTo(object) failed the aspect (LAMA0500). All four are refused now (CMTK1008 for CompareTo, CMTK1011 for the others, which say to declare the seam on the value object). A base's FromKnownGood(T) is hidden by the generated one, whose signature takes the caller's expression too, and stays allowed. Decision 121 records refusing over taking a base's as the seam; a base's sealed ToString() stays the seam.
Activity
fix: refuse a base record's public generated member and name a public…
fix: refuse a base record's public generated member and name a public…
CaffeinatedCoder commentedon Oct 30, 2026
Fixed by #203, merged into
release/2.3.1as fb11acf; ships in 2.3.1.