Skip to content

release: 2.3.1 - #258

Merged
CaffeinatedCoder merged 63 commits into
mainfrom
release/2.3.1
Oct 31, 2026
Merged

CaffeinatedCoder merged 63 commits into
mainfrom
release/2.3.1

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Closes #198

Release 2.3.1 into main

The changelog's 2.3.1 section, dated 2026-10-31, is the release notes: a patch of 2.3.0 with no new feature and no new public member. Generated members a value object or closed set reaches through a base record, a base class or an interface now fail with a CMTK error that names them, where they stood in silently or crashed Metalama; overloads that differ by arity or ref kind and a base's private members are kept. CMTK0005 and CMTK0006 cover more ways to make a default slot. EF Core refuses an ordering it would answer by the column, and builds a query filter it runs in memory.

Checked locally on release/2.3.1 at bce9e84 before the tag (CONTRIBUTING, "Releasing", step 1):

  • oracles seeds: every seed caught, every baseline passing without its seed, the SQL Server oracles included (exit 0).
  • docs-query due --nuget: no fact due (0 of 34 pinned external).

The last topics were #244 (#230), #255 (the changelog dated, the analyzer release recorded) and #257 (main merged in, keeping 2.3.1's Version, since main had opened 2.4.0). The tag goes on this PR's merge commit; step 5 then opens 2.4.0 on main again with the baseline at 2.3.1.

🤖 Generated with Claude Code

CaffeinatedCoder and others added 30 commits October 30, 2026 17:03
…ne to 2.3.0

2.3.1 is a patch of 2.3.0 (tag v2.3.0 at edeb90c): release/2.3.1 is cut
from that tag, per decision 94's patch release, and carries only fixes
for bugs that shipped in 2.3.0. Version is 2.3.1, and the changelog
opens its section as Unreleased with its first line, since pack fails
while the section is empty.

Package validation compares with 2.3.0, as release/2.4.0 does, so a
fix here can't change what 2.3.0 ships.

Closes #199

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a value object reaches it

CMTK1011, and CMTK1008 for comparison, listed the members of the value
object and its base records. An interface's explicit implementation of
a member the generators implement is the default every implementing
type reaches until it declares a public member, so the generated one
replaced it without a word: Max<Bounded>() read the generated MaxValue
where IBounded<TSelf>'s threw, and an IFormattable call printed 1 where
the interface answered its own text. Metalama lists no private member
of a type in another assembly, so a base record from a referenced
project was missed too, replaced (IFormattable) or kept answering
beside the generated member (IMinMaxValue).

ExplicitImplementationsOf now asks the interface mapping
(TryFindImplementationForInterfaceMember) for each member of a
generated interface the type implements, and refuses an explicit
implementation, naming where it is: "in its base type X" or "in its
interface X". An interface's == maps to the record's own, so it stays
allowed. INumberBase<TSelf>'s UTF-8 bridges, the only such members in
.NET's interfaces, are not refused, compared by assembly symbol.
IConvertible declared through an interface, and a closed set's
IParsable<TSelf> through one, are CMTK1011 where the aspect crashed
(LAMA0041, LAMA0512). Decision 121 records it, an exception to
decision 34 under decision 119.

Input class (GTS): an explicit implementation of a generated
interface's member, by where it sits. Catalogue in BuildOutcomeTests:
the eleven interfaces of #191 on a record struct, plain and validated;
two interfaces down; through a base record's interface; on a record
class over string; from another assembly (interface and base record,
in Generators.Probes); a closed set through an interface of either
assembly. Four controls build: a ToString() seam, a Uri's missing
ordering, an interface's ==, an INumberBase<TSelf> value object.
Siblings found and fixed here: another assembly's base record (missed
since phase 11), IConvertible through an interface (LAMA0041), a
closed set's IClosedSet<TSelf> and IParsable<TSelf> through an
interface. ExplicitlyImplementsEquatableBetween has no gap: a record
base's synthesized Equals(TBase) wins over any interface default.
Left for its own issue: IValueObject<,> through a user interface
still fails with LAMA0512.

Reverting the three source files fails 30 of 263 BuildOutcomeTests;
each guard's own seed fails only its rows.

Cherry-picked from 466c541 (#193, on release/2.4.0) for 2.3.1, since the
defect shipped in 2.3.0: decision 121 (124 on release/2.4.0, renumbered
here so 2.3.1's records run without a gap) records the exception to
decision 34 as taken in the patch, and the changelog line sits in
2.3.1's section.

Refs #191

Defect-Class: GTS
Found-By: VA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A value object over a base record could declare an Equals of a deeper
base's type, such as Equals(DeepBase?). It implemented IEquatable<DeepBase>,
which the value object lists again through IReimplementedEquatable, so a
HashSet<DeepBase> compared by it, while == compared the wrapped value:
two holding "a" were equal, and the set held both.
EqualityDeclaredByHand refused Equals(TSelf) alone, and
IntroduceEquality skipped its own new Equals(TBase) because the value
object declared one.

EqualityDeclaredByHand now refuses an Equals of the value object's type
or of any of its base records' types, nullability aside, with the
existing CMTK1011 and its remedy (normalise in Create). One of the
direct base's type is C#'s own CS0111, beside the override the record
synthesizes (measured).

Input class (GTS): the parameter type of a hand-written Equals. Catalogue:
the value object's own on a record struct and on a record class (nullable
parameter, already refused), a deeper base's (refused now), and the
direct base's (C#'s CS0111). The record class case had no fixture; it
has one now, and no sibling was found.

The test fails before the fix and with the base types dropped from the
check (1 of 233 BuildOutcomeTests).

Refs #196

Defect-Class: GTS
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… one as such

A base record's public member the generators introduce counted for
nothing. Its static MinValue and MaxValue, with IMinMaxValue<TSelf> or
without, were the value object's, and no MinValue or MaxValue was
generated; its public Value, From, Parse, TryParse, ToString(string?,
IFormatProvider?), TryFormat or CompareTo(object) failed the aspect
(LAMA0500), naming nothing the user wrote. From another project, a
public static member that implements a static abstract one reads as an
explicit implementation, since the compiler records every such
implementation as one, so the error called it "the explicit
implementation of ...".

ImplementationsOf (was ExplicitImplementationsOf) now refuses any
implementation the interface mapping finds outside the value object's
own public members, public or explicit, and names a public one by its
type and member. The names and signatures checked on the value object
are checked on each base record too, for a member that implements no
interface. MembersNamed leaves an explicit-looking member to the
mapping, so a member of another assembly is named once.

The build-outcome fixture builds a library that references the base
package alone, beside its consumers, since a base type there that
carries a marker or IClosedSet<TSelf> is no value object or set of its
own. A closed set over its base class that implements IClosedSet<TSelf>
explicitly, an outside review's report, is refused since 4a3d9a6; the
test holds it now.

Input class (GTS): a generated member declared in a base record, by
member and by where the base is. Catalogue: MinValue/MaxValue through
IMinMaxValue and by name alone, ToString(string?, IFormatProvider?),
CompareTo(object), Parse/TryParse, Value, From and TryFormat, in this
project, and a public static from the library. A base's TryFrom, a
seam, stays with #194.

Reverting this commit's source fails 12 of 274 BuildOutcomeTests; each
guard's own seed fails only its rows (the mapping's public case, the
names on base records, the public naming, the explicit-looking names).

Refs #197, #191

Defect-Class: GTS
Found-By: VA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de the value object's

Where a validated value object reaches the marker only through a base
record of another project that implements Create explicitly, every
generated way in called the value object's own Create, which accepted
"x", and generic code calling T.Create reached the base's, which
refused it. CreateDeclarations read the value object's own methods,
and Metalama lists no private member of another assembly's type.

CreateDeclarations asks the interface mapping where T.Create lands. An
explicit implementation in a base record or an interface is CMTK1011,
and so is a base's public Create beside the value object's own; a
base's public Create alone is the one rule set the generated code calls
too, and builds. The error says to declare the marker on the value
object itself, which re-implements it, since the base cannot be
changed from there.

Input class (GTS): where the Create that T.Create reaches is declared.
Catalogue: an explicit one in the library's base with and without the
value object's own, a public one beside it, and, building, the marker
declared again over the explicit base and a public base Create alone.
In the same project such a base carries the marker and is CMTK1022.

Reverting the CreateDeclarations mapping fails the three refusal rows;
dropping the public-alone condition fails OnlyPublicBase.

Refs #195

Defect-Class: GTS
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build: open 2.3.1 from v2.3.0, and move the package-validation baseline to 2.3.0
A base record's CompareTo(TSelf) was not seen as the comparison seam:
DeclaresCompareTo read the value object's own methods, so no
[HandWrittenOrdering] was put on it and an EF Core query ordered by the
column ([1, 2]) where memory ordered [2, 1]. The generated
CompareTo(TSelf) was not introduced, since OverrideStrategy.Ignore saw
the inherited one, and the generated operators and CompareTo(object)
called left.CompareTo(right), which C# binds to the generated
CompareTo(object), declared on the more derived type: < overflowed the
stack and ended the process. A base's TryFrom(T) and Revalidate() were
the value object's in the same way, and the generated ones, which run
Create, were not introduced. A base's CompareTo(object) failed the
aspect (LAMA0500).

HandWrittenComparisonMembers reports a base record's CompareTo(TSelf)
and CompareTo(object) (CMTK1008), named once where the interface
mapping finds the second too. ValueObjectDeclaration.InheritedSeams
reports a base's TryFrom(T) and Revalidate() on a validated value
object (CMTK1011), whose remedy says to declare it on the value object.
A base's FromKnownGood(T) is another signature than the generated one,
which takes the caller's expression too and hides it, so it stays
allowed. Decision 121 records the choice over taking a base's as the
seam.

Input class (GTS): a seam, by where it is declared. Catalogue: each seam
in a base record (CompareTo(TSelf), CompareTo(object), TryFrom,
Revalidate, FromKnownGood), beside the value object's own seams the
suite already holds. A base's sealed ToString() stays the seam.

Reverting the source to 03e199b fails 6 of 285 BuildOutcomeTests; each
guard's own seed fails only its rows, the named-once check included.

Refs #194

Defect-Class: GTS
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…implementation-refused-2-3-1

fix: refuse a generated member written by hand wherever a value object reaches it
…-tbase-refused-2-3-1

fix: refuse a value object's Equals of a base record's type
…sed-2-3-1

fix: refuse a seam a base record declares in place of the value object
…a base record

IValueObject<,>, IValueObjectMaterializer<,> and IPlainValueObject<,>
are implemented by the generators with OverrideStrategy.Fail, and
declaring one on the value object is CMTK1011. Declared through an
interface of the user's, as an IEntityId<TSelf> : IValueObject<TSelf,
Guid> written to constrain generic code, or by a base record, the
aspect failed instead (LAMA0512, and LAMA0041 for the materializer),
naming nothing the user wrote: GeneratedMembersDeclaredByHand read the
value object's directly declared interfaces alone.

It now reads each declared interface's own interfaces and each base
record's, and names where the contract comes from ("the interface
IValueObject<OrderId, Guid> in its interface IEntityId<OrderId>", "...
in its base type X"), with IConvertible folded into the same check.
Neither marker derives from a contract, so an interface derived from a
marker stays allowed. Only builds that failed before fail, now with a
CMTK error. Decision 121 records refusing over accepting the contract
through an interface, which would add behaviour to a patch.

Input class (GTS): a contract the generators implement, by where it is
declared. Catalogue: IValueObject one and two interfaces down, through a
base record's interface and through a library interface;
IValueObjectMaterializer and IPlainValueObject through an interface; and
an interface derived from the marker, which builds. IConvertible's rows
hold the folded check.

Reverting the source fails the 6 new rows and the two LAMA guards (8 of
294 BuildOutcomeTests); dropping the interface or the base read fails
its rows; counting the marker as a contract refuses every value object.

Refs #192

Defect-Class: GTS
Found-By: VA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct-through-interface-refused-2-3-1

fix: refuse a value-object contract declared through an interface or a base record
…rameter, as CMTK0001 does

CMTK0005 and CMTK0006 decided "no default" with IsNoDefaultStruct,
which admitted a type parameter only beside struct. CMTK0001 and
CMTK0009 use DefaultRestriction, which also admits a parameter bound by
the marker beside new() and no class bound: a generated class has no
public parameterless constructor, so such a T is a struct value object.
So for where T : IEntityId<T>, new(), new T() was CMTK0001 while
new T[3] and an unassigned T field reported nothing, although the
README defines both rules over CMTK0001's list.

IsNoDefaultStruct is now DefaultRestriction(type) is not null, the
predicate CMTK0001 reads. Both rules are Warnings, which the README
lets learn forms in a minor version.

Input class (AIS): what the judged type is. Catalogue:
TypeArgumentKinds, 80 kinds with the one added here (a new() parameter
bound by an interface deriving from IValue<int>, the reported shape).
NoDefaultKindTests runs CMTK0001, CMTK0005 and CMTK0006 over every kind
against one oracle, the README's list; CMTK0001 agreed on all 80 before
the fix, and the two new() kinds were the only rows where the other two
did not. No other sibling: only these two rules called the narrow
predicate.

Reverting the source fails the 4 new() rows of 240.

Refs #207

Defect-Class: AIS
Found-By: CA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…jection boxed first

Select(r => (object)r.Rank).Max(), .Min(), .Order(), .OrderDescending()
and .OrderBy(x => x) over a value object whose ordering is its own
CompareTo were not refused: EF dropped the cast and answered the
column's order, Max 3 and Order 1, 2, 3 where memory answered 1 and
3, 2, 1 for a descending CompareTo (measured on SQLite). The Finder
judged an ordering operator by its declared type, object here, and
looked under a conversion only in a key selector's body, which
8f6af8b added for OrderBy(r => (object)r.Rank).

The Finder now reads the types a sequence's elements have under their
boxing: a Select's body unconverted, followed through the operators
that pass elements on (Where, Take, Distinct, the orderings, Cast and
OfType, Concat and its kind). An operator without a key, or whose key
is the element itself, is refused by those types. Query filters go
through the same Finder, so a filter that does this is refused when
the model is built. The README says the cast changes nothing.

Input class (EFT): an ordering operator over a value object, by where
the conversion sits. Catalogue: QueryShapes' Op axis gains OrderBoxed,
OrderByItselfBoxed, MinBoxed and MaxBoxed over every wrapped kind,
converter and accessor of the column and nullable sites (green on
SQLite, PostgreSQL's translation and SQL Server). Explicit rows hold
OrderDescending, a record class, a navigation's Enumerable.Max, a Where
and Take between the box and the operator, and a query filter. Sibling
found: over a navigation, the same query threw a NullReferenceException
inside EF rather than answering; it is refused before EF now.

Reverting the source fails 33 of 11095 (24 catalogue rows, 9 explicit).

Refs #206

Defect-Class: EFT
Found-By: CA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and keep its sealed ToString()

ClosedSetTypes read the set's own members alone. A base class's
Parse(string), Parse(string, bool), TryParse(string?, bool, out TSelf),
Match, a static All, a protected _name, an IParsable<TSelf> it
implements, and its sealed ToString() failed the aspect (LAMA0500,
LAMA0502, LAMA0503, LAMA0512), naming nothing the user wrote, in this
project and from another. A base class's ToString() that is not sealed
was replaced by the generated one without a word, where a value
object's base record's has been CMTK1011 since phase 11.

The checks of names and signatures now read each base class, private
members aside (a base's private _name and _all built and still do), and
an IParsable<TSelf> a base implements is named with its base. A base's
sealed ToString() is the seam, through ValueObjectDeclaration's own
check; one not sealed is CMTK1011 with the value objects' remedy.
Decision 121 records refusing it over taking it as the seam, which
would change what such a set prints in a patch, and the exception to
decision 34: such a set built on 2.3.0. CS0738 joins CS0535 as what a
refused set may leave, where a base's All has another type.

Input class (GTS): a generated closed-set member, by where it is
declared. Catalogue in BuildOutcomeTests: each member above in a base
class of this project, Parse, IParsable and both ToString() forms in a
library base, QualifiedName (refused before, through the interface
mapping), and four that build: a sealed ToString() of either assembly,
private fields, a [JsonConverter] on the base. InheritedMembersTests
holds what a set over a sealed ToString() prints. Siblings found by the
catalogue: All, Match, Parse(string, bool), TryParse(string?, bool, out)
and a protected _name, beyond the three the report named.

Removing the base reads and the ToString() refusal fails the 11 new
refusal rows and the two LAMA guards (13 of 310 BuildOutcomeTests);
reverting the seam fails the test project's build on MaskedSet.

Refs #205

Defect-Class: GTS
Found-By: CA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rojection's member

OrderBy(r => (object?)r.Optional ?? r.Rank) and
Select(r => new { Key = (object)r.Rank }).OrderBy(a => a.Key) over a
value object whose ordering is its own CompareTo were not refused: EF
dropped the cast and answered 1, 2, 3 where memory answered 3, 2, 1 for
a descending CompareTo (measured on SQLite at 3d2abfd). The Finder looked
under a conversion only at the top of a key's body or a Select's, so a
?? over the box, or a member of a projection that holds it, read as
object.

The Finder now reads the types a key or selector body yields under its
boxing: both sides of a ?? and a ?:, the elements for the lambda's
parameter (as before), and for a member of a projection, the expression
its new or member initializer set it from, found in the same lambda or
in the Select that built the elements, through nested projections and
the operators that pass elements on. The README, its claim and the
changelog line name both shapes.

Input class (EFT): an ordering operator over a value object, by where
the boxing sits and how the key reads it back. Siblings found by the
same probe and fixed here (VA): Max(a => a.Key) and
Select(a => a.Key).Max() over the projection, a nested anonymous member,
a member-init type, a Where between the projection and the ordering, a
?? inside the projection; each answered the column's order. A ?: key
failed translation on SQLite and is refused before it now. Over a
navigation the projected Max threw a NullReferenceException inside EF;
it is refused before EF. Catalogue: QueryShapes' Op axis gains
OrderByCoalescedBoxed, OrderByProjectedBoxed and MaxProjectedBoxed over
every kind, converter and accessor of the column and nullable sites.
Explicit rows hold the anonymous shapes, the navigation and a query
filter; controls over .Value and a generated ordering still translate.

Reverting the source fails 29: 18 catalogue rows, 11 explicit.

Refs #206

Defect-Class: EFT
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…query's First carries

Eight shapes still answered the column's order at d573eef, 1, 2, 3 (Max
3) where memory answers 3, 2, 1 (Max 1) for a descending CompareTo,
measured on SQLite: a grouping's boxed key under OrderBy(g => g.Key), a
grouping's boxed elements under g.Max(), GroupBy's, Join's and
SelectMany's result selectors holding the boxed value, SelectMany of
boxed values under Max(), and a subquery's or a navigation's boxed
First() as a key. The Finder followed a lambda's parameter only through
Select and the operators that pass elements on, and visited a nested
operator, g.Max(), knowing nothing of its source.

The Finder now knows what each lambda parameter stands for. Every LINQ
operator binds its lambdas' parameters: to the source's elements for a
selector, key or predicate; to each side's elements for Join and
GroupJoin (the inner side whole for GroupJoin); to the collection's
elements for SelectMany's result selector; to the key and the grouped
elements for GroupBy's. A grouping's Key reads its key selector, and
First, Single, Last, ElementAt and their OrDefault forms read the element
of their source. The visitor carries these bindings into nested lambdas,
so an operator inside one reads what its source was built from. The
README, its claim and the changelog line name the new shapes.

Input class (EFT): an ordering operator over a value object, by the
operator whose lambda carries the boxed value to it. Sibling found by
the same rows (VA): a GroupJoin result reading cs.First().Rank, refused
before EF now. Loud and left as it is: a Tuple.Create projection, which
EF does not translate. Catalogue: QueryShapes' Op axis gains
OrderByGroupKeyBoxed and OrderByJoinedBoxed over every kind, converter
and accessor of the column and nullable sites. Controls over .Value and
a generated ordering, through a grouping, a join and a subquery, still
translate.

Reverting the source fails 21: 12 catalogue rows, 9 explicit.

Refs #206

Defect-Class: EFT
Found-By: RS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mension

AnalyzeArrayCreation skipped an array only when every dimension was a
constant 0. A multidimensional array is empty when any one dimension is,
so new Plain[0, 2] held no default element and was reported. The check
is now Any; stackalloc reads the same rule, where its one dimension made
All and Any agree already.

Input class (AIS): an array creation of a no-default struct, by rank and
by which dimension is a constant zero. The new row covers rank 2 with
the first, the second and both dimensions zero, a variable beside a
literal zero and a named constant beside a variable, rank 3 with a zero
in the middle and last, a jagged [0][], and two arrays with no zero
dimension that still report. No sibling: the invocation forms take one
length, where a zero is already skipped.

Reverting the source fails the row: 8 reports where 2 are expected.

Closes #227

Defect-Class: AIS
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zero check matched only a boxed int. An array dimension may be a
long, uint or ulong, so new Plain[0L] was reported, and a stackalloc
size is read from syntax with its type before the conversion to int,
so stackalloc Plain[(byte)0], [(short)0] and ['\0'] were too. One
IsZero now reads every integral constant, for the array creation, the
stackalloc and the allocating calls alike.

Found running #227's input class (an array creation by which dimension
is a constant zero) across how the zero is written: int, long, uint,
ulong, byte, short and char, in new, stackalloc, GC.AllocateArray and
Array.Resize. The six that failed are the long and unsigned dimensions
and the three stackalloc sizes.

Seeding the int-only match back fails the row: 8 reports where 2 are
expected.

Refs #227

Defect-Class: AIS
Found-By: VA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HasQueryFilter(r => r.Rank == Limits.Max()) over a value object whose
ordering is its own CompareTo was refused when the model was built,
though EF evaluates Limits.Max() in memory, by CompareTo, and sends the
answer as one parameter. The Finder refused an ordering operator in a
filter without asking whether it runs in memory, as its comparison
branches do.

It now lets an ordering through in a filter when it reads nothing of the
database and takes no lambda. The lambda matters: measured on SQLite,
EF evaluates Limits.Max(), Min(), Order().First() and OrderDescending()
.First() in memory, but sends the values to the database and orders them
there for OrderByDescending(l => l).First(), which kept the row ranked 3
where memory keeps 1, and fails to translate MaxBy(l => l). So #225's
proposed change, skipping by database reads alone, would have answered
those silently; they stay refused. KB-058 pins the distinction with plain
EF (a collection with a lambda goes into the SQL as a VALUES list of
parameters; Max(v => v) throws inside EF). The README says when a filter
may order the context's values (claim E05-query-filter-own-values).

Input class (EFT): an ordering operator in a query filter, by its source:
the context's values without a lambda (allowed: Max, Min, Order().First(),
OrderDescending().First() over an array and a list), the context's values
with a lambda (refused: OrderByDescending(l => l), MaxBy, a boxing
Select before Max, which EF happens to evaluate but which the lambda rule
keeps refused), the entity's navigation and the table itself (refused).

Reverting the source fails the 4 allowed rows; dropping the lambda
condition, the change #225 proposed, fails the 3 refused ones.

Refs #225

Defect-Class: EFT
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
…pe and ArrayPool<T>.Rent

AnalyzeInvocation looked only at generic calls with one type argument,
GC.AllocateArray, GC.AllocateUninitializedArray and Array.Resize. The
README defines CMTK0005 as an array of a value object created with a
length, which Array.CreateInstance(typeof(Plain), 3) in each overload,
CreateInstanceFromArrayType(typeof(Plain[]), 3) and
ArrayPool<Plain>.Shared.Rent(3) are too; none was reported.

The two factories are reported when a typeof names the element (the
element of the array type for the second); a Type in a variable could be
anything and stays silent. Rent is recognised on ArrayPool<T> or an
override of it, its T the element. The lengths are read from every int
or long parameter and the elements of a params or written-out array of
them, never the lower bounds, so #227's rule holds: a constant zero in
any dimension stays silent.

Input class (AIS): an expression that creates an array of a no-default
struct, by the API that creates it. Every Array.CreateInstance and
CreateInstanceFromArrayType overload that takes a length, Rent on the
shared pool, on ArrayPool<T>.Create() and on a derived pool, each with
a zero-length control; a Type variable, GetType(), typeof(Plain?) and
an array of another element silent. Rent's T and the typeof element run
across TypeArgumentKinds (79 kinds; dynamic and string? take Rent
alone, typeof refusing them): each reports exactly where
GC.AllocateArray<K> does. No sibling beyond #229's
CollectionsMarshal.SetCount, which 2.4.0 takes.

Reverting the source fails the forms row and the three kinds that
report. Seeding the zero check away fails the three zero rows, reading
the lower bounds as lengths fails the forms row, and skipping an array
of lengths fails the silent row.

Closes #228

Defect-Class: AIS
Found-By: RS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the class's implementation

Assignments.Collect recorded a property assignment only on a bare
IInstanceReferenceOperation and added the referenced property. A class
reaches its own explicit interface implementation only through a cast,
((IHasId)this).Id = id, whose Instance is a conversion over this and
whose property is the interface's, not the candidate. So a member every
constructor assigned was reported, failing a warnings-as-errors build.

Collect now strips conversions down to this, for fields and properties
alike, and maps an interface's property to the analysed type's
implementation (FindImplementationForInterfaceMember). Tuple elements
and ref/out arguments go through the same Collect. A local holding this
is not followed: IHasId self = this; self.Id = id stays reported, as
the README now says.

Input class (AIS): the forms that assign a member in a constructor.
Plain and this. (silent before and after), a cast of this to an
interface the member implements explicitly, implicitly, to the second
of two it implements and to a generic one, a cast to the class itself
for a field, deconstruction through the cast, and out through the cast:
all seven were reported, the same cause, and are silent now. Two
constructors where one assigns through the cast still name the other,
and a cast of another instance assigns nothing.

Reverting the source fails both rows; mapping nothing (casts stripped,
the interface's property kept) fails both as well.

Closes #231

Defect-Class: AIS
Found-By: CA
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>
…signing this one

Collect took any IInstanceReferenceOperation as this. An object
initializer's receiver and a with expression's are instance references
too, of the object they create. So a constructor whose only assignment
was _ = new Twin(0) { Id = x } counted as assigning its own Id, and the
member nothing set on this instance was not reported. Only the
containing type's instance counts now.

Found running #231's input class (the forms that assign a member in a
constructor) across what the receiver is: this, a cast of it, another
instance, a local alias, an object initializer and a with. The last two
were silent; both report now.

Reverting the source fails the row: 0 reports where 2 are expected.

Refs #231

Defect-Class: AIS
Found-By: VA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…patch property

ASP.NET leaves out items and additionalProperties for a converter-backed
element, and PatchSchemaTransformer acted only on a Patch<T> itself. So a
Dictionary<string, Patch<string>> was {"type":"object"} and a
List<Patch<int>> {"type":"array"}, admitting anything, in 3.1, 3.0 and
2.0, while a Patch<T> property was its argument's schema admitting null.
The container branch now puts the patch's own schema back where ASP.NET
left it out, asked for through the context, so it is filled as a property
is: a described or replaced argument, a value object through its wrapped
type, 3.0's nullable form.

Input class: where a Patch<T> sits. Catalogue: PatchSchemaTests'
arguments (12 kinds, now 14 with Lines[] and Dictionary<string, int>, a
list of a component and a dictionary) in 6 positions (property, list,
array, Dictionary<string,>, IReadOnlyDictionary<string,>, a list in a
dictionary) plus a list as the whole body, in a minimal API and an MVC
controller, over OpenAPI 3.1, 3.0 and 2.0 and the three value-object
placements (components, InlineValueObjects, inlined by the host). A host
that inlines everything has no polymorphic argument: ASP.NET refuses a
plain property of one too. A wire test reads a map and a list of patches
as the document says they are read. The nullable patch the catalogue
found is the next commit.

Reverted per verify-the-guard: the 11 new document rows fail; the
dictionary arm alone fails them too.

Closes #232

Defect-Class: FCI
Found-By: CA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CaffeinatedCoder and others added 25 commits October 31, 2026 08:50
# Conflicts:
#	CHANGELOG.md
#	test/oracles/claims.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…assignment-silent-2-3-1

fix: CMTK0006 counts an assignment through a cast of this
…ema-2-3-1

fix: describe a container of patches, and a nullable patch, as a patch property
…r-captured-ordering-2-3-1

# Conflicts:
#	CHANGELOG.md
#	design/knowledge/README.md
… pass to each rule's focused tests

DroppedTaskVariantTests is one class of 1,838 rows, which xunit's default mode ran one after another. On CI
it kept the test step going for two to seven minutes after every other project had finished (#245). The
analyzer test project now runs with parallelMode "all" (xunit.runner.json), so every row of every theory
is scheduled on its own. No test there shares state: each builds its own compilation.

The four sweeps (DroppedTaskVariantTests, TypeArgumentConstraintKindTests, QueryDelegateConditionTests and
TypeArgumentSiteRun, which TypeArgumentConstraintVariantTests runs) set VariantSweep.Behaviors, which skips
the verifier's second run of each reporting case under '#pragma warning disable'. Whether a pragma silences
a rule is the same for every shape, and the focused tests of each rule (IgnoredOutcomeAnalyzerTests,
TypeArgumentConstraintAnalyzerTests, DefaultProducingCallAnalyzerTests) keep it. The verifier's
generated-code pass already never ran: every analyzer configures generated-code analysis.

Seeded twice, each reverted by its inverse edit:
- CMTK0003 dropping the first element of an array's tasks: DroppedTaskVariantTests fails 2 rows under the
  new mode, so the sweep still catches its own defects.
- CMTK0003 reporting a probe rule only below a leading '#pragma warning disable': IgnoredOutcomeAnalyzerTests
  fails 16 tests in the pragma pass, DroppedTaskVariantTests passes. The pass is now the focused tests'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rallel-2-3-1

test: run the analyzer sweeps' rows in parallel, and leave the pragma pass to each rule's focused tests
… SQL Server database once

After #250, CI's Linux leg ends every run, and the EF Core project is half of it: its two query sweeps are one
class each of 12,275 rows, which xunit's default mode ran one after another (#251). A row waits on SQLite, SQL
Server or PostgreSQL more than it computes: at four threads the project took 55 s instead of 123 s for the same
CPU. The project now runs with parallelMode "all" (xunit.runner.json). A row reads only what was seeded for its
model, and each SQLite class opens its own database per test.

SqlServerRoundTripTests opts out ([TestClass(DisableParallelization = true)]): each of its tests creates the
same tables in one database. Seeded by removing the opt-out: 5 of its 7 tests fail.

The whole suite found a race in the SQL Server fixture: DatabaseAsync's GetOrAdd ran its factory more than once
for callers that arrived together, and the first rows of a sweep dropped and created typekit_queries under each
other. Each database is now created once behind a Lazy, as the sweeps' own seeded databases are.
SqlServerFixtureTests asks for one database from 32 callers at once; with the plain GetOrAdd restored it fails
3 runs of 3.

The profile also answered the issue's other half: a CMTK0003 sweep row costs its own file, and reusing the
compilation or compiling the declarations to metadata saves nothing, so DroppedTaskVariantTests is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e fixture's create-once

On CI, parallel rows bought the EF Core project almost nothing (#251, #252's run): the database containers share
the runner's four cores with the tests, so what looked like waiting on a local machine with spare cores became
CPU. The Linux test step went from 9m 28s to 9m 17s, and the Windows one from 5m 17s to 7m 11s, as the projects
beside EF Core slowed. xunit.runner.json and SqlServerRoundTripTests' opt-out go.

SqlServer.DatabaseAsync keeps creating each database once behind a Lazy, with SqlServerFixtureTests as its guard:
GetOrAdd may run its factory twice for callers that arrive together, which rows run in parallel did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-3-1

test: create each SQL Server test database once, as rows arriving together would race it
…ordering-2-3-1

fix: let a query filter order the context's own values without a lambda
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orted-2-3-1

fix: CMTK0005 reports Array.CreateInstance, CreateInstanceFromArrayType and ArrayPool<T>.Rent
…-3-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…load-refused-2-3-1

fix: keep an overload of a generated member that differs by arity or ref kind
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-refused-2-3-1

fix: a base's private member of a generated name is hidden from the value object, not refused
The 2.3.1 section is dated 2026-10-31 and links blob/v2.3.1/. Shipped.md
moves its empty release header from 2.3.0 to 2.3.1, since Roslyn's release
tracking refuses a header after an empty release (RS2007); 2.3.1 adds no
rule. The comparison and the README's Status name 2.3.1, the TypeKit cells
checked against the release branch on 2026-10-31, none changed.

Closes #254

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: close 2.3.1: the changelog dated, the analyzer release recorded
…ngelog

main opened 2.4.0 after release/2.3.1 was cut from v2.3.0. The tag goes on
the release PR's merge into main and is checked against Version, so both
conflicts keep the release side; the tree is release/2.3.1's. 2.4.0 opens
on main again after the tag, with the baseline at 2.3.1.

Closes #256

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es-main

build: merge main into release/2.3.1, keeping 2.3.1's version and changelog
@CaffeinatedCoder CaffeinatedCoder added this to the 2.3.1 milestone Oct 31, 2026
@CaffeinatedCoder CaffeinatedCoder added chore Build, CI, tooling, packaging, refactoring with no behaviour change area/release Versioning, changelogs, packaging and release wiring labels Oct 31, 2026
@CaffeinatedCoder
CaffeinatedCoder merged commit ad25baa into main Oct 31, 2026
25 checks passed
@CaffeinatedCoder
CaffeinatedCoder deleted the release/2.3.1 branch October 31, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/release Versioning, changelogs, packaging and release wiring chore Build, CI, tooling, packaging, refactoring with no behaviour change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Epic: 2.3.1

1 participant