Repository navigation
Release/2.0.0 - #2
Merged
Merged
Conversation
… a constructor rule and the text guards (decision 43) Reopens the goals: no Try type, since it is Result<T, Exception> renamed; no Unit, since Result<TError> is the value-less shape. Constraints a type parameter cannot declare and the parameterless-constructor guarantee ship as attributes and analyzer rules (CMTK0010, CMTK0011), not aspects. The text guards ship as string extensions and TextFault in the base package, with no value object and no .Text package. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ch part must settle Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…decision 44) The version number names the API, never the runtime: a net11.0-only 2.0.0 would leave the .NET 10 LTS line on 1.5.x for an STS release. net11.0 joins a 2.x minor when a feature needs it; C# 15 unions and the net10.0 drop are 3.0.0. The preview becomes 2.0.0's own, from main. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The baseline had stayed at 1.5.0 when 1.5.1 shipped; the convention that checks it excludes the version being built, so it only saw the miss once the version moved to 2.0.0. The changelog section reads Unreleased. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…xtFault AsText() is for text a person wrote: trimmed, space separators folded to U+0020, composed to NFC, refused when a character renders as nothing; the two zero-width joiners are kept. AsIdentifier() is for a value another system issued: trimmed, otherwise as it arrived, joiners refused. Both scan by Rune, answer Result<string, TextFault>, and read a null as Blank. The tests pin the refused characters by code point; a seed that dropped the named invisible list failed twelve of them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rough one factory The async trap is an [Obsolete(error: true)] overload over a task-returning delegate, which the compiler picks over Func<T>; a lambda that only throws needs its return type stated. No ValueTask overloads: ambiguous for every async lambda. Cancellation passes through; Native AOT clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… data Catch(() => …) answers Result<T, Exception>, Catch(Action) Result<Exception>, Catch<T, TException> catches that type only, CatchAsync awaits the task. OperationCanceledException passes through every overload; a task-returning delegate given to Catch is a compile error naming CatchAsync; a delegate that returns null throws as Result.Success(null) does. Seeded: without the cancellation filter and the trap overloads, seven tests failed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e's first code fix An attribute on an interface or base class, and an analyzer rule on every concrete type under it, directly or through another: a parameterless constructor of the given accessibility, or an error where the host would have failed by reflection. A class with no constructor satisfies Public; a missing constructor is reported at the type, a wrong accessibility at the constructor. The fix writes or changes it, and skips a type with a primary constructor. The fix is its own assembly, nested in the analyzer project and packed beside it (RS1038). Seeded: the sample without its pragma fails the build twice; the check disabled fails four tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n type arguments where C# cannot The real rule over a library, a consumer with one seeded breach per claim and a Razor component: every seeded line reported with its count, nothing else; a forwarding type parameter is a breach unless it carries the attribute or struct/new() proves it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and CMTK0010 Four attributes in the base package say what C# has no where-clause for, and the rule reports a type argument that breaks them at any closed use: at the argument where a generic name is written, at the call where the type arguments are inferred. A forwarding type parameter must carry the attribute, or the requirement would end at it; struct proves [Sealed] and [Concrete], new() proves [Concrete]. Seeded: with forwarding always proven, the two tests that guard it failed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… generation and Native AOT MakeGenericType + Activator has no constructor to invoke under AOT, a static field read by reflection is trimmed, an open generic converter in [JsonConverter] is SYSLIB1220; a registry the closed type's static constructor fills, run by the factory, passes every claim. A primitive inside a patch takes the serializer's built-in converter; the document inlines a patch as its argument, never required, admitting null. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
default is absent, the one meaningful default of the package, and the analyzers leave it alone. Reading never refuses a null; a value object inside a patch keeps its own rule. Match, TryGetValue, IfValue, IfPresent and ToOption apply it without reflection; writing an absent patch is refused and WhenWritingDefault leaves it out. The converter is registered by the closed type's static constructor, which is what works under Native AOT; a primitive takes the serializer's built-in converter. AddTypeKit() describes a patch property as its argument's schema, inlined, not required, admitting null. Seeded: without the registration seven tests fail, without null handling two. The consumer smoke test exercises a patch under Native AOT. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… on Metalama 2026.1 and .NET 10 Name from the field through a BeforeTypeConstructor initializer, All in declaration order, Match with one delegate per member, Parse and JSON by name, a private constructor where none is declared. One workaround: Func<TResult> cannot be built from the method builder's type parameter, so the template's own parameter type is copied per member. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…reads docs/ becomes the home of the topic pages, every one compiled like a README (decision 45), and nothing in it is exempt. The plan and the design notes move to design/, which is a solution folder, and every reference follows: AGENTS.md's workflow, the workflows' comments, the smoke test, the packaging comments, the base README's design links and the spike READMEs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e of contents in the root README
Eleven guides, one topic each and written around examples: value objects, Option, Result, written
text, type constraints, JSON, EF Core, ASP.NET Core, Native AOT, logging and secrets, and testing.
The package READMEs stay the reference for each package, since nuget.org shows them and every
exception message and analyzer help link lands in one; a page is the guide for a topic, and the same
code block is never in both.
ReadmeSampleTests now discovers docs/*.md beside the READMEs: every C# block in a page equals one
region in test/CodoMetis.TypeKit.Samples/Docs (named {file name}/{name}), a page must be linked from
the root README's Documentation table, and the table links only pages that exist. Each stated output
is read from the page by the test beside its region, as for the READMEs. The Option and Result pages
list the complete public surface, and a test reads the types by reflection so a member added without
a row fails. DocumentationLinkTests checks every relative link and anchor in the READMEs and pages
against the files and their headings, derived as GitHub derives them.
Seeded, each failing exactly its guard: a page with an unmirrored block, a page not in the table, a
table link to a missing page, a page block edited without its sample, the Documentation section
renamed, a surface row dropped, and a stated output changed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
….TypeKit.Generators A sealed partial class whose every instance is a public static readonly field, with constant data through a private constructor. The aspect adds Name from the field, QualifiedName, All in declaration order, Match with one required delegate per member, TryParse and Parse by name, ToString, a JSON converter that writes the name and reads the member back, a private constructor where none is declared, and the discovery attribute. CMTK1013 to CMTK1018 refuse a declaration that cannot be generated. The EF satellite maps a set to a text column of the name, the OpenAPI satellite to a string enum. Seeded: without the names initializer five of nine tests fail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ring a struct to default Closed sets, from a generation review of 09d8b78: - A member initialized with another (`Legacy = Open`) renamed the real one: Name, JSON and the column said Legacy, All held Open twice. CMTK1019 now refuses a member not initialized with `new` where it is declared, and with nothing else. - A hand-written ToString() was replaced silently. It is kept as the seam; any other generated member written by hand, by signature, is CMTK1011 instead of a Metalama error naming no fix. Overloads of another signature are kept. - An instance the set's own code made beside its members had a null Name, written as JSON null and stored as a null column. CMTK1016 refuses any constructor that is not private, CMTK1020 a record (`with` makes a copy that is no member), and Name, ToString, Match, JSON and EF throw NotAMember for such an instance. - The generated TryParse lacked [NotNullWhen(true)], so a found member was CS8602 at the call site. - CMTK0002 now covers IClosedSet<TSelf>, as IClosedSet's docs claimed, and BuildOutcomeTests allows CS0535 only on the refused sets. - A JSON null is the serializer's to read, as for any class; the converter comment claiming a refusal is corrected and both behaviours are tested. Patch<T>.IfPresent handed a clear over as default(T) for a struct: a zero, or an uninitialized value object. It is now two extensions, a class T receiving T? and a struct T receiving Nullable<T>, so a clear is null. Seeded: each fix reverted fails its guard (14 failures across 7 seeds, then the TryParse attribute, the CS0535 guard, the trailing call, the record and the overload probe one by one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd silent as dictionary keys From the open items of the closed-set review of b514d10, each measured first: - Match takes one Func<TResult> per member in declaration order, so an argument by position followed the order: reordering two members swapped their branches and compiled. CMTK0012 (Error, 2.0.0 is a major) requires every argument named, in the declaring project too, where the call does not bind before weaving; its code fix names them as they bind now. - Match accepted a null delegate for a member not matched. Every delegate is checked before a branch is picked, as Option's and Result's are. - A set as a JSON dictionary key threw NotSupportedException both ways. The converter writes and reads the member's name as a key. - A member named Match, Parse, TryParse or ToString failed the aspect (LAMA0506, LAMA0611), one named All or Name was told by CMTK1011 to be removed, and members Open and open gave Match one parameter twice (LAMA0611, CS0100). CMTK1021 refuses each and says to rename the member. A member named New or Default was fine: Metalama escapes the keyword. - dotnet ef dbcontext optimize wrote the mapping's internal helpers out as C# in the application (CS0122), so no closed set reached a compiled model or Native AOT. ClosedSetConverter<TSet> is the public converter whose static methods the compiled model calls, and the JSON reader/writer is composed with both types known: without it a closed-set parameter failed under Native AOT. A collection of members and a JSON column are tested. - The consumer smoke test's aot consumer publishes a set's JSON (a value and a key), its OpenAPI enum, its column, a parameter and a JSON column. - CatchTests.The_sample_runs_as_written raced a 100 ms HttpClient timeout against a refused connection; a timeout is a cancellation, which CatchAsync lets through. A handler that refuses at once replaces it. The EF README records three EF Core 10.0.12 limitations measured on the way: --precompile-queries reads woven types of its own project without bodies, a precompiled query cannot read a collection of converted elements (EF's own EnumToStringConverter fails alike), and an entity with any primitive collection fails to materialize in one. Seeded: the null checks with the key overrides (2 failures), CMTK0012 (7), CMTK1021 (10), the member exclusion from CMTK1011 (1), the composed JSON reader/writer under Native AOT (the parameter query), each reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every overload let an OperationCanceledException through, and HttpClient throws a TaskCanceledException over a TimeoutException when its Timeout elapses: CatchAsync(() => client.GetAsync(...)) threw on the most common failure of the call the README shows it with (measured with a 1 ms timeout). A timeout is the call failing, not the caller cancelling, so a cancellation over a TimeoutException is caught by every overload, and any other cancellation still passes through (decision 46). The test uses a real HttpClient whose handler waits for its token, so the timeout is the only way out and nothing races. Seeded: the old filter fails two tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…very row in memory The IEnumerable<T> overloads were the only ones, so on an EF Core query FirstOrNone(predicate) and FirstOrError(predicate, error) compiled the predicate, read the whole table and ran it in memory: no WHERE, no LIMIT, no warning. IQueryable<T> overloads take an expression and Take(1), so the provider runs both (WHERE ... LIMIT @p). LastOrNone and LastOrError on a query are compile errors: a query has no last element until it is ordered, and they would read every row (decision 47). CodoMetis.TypeKit.EntityFrameworkCore adds FirstOrNoneAsync, with or without a predicate, in EF Core's namespace. CMTK0009 names it for FirstOrDefaultAsync where the project references the satellite, and names FirstOrNone and Skip(index).FirstOrNone() for a query's FirstOrDefault and ElementAtOrDefault. Measured under Native AOT: neither is precompiled by EF 10.0.12 ("Query wasn't precompiled"), so the EF README gives the call-site form there. Seeded: without the expression overload, the binding tests and the EF translation tests fail (EF.Functions.Like throws in memory, the SQL has no WHERE). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A closed set had no type converter, so the reflection-based configuration
binder, MVC and anything else reading a string through TypeDescriptor
left a set property null with no error; and no IParsable<TSelf>, so
generic code parsing through T.Parse could not take one.
The generators implement IParsable<TSelf> explicitly, beside the set's own
Parse(string) and TryParse(string, out), since a provider means nothing to
a name, and add a {Set}TypeConverter that reads with Parse (a refusal names
the set, never the input) and writes the name. A hand-written
IParsable<TSelf>, [JsonConverter] or [TypeConverter] on a set is CMTK1011,
where each failed inside the generated code; the converter's name joins
CMTK1021's.
Tested through TypeDescriptor, through T.Parse, and with the real
configuration binder, a list of members included. Seeded: without the
attribute the converter and binding tests fail; without each CMTK1011
check its build-outcome cases fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Destructure.WithTypeKit() recognised value objects only, so {@tier} and
every closed set inside a destructured record were logged by their
public properties: ProbeTier { Rank: 2, Perk: "Priority support.",
Name: "Premium", QualifiedName: ... }, the set's constant data in the log
and a hand-written ToString() bypassed (measured). The policy reads the
GeneratedClosedSetAttribute beside GeneratedValueObjectAttribute, so a
set is a scalar of its ToString(): its name, or its own text.
An instance that is no member has no name; Serilog catches the
ToString() refusal and logs that it could not capture the value, pinned
by a test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sult pipeline A repository returning Task<Option<T>> broke the README's pipeline at its first step: MapAsync on it failed to infer against the Task<Result> overloads (CS0411), and an option had no asynchronous step at all. Option<T> gains MapAsync, BindAsync and TapAsync over callbacks that return a Task, and Task<Option<T>> gains MapAsync, BindAsync (each with a synchronous and a Task overload), FilterAsync, TapAsync and ToResultAsync, the step into a Result pipeline. The shapes are the Task<Result> continuations': every delegate, and ToResultAsync's error, is checked before the option is known, and a callback returning a Task is waited for. The README's Option section shows a lookup into a Result, compiled. Seeded: FilterAsync without its delegate check fails the null-delegate case on a faulted task; ToResultAsync checking its error only after the await is cancelled at its timeout; TapAsync not awaiting fails the waited-for test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…09 lacked CMTK0009 sent SingleOrDefault to the overload that takes a default value, which for a no-default struct is the hazard itself, and ElementAtOrDefault to Skip(index).FirstOrNone(). SingleOrNone answers the only element or None, and throws for more than one as SingleOrDefault does, since many is a defect and not absence; ElementAtOrNone answers the element at an int or an Index. On a query both ask the provider for what they need, two rows and one, and a negative index is None without a query, since Skip(-1) skips nothing. The EF Core satellite adds SingleOrNoneAsync, and CMTK0009 names each. Transpose turns an Option<Result<T, TError>> into a Result<Option<T>, TError> and back, and Option<T>.Traverse(selector) validates an optional input when present: dto.Phone.ToOption().Traverse(Phone.Create). Seeded: SingleOrNone answering the first of many, the query's ElementAtOrNone without its negative guard, and Transpose dropping an error each fail their tests, in the base and the EF Core suites. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges claude/v2-docs-structure-21919f: the topic pages under docs/, compiled like the READMEs, and the plan moved from docs/plan.md to design/plan.md (decision 45). The plan's own edits since the branch point followed it through the rename; the CHANGELOG conflict is resolved with the Documentation entry at the end of Added. Fixed after the merge, what the release branch had added or changed since the pages were written: - The two spike READMEs written since, ClosedSets and MergePatch, linked ../../docs/plan.md; they link ../../design/plan.md. - The Option and Result pages list the complete public surface, checked by reflection: rows for SingleOrNone, ElementAtOrNone, the async steps and continuations of an option, ToResultAsync, Transpose and Option.Traverse, and the query forms of FirstOrNone and FirstOrError. - The Result page said every cancellation passes through Catch; a timeout is the error (decision 46). The Option page names the query forms. - The root README listed Patch<T> and the closed sets as still to come; they have landed, documented in the base and generators READMEs until their pages are written. Its comparison sent smart enums to another library, which TypeKit's closed sets now are. - The Native AOT page's configuration binding covers a closed set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two topic pages for the 2.0.0 features that landed before the docs did, each linked from the root README's Documentation table, its blocks compiled from test/CodoMetis.TypeKit.Samples/Docs and its stated outputs tested. docs/merge-patches.md: the three states and what each member does with them, applying a patch to an entity (a required field refusing a clear, a struct's clear arriving as null), a value object inside a patch keeping its rule, writing a body with WhenWritingDefault, the endpoint and its document, source-generated contexts, and what a patch is not (a deep merge, an Option). docs/closed-sets.md: declaring a set with data on its members, Name, All, Parse and their refusal, Match with named arguments, JSON values and dictionary keys, the OpenAPI enum, a route parameter (and ASP0020 in the declaring project), the EF Core column and its SQL, configuration binding and the source generator that cannot, logging, every build error, and when an enum is still right. The tests beside them also check what the prose claims: the document's patch schemas, the string enum, the route binding and its 400, the text[] column on PostgreSQL (the EF README said a JSON array; corrected there too). Seeded: a wrong stated output in either page fails its test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ult type Step 8 of 2.0.0, after spikes/FaultResponses, run and answered: the candidate was the satellite itself, and its thirteen claims are tests here and in the smoke test's Native AOT consumer. CodoMetis.TypeKit: FaultKind, IFault and IFaultSet<TSelf>, a closed set whose members carry a kind and an optional description; and Fault<TFault>, a reason with a sentence about one occurrence, refused by JSON and, when default, by CMTK0001. CodoMetis.TypeKit.AspNetCore, each part opt-in and decision 38's default response unchanged: - one internal FaultType<TFault>, so an enum and a closed set take the same path through the mapping, the startup check, the document and the response; - AddFaultResponses<TSet>() answering every member by its kind (404, 409, 422, 403, 503), its description as the detail; a FaultResponse per member with a type and a title, listed in the document; - Fault<TFault>'s detail as the problem's detail, keyed on the reason's type; - WithFaultStatus on an endpoint and [FaultStatus<TFault>] on an MVC action, for that endpoint's response and document entry; the startup check refuses a status for a fault type the endpoint does not answer with, and an attribute naming no member; - ToHttpResult(success), the endpoint's own success such as Created, its TSuccess annotated as ASP.NET annotates Results' (IL2091), not suppressed; - IFaultFeature on the request while the problem is written, and FaultResponseOptions.QualifiedFaultNames. FaultProblem<TFault> takes notnull where it took struct, Enum: the first difference the 1.5.1 baseline reports (CP0021), suppressed alone so any other break still fails the pack (decision 48). Design choices in decision 49. Seeded: the endpoint override, the feature's removal, a kind's status, the qualified names, the detail, the stray-status check and the document's problem types, each failing its test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…line's verdict Step 9 of 2.0.0, the deliberate breaks (decision 50). CMTK0003, a dropped Result or Option, is the one Warning raised to Error. The bar: every report can be answered in the code by a form that says what is meant, never by a suppression alone, and the reach is complete, since an Error cannot learn within the major. A drop nothing depends on is correct, and '_ =', a cast or a loop variable named '_' says so. Its reach is a return type, not a list, so it was complete once its 1.x gaps were closed: - Wait on a task of a result the expression made, whatever reads the bool a timeout overload returns; - tasks of results the statement made converted to plain tasks, an array, a sequence or a span of them, as Task.WaitAll and WaitAny take; an explicit cast stays silent, as a cast to Task does; - a foreach or await foreach over results its header made whose body never reads the iteration variable; - a lazy sequence of results a call made without being given any, materialised by ToList or ToListAsync and dropped. CMTK0005, CMTK0006, CMTK0008 and CMTK0009 stay Warnings: correct code looks the same and has only a suppression to say so (an array filled in a loop, a field set in OnInitialized), or the reach would freeze (CMTK0008's four known gaps, CMTK0009's list of framework methods). CMTK0010-0012 needed nothing: new at Error in this major, reporting only on what it adds. The 1.5.1 baseline over all seven packages: under package validation's default rules one difference, CP0021 on FaultProblem<TFault>, suppressed alone (decision 48); with the attribute and parameter-name rules on as well, 29 compiler-emitted attributes (state machines renumbered by added members, nullable-context bytes) and no renamed parameter. Guards: AnalyzerReleases records the change (RS2001 refuses a descriptor that disagrees, RS2007 a change that is none); AnalyzerHelpLinkTests holds the README's severity column to every descriptor; the build-outcome consumer no longer raises CMTK0003 itself, so its reports prove the default. Eleven seeds for the new forms, the severity seeded back to Warning (15 analyzer tests and 3 build-outcome tests fail), each failing its guard. Consumer smoke test passed, Native AOT included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…luded A review found the async refusal only on the untyped overloads. Measured in a consumer compilation, these compiled and answered wrongly: - Catch<TException>(() => FetchAsync()), a method group, and an async lambda: bound to the command form's Action, the task discarded, an async lambda as async void, whose exception reaches no caller; - Catch<Task<int>, TException>(...) and Catch<Task<int>>(...): a success holding the task; - Catch(() => FetchValueAsync()) and Catch<TException>(() => stream .ReadAsync(buffer)): a ValueTask held, or discarded. Catch<TException>(Func<Task>) is a compile error naming CatchAsync, and so is Catch<TException>(Func<object?>): any value the command form would discard, which is the only overload a ValueTask<T> can reach. The spike's finding holds: ValueTask refusal overloads beside the Task ones made every async lambda CS0121, measured again. A task as the value, spelled or inferred, is refused at run time before the delegate runs, as is a task whose value is a task in CatchAsync; the message links to the README's new "A task given to Catch". Refusals over two type arguments were measured and dropped: they make a lambda that only throws, or returns null!, ambiguous, and what they would refuse is already a conversion error. The command form no longer discards a value: Catch<FormatException>(() => int.Parse(s)) is a compile error naming Catch<T, TException>; a statement lambda is a command. Catch is new in 2.0.0, so no shipped call changes. Seeds: each refusal overload, each of the four guards, and the guard's ValueTask branch, each failing its test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A review found patch schemas a validator reads wrongly. Measured in the probe document, 3.1 and 3.0: - a string enum's patch: no type of its own, so admitting null wrote "type": "null" beside its names, and the schema admitted nothing; - a closed set's patch: "type": ["null", "string"] with an enum that lists no null, so null was refused; - Patch<object>: "type": "null", so it admitted only null. Null now joins what restricts the values: an enum's list gains null, a type gains null (3.0's nullable), and a schema that combines subschemas is kept whole in a branch beside a null one. A schema that restricts nothing stays so. ASP.NET's own polymorphic schema needs no branch, its derived schemas stating no type; a host's oneOf of typed schemas does. The test validates values against the document as a client would, with JsonSchema.Net 9.4.0 (test-only), the property's references inlined and 3.0's nullable read as 3.0.3 defines it: for eleven kinds of argument, a patch accepts null and judges every other value exactly as the argument's own schema does. Seeds: the original code, the enum's null, the branch and the typed path, each failing the test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the refused one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…usals, TryParse, obsolete and default-instance changes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Exception's path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a default ulong key's throw at Add is documented A key over char is no longer declared generated, on any provider, as 1.5.1 left it. No provider generates a char key, and on SQL Server, whose column is an nvarchar(1), the value object's key declared generated was given a character of a GUID string at Add, which was then stored: EF picks the generator that fills a value object's key by the column's type, not by the wrapped type. The application's own ValueGeneratedOnAdd() or value generator still applies. A default '\0' is stored as it is. A key over ulong stays declared generated, and a test pins what the docs now say: a default key throws OverflowException at Add on SQL Server and PostgreSQL, whose column is a decimal, and ValueGeneratedNever() takes an assigned key. Decision 63 refines decision 58 with the measurements per provider; the README, the docs page, the design note and the convention's XML docs state the rule with these two exceptions in place of parity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eys a provider generates, that char keys are assigned, and what a default ulong key does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, as a Kelvin sign and a K do CMTK1021 grouped a closed set's member names ignoring case ordinally, which stood in for both collisions it exists for and missed one: the Kelvin sign (U+212A) lower-cases to k, so a member starting with it and one starting with K gave Match one parameter name twice (LAMA0611, CS0100), while ordinal ignore-case, which upper-cases, tells them apart. CMTK1021 now compares by each transform itself, TryParse(ignoreCase: true)'s and the one that names Match's parameters, and says which of them collides; Express and EXPRESS are told they collide in TryParse alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2026.1 or 2027.0 preview A template's documentation comment is not copied to the member introduced from it, declaratively or programmatically, and Metalama.Framework has no API that sets one (its only comment API writes a // comment into a body). The consumer's documentation file lists only the members written by hand, also when the aspect library writes its own, and on 2027.0.3-preview too; the repository's woven probes show the same. An undocumented introduced member is no CS1591 for a consumer that documents itself. The IDE's design-time view could not be measured from a command-line build. Generated members stay undocumented in 2.0.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt, as a referenced one does Where the host inlines closed sets, a nullable set property was the bare string enum, so a validator or a generated client refused the null the application reads, while a referenced one admits it around its reference (decision 36). Inlined, the property's own schema now admits null in its type and among its values, in OpenAPI 3.1 and 3.0; the shared component never does. Elements and parameters stay as a referenced set's, which ASP.NET describes without null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The problem's type and the document's list of problem types wrote FaultResponse.Type with Uri.ToString(), which unescapes: https://example.com/problems/a%20b went out with a space, an escaped non-ASCII path as the letter, so the type was no URI. An absolute type is now written as Uri.AbsoluteUri, which keeps an escape escaped and escapes an unescaped space, and a relative one as given, once where the faults are mapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…apart are mapped to one fault Every problem ToHttpResult() writes names its member in fault, with a description or without, so a sign-in that answers 401 for an unknown user and a wrong password still says which. The AspNetCore README, docs/aspnet-core.md and docs/logging-and-secrets.md now say the name is part of the response's contract, and show mapping both causes to one member before ToHttpResult(), as compiled code with a test that the two then answer alike. An opt-in that withholds the name is for 2.1 (decision 64). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he enum's values Where the host inlines the wrapped enum as well as the value object, a nullable value object over an enum written by name was the enum without null, where ASP.NET writes a nullable enum property with null among its values (decision 36); the null went into the type alone, and a by-name enum has none. The inlined schema now lists null among its values too, which also covers a value object over an inlined closed set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…raint that proves the attribute Before it ships as a frozen Error, two kinds of false report are removed. [Concrete] refused an enum, a delegate and dynamic. An enum is a value type, which new() and struct accept, and a struct-constrained parameter that proves [Concrete] could already be one; a delegate is a sealed class, made from a target and a method as a class with a parameterised constructor is made from its arguments; dynamic is object. Their abstract bases (Enum, Delegate, MulticastDelegate, ValueType) are still refused. A forwarded type parameter had to carry the attribute even where its own constraints leave no breaking argument. Now a bound that is or proves a record proves [Record] (only records derive from records, CS8865); a bound proving [Sealed] proves [Sealed] (a sealed type has no other subtype); whatever proves [Sealed], the attribute included, proves [Concrete] (a sealed type is never abstract). Nothing but the attribute proves [Abstract]. No missed form was found among the neighbours measured. The docs page, the Analyzers README, the attributes' XML docs and the design note state what each attribute accepts and what proves it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ld on a partial type with a primary constructor The fix laid the whole type out again through the formatter and built its tokens with elastic trivia. Measured: the members' own spacing was rewritten and tabs became spaces; a constructor without modifiers got its doc comment a second time after its attribute and its space moved ([Obsolete] internal X()); a comment after a replaced modifier was dropped; in a CRLF file the added lines, and the brace's own, ended in LF. It now writes its own trivia and asks no formatter. The accessibility takes the place of the first keyword written, keeping what led it and what followed the last, or goes before the name after the attributes. The constructor is added as the first member, indented as the members are (a tab where they use tabs, one level inside the braces of an empty body), with the line ending of the brace it follows; a type on one line gets it on that line, and one ending in ';' a body there, the ';''s comment kept after it. On a partial type whose other part has a primary constructor, the added constructor did not chain to it (CS8862); the fix is withheld there, as it was for a primary constructor in the same part. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ach change in one line The detail lives in docs/migrating-to-2.md and the topic pages; the section links the guide absolutely, since the release page resolves no relative link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e value's type A value is never null, a clear being the Null state, yet over a nullable type every member handed it as nullable: on a Patch<string?>, p.Map(s => s.Trim()) warned (CS8602) and Traverse(Email.Create) did not match (CS8622), in a build where warnings are errors, and the docs declared Patch<string?> fields. Handing the non-null T to a Patch<string?> and a Patch<string> alike cannot be written (measured): overload resolution ignores nullability, so one member serves both; over Patch<T?> where T : class a Patch<string> receiver is CS8620, blocks over Patch<T> and Patch<T?> lower to one signature (CS0111), and [DisallowNull] on a delegate's parameter does not reach a lambda's. So T is notnull, as Option's is: Patch<string?> and Patch<int?> are warned once at their declaration (CS8714), and Patch<string> is the field that can be cleared. IfPresent over a class takes T : class and still hands a clear over as null. The docs, the samples, the OpenAPI tests and the smoke consumer declare Patch<string>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Choose(selector) sat with the sequence members whose T is notnull, because their element becomes an option's value; Choose's element is only the selector's argument, and only what the selector finds must be non-null. So IEnumerable<string?>.Choose(s => s.ToOption()) warned CS8714 and CS8622, and IEnumerable<int?> CS8714, where warnings are errors. It moves to an unconstrained block over IEnumerable<T>, in a class of its own, SequenceOptionExtensions: beside Option's notnull sequence block, a second block over IEnumerable<T> regroups both in metadata, and package validation read that as FirstOrNone and LastOrNone removed from 1.5.1 (CP0002, CP0021; measured), as ValueTypeOptionExtensions already sits apart for OrNull. The receiver is a sequence, never a bare T, so decision 31 holds, and Choose() over options is unchanged. The option page's surface table reads the new class and says the elements may be null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…every row A lambda written at the call is an expression and binds to the query's FirstOrNone, SingleOrNone and FirstOrError, which the provider runs. A Func<T, bool> held in a variable, or a method group, is not: it binds to the sequence's overloads, which read the whole table and run the condition in memory, without a warning. The option page, the EF guide, the base and EF READMEs and the sequence overloads' XML remarks say so and name the way out, an Expression<Func<T, bool>>; FirstOrNoneAsync takes only an expression. An EF test pins the SQL: no WHERE and no LIMIT for a Func and a method group, a WHERE for an expression held in a variable. No analyzer yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uide carry Patch's notnull T and FirstOrNone over a Func Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…9.0.21, past GHSA-846f-f764-76mx and GHSA-2fmv-rv2c-59x7 The analyzer-testing packages bring 9.0.6 through NuGet.Packaging, and the SQL Server provider 9.0.11 through Microsoft.Data.SqlClient. No shipping graph has it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…implicit base() binds to nothing A base with only constructors that take arguments (CS7036), one the type cannot access (CS0122), or two that base() could each be (CS0121): the fix wrote a constructor that did not compile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aration
The generators name each member and fill All after the static field initializers and before the
static constructor's body, so Open = Closed in that body made Closed.Match take the open branch and
Parse("Open") write "Closed"; in another field's initializer it gave one instance two names. Every
write is reported: an assignment, a deconstruction, an increment, a ref or out argument and a
writable ref taken to the member, each also through a ref conditional. A ref readonly local or
field, declared or reassigned, only reads and is silent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed packages, in the declaring project on woven code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nterface method called through a derived interface There the method is its own implementation, so it was taken for a method it implements and its constraints were asked for again, without end. It is skipped, and each type parameter is asked once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed for, no longer reports CMTK0003 Task Log() => pending.TapAsync(log); and held.TapAsync(log).Wait() leave the outcome with the caller, as the same tap as a statement does, on a Result and on a Task of one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ema.Net 9.4.1 10.0.402 ships the October servicing, which fixes six SDK CVEs, and the release packages are built with it; pinned rather than left to latestPatch, so no workflow builds on 10.0.401. Metalama stays on the latest 2026.1 (decision 4), and its compiler moves to 2026.1.20. The other floors stay: EF Core Relational and AspNetCore.OpenApi 10.0.12 (no advisory names either; the EF test providers stay with them so the tests run on the floor), Serilog 4.0.0, Roslyn 5.0.0 (decision 25). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rosoft's build, a running Docker, the AOT toolchain Measured 2026-10-14: on Microsoft's SDK 10.0.402 the smoke test's Native AOT consumers link with no LIBRARY_PATH, which Homebrew's dotnet formula needed; without Docker the PostgreSQL round-trip tests fail with DockerUnavailableException and the other EF tests pass. Also the stale workload set an SDK upgrade can leave, the IDE's CLI path, and which dependencies Dependabot raises. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CHANGELOG.md dates the 2.0.0 section 2026-10-14. AnalyzerReleases.Shipped.md records "Release 2.0.0": CMTK0010 to CMTK0013 new, CMTK0003 raised from Warning to Error; the unshipped file keeps only its header. The roadmap keeps what remains before the tag: the merge and the tag itself. ChangelogTests fails with the rows left unshipped under the dated section, and passes with them moved. Release build 0 warnings; full suite 3698/3698. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decision 65 changes decision 44's preview of 2.0.0 from main: the user dropped it, so 2.0.0 is tagged without a 2.0.0-preview.N on nuget.org. The consumer smoke test already installs the packed packages, Native AOT included, in CI and in the release workflow before the approval gate. The roadmap loses the preview step; what remains before the tag is the merge and the tag. Convention tests 972/972. 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.
The 2.0.0 release branch. The changes:
CHANGELOG.md, section 2.0.0, which becomes the GitHub release notes. What builds or runs differently since 1.5: docs/migrating-to-2.md. Decisions 42 to 64 record why.Release prepared: the changelog section is dated, and the analyzer rows (CMTK0010 to CMTK0013 new, CMTK0003 raised to Error) are in
AnalyzerReleases.Shipped.mdunder## Release 2.0.0. Merged beforev2.0.0is tagged, so the diagnostics' help links reach headings that exist onmain.Verified locally on SDK 10.0.402: full suite green, Release build 0 warnings, consumer smoke test incl. Native AOT passed.
Merge with a merge commit (not squash, not rebase), as part of the release steps in CONTRIBUTING.md.
🤖 Generated with Claude Code