Repository navigation
A closed set over a base class that declares a generated member fails with a Metalama error, or its ToString() is replaced #205
Description
Found by a sweep of release/2.3.1 @ dbdd44e (lens: sibling-parity, generators) for the defect class of #191-#197; reproduced in a scratch build-outcome test.
Package
CodoMetis.TypeKit.Generators
Version
2.3.0 (the same code in v2.3.0), and release/2.3.1 @ dbdd44e
How does it fail?
Loudly but naming nothing you wrote (LAMA0500, LAMA0502, LAMA0512), and silently for an unsealed ToString()
Problem
ClosedSetTypes.GeneratedMembersDeclaredByHand (src/CodoMetis.TypeKit.Generators/ClosedSetTypes.cs:203-216) reads Parse, TryParse and Match from set.Methods, and IParsable<TSelf> from set.ImplementedInterfaces; ClosedSetTypes.DeclaresToString (:237-238) reads set.Methods. None reads a base class. The value-object side does since #197 and #194 (ValueObjectDeclaration.SelfAndBases, InheritedToString, UnsealedInheritedToString), and a closed set's base Name is already refused through ImplementationsOf.
Scenario
Base classes in the same project as the set:
public abstract class SealedToStringSetBase { public sealed override string ToString() => "base"; }
public sealed partial class OverSealedToString : SealedToStringSetBase, IClosedSet<OverSealedToString> { public static readonly OverSealedToString A = new(); }
public abstract class ParsingSetBase<TSelf> { public static TSelf Parse(string text) => throw new NotSupportedException(); }
public sealed partial class OverParsingBase : ParsingSetBase<OverParsingBase>, IClosedSet<OverParsingBase> { public static readonly OverParsingBase A = new(); }
public abstract class ParsableSetBase<TSelf> : IParsable<TSelf> where TSelf : ParsableSetBase<TSelf>
{
static TSelf IParsable<TSelf>.Parse(string s, IFormatProvider? p) => throw new NotSupportedException();
static bool IParsable<TSelf>.TryParse(string? s, IFormatProvider? p, out TSelf r) { r = null!; return false; }
}
public sealed partial class OverParsableBase : ParsableSetBase<OverParsableBase>, IClosedSet<OverParsableBase> { public static readonly OverParsableBase A = new(); }
public abstract class OpenToStringSetBase { public override string ToString() => "***"; }
public sealed partial class OverOpenToString : OpenToStringSetBase, IClosedSet<OverOpenToString> { public static readonly OverOpenToString A = new(); }Measured in BuildOutcomeTests @ dbdd44e:
OverSealedToString: LAMA0502 "cannot introduce member 'OverSealedToString.ToString()' ... already defined in type 'SealedToStringSetBase' and is static, non-virtual or sealed", plus 4 CS0535.OverParsingBase: LAMA0500 "cannot introduce member 'OverParsingBase.Parse(string?)' ... already defined in type 'ParsingSetBase'", plus 4 CS0535.OverParsableBase: LAMA0512 "cannot implement interface 'IParsable' ... already implements it and WhenExists is set to Fail", plus 4 CS0535.OverOpenToString: builds with no diagnostic; the generatedToString()overrides the base's. The same declaration over a value object is refused (UnsealedInheritedToString).
No CMTK error names any of them. A base from another assembly takes the same path (set.Methods lists no base member at all): traced, not run.
Change
Walk ValueObjectDeclaration.SelfAndBases(set) for the Parse/TryParse/Match signatures, with "in its base type X" wording; find a base's IParsable<TSelf> through the base types' AllImplementedInterfaces (or pass it to ImplementationsOf); treat a base's sealed ToString() as the seam, as ValueObjectDeclaration.DeclaresToString does. For a base's unsealed ToString(), refuse it as value objects do, or take it as the seam: the fix decides, recorded beside decision 121.
Test
The four rows above in BuildOutcomeTests (each a CMTK1011 naming the set, no LAMA or CS error naming it), plus the same shapes with the base in the build-outcome Library project; reverted per verify-the-guard. Input class (GTS): a generated closed-set member, by where it is declared.
Environment
.NET SDK 10.0.4xx, Metalama 2026.1; any consumer.
Fixed in #218 (9f749a4)
Measured beyond the report: a base class's Parse(string, bool), TryParse(string?, bool, out TSelf), Match, a static All (LAMA0503) and a protected _name failed the same way, and all four shapes failed from the library base too (run, not only traced). All are CMTK1011 naming the base. A base's sealed ToString() is the seam; one not sealed is CMTK1011, recorded in decision 121 as an exception to decision 34 (it built on 2.3.0). A base's private _name/_all and a [JsonConverter] on it build, as before (lead 2 of the sweep: harmless).
Activity
CaffeinatedCoder commentedon Oct 31, 2026
A finding against #218 (
9f749a4) from the pre-release sweep ofrelease/2.3.1(lens: new-check-vs-invariant); reproduced in a scratch generator test.ClosedSetTypes.GeneratedMembersDeclaredByHand's three method lines (src/CodoMetis.TypeKit.Generators/ClosedSetTypes.cs:224-226,Declared(...)overSelfAndBases(set)forParse,TryParseandMatch) don't filter on accessibility. The field and property line above them does (:222,member.Accessibility != Accessibility.Private), and the method's remarks say a private member of a base "is out of the set's sight and builds".v2.3.0: builds, andCodedSet.Parse("Second") == CodedSet.Second.CMTK1011 'CodedSet' declares CodedSetBase.Parse(string) in its base type CodedSetBase, so it is not generated…, followed by CS0535 forAll,Name,QualifiedNameandTryParse.Change: skip
Accessibility.PrivateinDeclared, as:222does. Test: a privateParse/TryParse/Matchin a set's base must generate, and aprotectedone is refused.The same root cause on value objects (#201, #203) is #230.
CaffeinatedCoder commentedon Oct 31, 2026
The finding above (a set's base class's private
Parse,TryParseorMatchrefused by #218's method lines) is fixed in #244, stacked on #236 and #218, together with #230's value-object version: each base check counts only a member the type can see (IsAccessibleFrom), the field and property line included. No change to #218 is needed for it.CaffeinatedCoder commentedon Oct 31, 2026
Fixed by #218, merged into
release/2.3.1(48af3f2); ships in 2.3.1.