Repository navigation
An interface's explicit implementation of a generated interface member is replaced without a diagnostic #191
Description
Package
CodoMetis.TypeKit.Generators
Version
2.3.0, and every release that generates these members
How does it fail?
Silently. It compiles or answers, and does less than it appears to
What you ran
An interface can implement a member of an interface the generators implement, explicitly, and every type that declares it reaches that implementation until it declares a public member of its own:
public interface IBounded<TSelf> : IMinMaxValue<TSelf> where TSelf : IBounded<TSelf>
{
static TSelf IMinMaxValue<TSelf>.MaxValue => throw new NotSupportedException();
static TSelf IMinMaxValue<TSelf>.MinValue => throw new NotSupportedException();
}
public interface IFormats : IFormattable
{
string IFormattable.ToString(string? format, IFormatProvider? provider) => "interface";
}
public readonly partial record struct Bounded : IValue<int>, IBounded<Bounded>;
public readonly partial record struct Formats : IValue<int>, IFormats;
static T Max<T>() where T : IMinMaxValue<T> => T.MaxValue;
Max<Bounded>(); // the interface's, which throws
((IFormattable)Formats.From(1)).ToString(null, null); // "interface"What you got
Measured on feat/value-object-arithmetic (2.4.0 in progress), the same since these members are generated: Max<Bounded>() returned the generated MaxValue, and the IFormattable call printed 1. The generated public member replaced the interface's explicit implementation, with no diagnostic.
ValueObjectDeclaration.ExplicitImplementationsOf, which reports an explicit implementation of a generated interface's member as CMTK1011, reads the value object and its base records, never the interfaces it implements.
What you expected
The same answer as an explicit implementation on the value object itself: CMTK1011, naming the member and the interface that implements it. That reports more under an Error in a minor, so the fix records the exception to decision 34, as #159 did.
The input class is every interface the generators implement whose member an interface can implement explicitly: IFormattable, ISpanFormattable, IUtf8SpanFormattable, IParsable, ISpanParsable, IUtf8SpanParsable, IMinMaxValue, IComparable, IComparable<T>, IComparisonOperators, IConvertible. Only IMinMaxValue and IFormattable are measured so far.
Found while fixing the same gap in the arithmetic operators of #184, where an interface's explicit implementation is kept (decision 121), from an outside review of #188.
Environment
.NET SDK 10.0.4xx, Metalama 2026.1; any consumer.
Scope as fixed (#193)
Measured on release/2.4.0 at 97c87c1, 2026-10-30: all eleven interfaces of the input class were replaced as described, two interfaces down and through a base record's interface too. The fix found more of the same kind:
- Another assembly's types. Metalama lists no private member of a type in another assembly, and an explicit implementation is private. An interface from a referenced project was missed, and so was a base record from one, which the rule has missed since phase 11 (its
IFormattablereplaced, itsIMinMaxValuekept answering). The check now usesTryFindImplementationForInterfaceMember, which sees them. IConvertibledeclared through an interface crashed the aspect (LAMA0041). It is CMTK1011 now.- Closed sets. An interface's explicit
IClosedSet<TSelf>.NameorTryParsewas replaced without a word, andIParsable<TSelf>through an interface crashed the aspect (LAMA0512). Both are CMTK1011.
Not refused, each with a test: an interface's ==, which the record's own replaces in any record; .NET's INumberBase<TSelf>, whose UTF-8 bridges are the only such members in .NET's interfaces; and interfaces the generators don't implement (a ToString() seam, a Uri's ordering). The comparison interfaces answer CMTK1008, as on the value object itself. ExplicitlyImplementsEquatableBetween has no gap. The exception to decision 34 is recorded as decision 121 on release/2.3.1 (124 in #193 on release/2.4.0, renumbered so 2.3.1's records run without a gap).
IValueObject<,> declared through a user interface still fails with LAMA0512: #192.
A closed set over a base class of another project that implements IClosedSet<TSelf> explicitly, reported by a second outside review (All 2 vs T.All 0, names and parsing apart through the interface, EF Core storing the base's name), is the same case: refused since 466c541, and held by SetOverLibraryBase in 38b316f. The same review's base-record findings are #195 and #197, fixed in #193 as well; #194 and #196 are separate.
Moved to 2.3.1 (2026-10-30)
The defect shipped in 2.3.0, so its fix ships in the patch 2.3.1 (epic #198). The fix is cherry-picked onto fix/interface-explicit-implementation-refused-2-3-1 (from build/open-2-3-1, #200); #193's later commits for #195 and #197 follow it there, and #193 closes in favour of that branch's PR.
Activity
fix: refuse a base record's public generated member and name a public…
docs: renumber the arithmetic decision record from 0121 to 0122
fix: refuse a base record's public generated member and name a public…
CaffeinatedCoder commentedon Oct 30, 2026
Fixed by #201, merged into
release/2.3.1as 8fac39b; ships in 2.3.1.