Repository navigation
One signature identity for every hand-written-member check, and [GeneratedMember] on every generated member - #279
Merged
CaffeinatedCoder merged 4 commits intoOct 31, 2026
Conversation
…ted member Every check of a hand-written member asks C#'s identity (name, arity, parameter types without nullable annotations, by reference or by value, another kind of member of its name), the named generated members are entries the aspects introduce through and the checks read, and every generated member carries [GeneratedMember]. Measured on af3f95e; refines decisions 28 and 121. Refs #239 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from what the aspects introduce MethodsWithSignature, ClosedSetTypes.SameParameters, MembersNamed and the base-type walks each decided arity, ref kind, nullability and kind by rules of their own, and the generated members' names were two lists kept by hand. - MemberIdentity is C#'s: a method of the generated one's name, arity and parameter types without nullable annotations (Nullable<T> its own type), by reference or by value; or another kind of member of its name, in the type or a base, except a base's method beside a generated property, which it hides. Visible from the value object only (#230). - GeneratedMembers holds one entry per named member the aspects introduce. Each aspect introduces through its entry, which names the member and checks that its template gives it that signature (a drift fails every build, LAMA0041); the checks, the CMTK1005/CMTK1021 names and the refused interfaces read the same entries under the aspects' own predicates. Input class: a hand-written member beside a generated one (GTS), over the shapes kind x site (own, base, base of another assembly) x signature (arity, ref kind, Nullable<T>, annotation) x visibility, measured against af3f95e in a throwaway consumer. Siblings the unified identity found: - built now, were CMTK1011: a base record's TryFrom(int?) over an int, a closed set's Parse(string, bool?), a base record's MinValue(), Value() or _value(), an entry point's overload (each builds with warnings as errors and answers through the generated member); - CMTK1011 now, were LAMA0506/LAMA0531/LAMA0611: a property or field named Parse, TryFormat, TryFrom or CompareTo on the value object or a base, a set's Name() method, a set base's nested type of the converter's name. Every regression row of #191 to #197, #205, #226 and #230 passes unchanged. Seeded: Nullable<T> merged into T, no cross-kind clash, visibility, ref kind and arity ignored, base methods clashing, a catalogue signature drifting, a seam found by name; each fails the generator tests. Refs #239 Defect-Class: GTS Found-By: VA Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…contract member it implements GeneratedMemberAttribute(Type contract, string member), in the Metalama-free base package's CompilerServices: the interface the member implements (IParsable<OrderId>.Parse), or the marker that promises it (IValidatedValue<Sku, string, SkuFault>.TryFrom). Not [GeneratedCode], which analyzers skip. Every GeneratedMembers entry adds it beside [CompilerGenerated]; explicit implementations, operators, constructors and the companion's methods get it where they are introduced. ValueObjectDeclaration.IsHandWritten reads it, and a hand-written one on a value object's or a closed set's member is CMTK1011. Metalama 2026.1 writes the override of a record's implicit ToString(), and of Equals and GetHashCode() over a base record, over that declaration and applies no attribute to it, nor introduces one there (measured), so those carry neither attribute, as they carried no [CompilerGenerated] before. GeneratedSurface.verified.txt names each member's contract member and is otherwise unchanged; GeneratedMemberAttributeTests holds that every member with [CompilerGenerated] carries the mark and none without it does, in this assembly and the probes'. Seeded: no mark on catalogue members (182 rows fail), a hand-written mark not refused (its row fails). A surface waiver covers the attribute, which no document promises yet. Refs #239 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #239
Problem
Seven 2.3.1 bugs (#191, #195, #196, #197, #205, #226, #230) were one question answered in several places with different rules: is this member the user wrote the one a generator introduces?
MethodsWithSignaturecompared parameter types withEquals.ClosedSetTypes.SameParametersand the base-record seam walk compared them afterToNonNullable(), which also turnsint?intoint.MembersNamedtook every member of a name on a value object's bases, while the closed set's base walk took only fields, properties and events. The generated members' names were two lists kept by hand (ValueObjectDeclaration.GeneratedMemberNames,ClosedSetTypes.GeneratedNames), so a new generated member left off them was kept silently. Nothing marked a generated member as generated.Change
The design is decision 128. The identity and the attribute turned out to be separable, so they land as two commits, identity first.
docs:decision 128, its index row (0028 and 0121 marked as refined by it),design/aspects.md, and the AGENTS.md line about new generated members.fix:one identity (MemberIdentity), C#'s rules: a method of the generated method's name, arity and parameter types (nullable annotations ignored,Nullable<T>a type of its own, by reference against by value), or another kind of member of its name. A base's method beside a generated property doesn't count, because the property hides it. Only members visible from the value object count.GeneratedMembershas one entry per named member the aspects introduce. Each aspect introduces the member through its entry, which names it, checks the template's signature (a drift fails every build, LAMA0041) and marks it. The checks, the CMTK1005/CMTK1021 names and the refused interfaces all read the same entries, under the aspects' own predicates. Both name lists are gone.feat:[GeneratedMember(typeof(Contract), "Member")]inCodoMetis.TypeKit.CompilerServices(base package, Metalama-free), on every generated member, the explicit implementations, operators, constructors and companion methods included.IsHandWrittenreads it. A hand-written one is CMTK1011. Exception (measured): Metalama 2026.1 applies no attribute to an override it writes over a record's implicitToString()(andEquals/GetHashCode()over a base record), and refusesIntroduceAttributethere. Those members carry neither attribute, just as they never carried the[CompilerGenerated]the aspects asked for.Build outcomes that change (each measured against
af3f95ein a throwaway consumer, and held by a newBuildOutcomeTestsrow):Parse,TryFormat,TryFromorCompareTo, own or a base'sName()methodTryFrom(int?)over anint; a set'sParse(string, bool?)TryFrom(int?)already built)MinValue(),Value(),_value(); an entry point's overload__FromJson(string)No build that built fails now. The newly building forms are refusals of code C# compiles beside the generated member, the #230 family. Loosening an Error in a minor is allowed by decision 34. Every regression row of #191–#197, #205, #226 and #230 passes unchanged.
Tests
GeneratedMemberAttributeTestsrows, one more fixture row). After thefix:commit alone, 1463 passed.GeneratedSurface.verified.txtgains the contract member on each generated member's line and is otherwise byte-identical, checked by stripping the markers and diffing againstaf3f95e.tools/BuildVerdict: everything passes except (a) the SQL Server container fixtures, the arm64-Mac environment issue (TYPEKIT_MSSQL_IMAGE), untouched by this change, and (b)DesignRecordTests.The_records_are_numbered_without_a_gap, which wants record 0127. That number is reserved for another 2.4.0 session, so this PR needs 0127 merged first, or a re-sync after it lands.Nullable<T>merged intoT(the two new "builds" rows fail); no cross-kind clash (31 rows); visibility ignored, ref kind ignored, arity ignored, a seam found by name only (the A generic overload such as Parse<TFormat>(string, IFormatProvider) is mistaken for the generated Parse, and the value object or closed set is refused #226/A private member of a base record with a generated member's name, such as MaxValue, Parse, TryFrom or CompareTo(object), now fails the build (CMTK1011, CMTK1008), where 2.3.0 generated the value object #230 declarations fail with CMTK1011); a base's method clashing with a generated property (5 rows); a catalogue signature drifting from its template (LAMA0041 on every probe); no mark on catalogue members (182 rows); a hand-written mark not refused (its row).Create's routing and the materializer are unchanged. The new code finds members by symbol and by TypeKit's own member names only. The base package gains a plain attribute. Not run: the consumer smoke test (Native AOT); the attribute'stypeofarguments only reference contract types the members already implement.The analyzers' own name constants (
TryFrom,Revalidate,FromKnownGood) are left alone. Before weaving, in the declaring project, no member carries the attribute yet, so they can't read it there.🤖 Generated with Claude Code