Description
Package
CodoMetis.TypeKit.Generators
Version
2.3.0, and every release that generates equality over a base record
How does it fail?
Silently. It compiles or answers, and does less than it appears to
What you ran
A value object over a base record declares an Equals for one of its base types:
public abstract record DeepBase;
public abstract record MiddleBase : DeepBase;
public sealed partial record Tag : MiddleBase, IValue<string>
{
public bool Equals(DeepBase? other) => ReferenceEquals(this, other);
}
Tag.From("a") == Tag.From("a"); // true
new HashSet<DeepBase> { Tag.From("a"), Tag.From("a") }.Count; // 2
new HashSet<Tag> { Tag.From("a"), Tag.From("a") }.Count; // 1What you got
Measured on release/2.4.0 at 97c87c1, 2026-10-30. ValueObjectDeclaration.EqualityDeclaredByHand refuses Equals(TSelf), GetHashCode() and an explicit IEquatable<TSelf>.Equals, and reads no Equals(TBase). The hand-written one implements IEquatable<DeepBase> (re-listed through IReimplementedEquatable<DeepBase>), and ValueObjectContractAspect.IntroduceEquality skips its own new Equals(TBase) when the value object declares one, so a collection of the base compares by reference while the value object's equality is its wrapped value's.
What you expected
CMTK1011, as for a hand-written Equals(TSelf): a value object's equality is its wrapped value's. Reported from an outside review of #193.
Environment
.NET SDK 10.0.4xx, Metalama 2026.1; any consumer.
Activity
CaffeinatedCoder commentedon Oct 30, 2026
Fixed by #202, merged into
release/2.3.1as 70e3d8b; ships in 2.3.1.