Skip to content

fix: keep an overload of a generated member that differs by arity or ref kind - #236

Open
CaffeinatedCoder wants to merge 4 commits into
release/2.3.1from
fix/generic-or-ref-overload-refused-2-3-1
Open

CaffeinatedCoder wants to merge 4 commits into
release/2.3.1from
fix/generic-or-ref-overload-refused-2-3-1

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Closes #226

Stacked on #218 (fix/closed-set-base-member-refused-2-3-1), whose base-class reads for closed sets this fix's closed-set rows need. Merge #218 first; this PR then retargets to release/2.3.1.

Problem

ValueObjectDeclaration.MethodsWithSignature and ClosedSetTypes.SameParameters matched a hand-written method to a generated one by name and parameter types only. A legal overload was refused with CMTK1011 as the generated member written by hand: Parse<TFormat>(string, IFormatProvider?), From(in int), From(ref int), a TryParse whose last parameter is by value, a generic or by-value TryFormat, and a closed set's generic Parse or by-value TryParse. That happened on the type, on a base record and on a set's base class. C# overloads by each of these, and Metalama introduces the generated member beside it (measured in a scratch consumer: every row builds and calls the generated member).

The same catalogue, run over the other name-and-parameters checks, found seven siblings (Found-By: VA). Three were silent:

  • A generic ToString<TFormat>() was taken for the seam. The value object printed ToStringOverloadedByArity { Value = a }, as a record does, and a closed set printed its type name.
  • A generic or by-in CompareTo(TSelf) was taken for the ordering seam, so the ordering was marked [HandWrittenOrdering] and EF Core refused to order by it.
  • A generic Equals<T>(TBase?) stopped the value object re-implementing a deeper base's explicit IEquatable<TBase>, so a HashSet<TBase> held two value objects holding 42.

The other four were refusals: a generic or by-reference CompareTo(object), TryFrom, Revalidate, Equals, GetHashCode or PrintMembers, on the type or a base record.

Change

  • MethodsWithSignature and SameParameters match only a method without type parameters, and compare each parameter's ref kind: by reference where the generated one is out, by value where it's by value. ref or in against out (CS0663) and nullability stay CMTK1011.
  • ValueObjectDeclaration.IsByValueAndNotGeneric holds the seam and synthesized-member checks to the same rule: IsHandWrittenToString, InheritedSeams, PrintMembersDeclaredByHand, EqualityDeclaredByHand, InheritedEquality, the comparable aspect's HandWrittenComparisonMembers and DeclaresCompareTo, and the contract aspect's re-implemented IEquatable check.
  • Generators README ("Write your own members", "Rules for a closed set"), design/aspects.md, three claims, two CHANGELOG lines.
  • Test infrastructure: FabricTests looks up From/TryFrom by signature, since an overload made it ambiguous. The surface snapshot now prints a generic method's type parameters and in/ref, so an overload doesn't read as a duplicate line. Match gains <TResult>.

Tests

  • OverloadTests (new): 53 rows where the generated member sits beside a hand-written overload and is what a plain call reaches, 14 rows that reach the hand-written one, and 3 that check the ordering isn't marked hand-written. They cover the type (record struct, sealed record class), a base record in this assembly and in another (ProbeOverloadingRecord), two levels up, a closed set and its base class.
  • BuildOutcomeTests: 8 control rows stay refused: ref/in against out on the type, a base record, a set and a set's base, a ref TryFormat, and another nullability.
  • Reverted: without the first commit, all 16 new types are refused (32 CMTK1011). Comparing ref kinds exactly fails the 6 ref/in controls. Without the second commit, 5 of its types are refused. Reverting IsHandWrittenToString, DeclaresCompareTo or the contract aspect's check alone fails its own rows (3, 2 and 1).
  • Locally through tools/BuildVerdict: Generators.Tests 1249 passed, Conventions.Tests 2016, Samples 150.

🤖 Generated with Claude Code

CaffeinatedCoder and others added 2 commits October 31, 2026 08:28
…ref kind

ValueObjectDeclaration.MethodsWithSignature and ClosedSetTypes.SameParameters
matched a hand-written method to a generated one by name and parameter types
alone. A generic Parse<TFormat>(string, IFormatProvider?), a From(in int) or
From(ref int), a TryParse whose last parameter is by value, a generic or
by-value TryFormat, and a closed set's generic Parse or by-value TryParse were
refused as the generated member written by hand (CMTK1011), on the type and on
a base record or base class, though C# overloads by each and Metalama
introduces the generated member beside it (measured in a scratch consumer:
every row builds and calls the generated member, no LAMA error).

Both now match only a method without type parameters, and compare each
parameter's ref kind: by reference where the generated one is out, by value
where it is by value. ref or in against out is no overload in C# (CS0663) and
nullability none at all, so those stay CMTK1011.

Input class (GTS): a method that shares a generated member's name and
parameter types, by what differs (arity; in, ref or out against by value;
ref and in against out; nullability) and where it is declared (the type, a
base record of this assembly and of another, a closed set and its base
class), over the declaration shapes the check reads (record struct, sealed
record class, closed set). 34 rows in OverloadTests build and call the
generated member, 10 reach the hand-written overload, and 8 controls in
BuildOutcomeTests stay refused. The wrapped type only enters the check as
From's parameter, so the rows wrap int, which has every generated parse and
format. Siblings found by the same catalogue on the other signature checks
(TryFrom, Revalidate, CompareTo, Equals, GetHashCode, PrintMembers,
ToString()) are the next commit.

Reverting the source refuses all 16 new value objects and sets in the test
project (32 CMTK1011); comparing ref kinds exactly (ref against out as
another method) fails the 6 ref and in controls and Every_error_is_one_of_
the_intended_ones.

FabricTests looked up From and TryFrom by name, which an overload made
ambiguous; it looks them up by signature. The surface snapshot renders a
generic method's type parameters and a ref or in parameter, so an overload
no longer reads as a duplicate line; Match gains <TResult>.

Closes #226

Defect-Class: GTS
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mber is another method

The previous commit's catalogue, run over the other checks that find a
hand-written member by name and parameters, found the same cause in seven
of them: each read a method of any arity and any ref kind.

Silent, measured with the check reverted:
- IsHandWrittenToString took a generic ToString<TFormat>() for the seam.
  The value object printed "ToStringOverloadedByArity { Value = a }", what
  a record prints, and a closed set printed its type name. A base record's
  was CMTK1011 as an unsealed ToString().
- DeclaresCompareTo took a generic or by-in CompareTo(TSelf) for the
  ordering seam and marked the ordering [HandWrittenOrdering], which the EF
  Core satellite refuses to order by, though the ordering was generated.
- ValueObjectContractAspect skipped re-implementing a deeper base's explicit
  IEquatable<TBase> beside a generic Equals<T>(TBase?), so a
  HashSet<TBase> held two value objects holding 42.

Refused (CMTK1011, CMTK1008), where the generated member sits beside it:
a generic or by-in CompareTo(object), a base record's generic CompareTo,
TryFrom<T>(int) or TryFrom(in int) and Revalidate<T>() (InheritedSeams), a
generic Equals(TSelf) or GetHashCode() (EqualityDeclaredByHand), a base
record's (InheritedEquality, CMTK1023), and a generic PrintMembers.

Each check now reads only a method without type parameters whose
parameters are all by value (IsByValueAndNotGeneric), as every generated
member but TryParse and TryFormat is; those two are MethodsWithSignature's.

Input class (GTS): as in the previous commit, over the seams (ToString(),
CompareTo(TSelf), TryFrom, Revalidate) and what a record synthesizes or
calls (Equals, GetHashCode, PrintMembers, CompareTo(object)), on the type,
a base record, two levels up and in a closed set. 19 rows join
OverloadTests' 34, 4 the hand-written ones, and 3 check the ordering is
not marked hand-written. Reverting the commit refuses 5 of its types;
reverting IsHandWrittenToString alone fails the 3 ToString rows,
DeclaresCompareTo alone the 2 ordering rows, and the contract aspect's
check alone the deeper-base row.

Refs #226

Defect-Class: GTS
Found-By: VA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CaffeinatedCoder CaffeinatedCoder added this to the 2.3.1 milestone Oct 31, 2026
@CaffeinatedCoder CaffeinatedCoder added bug Something isn't working silent Compiles or answers and does less than it appears to stacked Base is another open PR; merge in order, CI does not run until it targets a release severity/medium Inconsistency or failure in a narrow scenario package/generators Affects CodoMetis.TypeKit.Generators area/value-objects Value-object contracts and what the aspects generate for them area/parsing Parse, TryParse, formatting and the type converter labels Oct 31, 2026
Base automatically changed from fix/closed-set-base-member-refused-2-3-1 to release/2.3.1 October 31, 2026 07:48
@CaffeinatedCoder CaffeinatedCoder removed the stacked Base is another open PR; merge in order, CI does not run until it targets a release label Oct 31, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/parsing Parse, TryParse, formatting and the type converter area/value-objects Value-object contracts and what the aspects generate for them bug Something isn't working package/generators Affects CodoMetis.TypeKit.Generators severity/medium Inconsistency or failure in a narrow scenario silent Compiles or answers and does less than it appears to

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant