fix: refuse a comma in a name only where the serializer does - #69
Merged
Conversation
EMN0004 reported a comma inside a declared name on every enum, and the start-up check refused one, on the reading that a comma separates values everywhere so a name carrying one can never be read back. The first half is true and the second does not follow. System.Text.Json looks the trimmed value up as one name before it splits anything. Measured on an enum declaring a, b and a,b: it answers "a,b" with the member of that name and "a, b" with a | b. It refuses the shape only on a [Flags] enum, which its own message spells out — "Flags enums must additionally not contain commas". So this package refused a contract the serializer accepts, which is what EnumContract calls the one thing it promises never to do. Worse than the refusal was what the binder did once the shape got past it: TryParse split on the comma without ever trying the whole value, so "a,b" read as a | b — a different member, silently. EMN0004 and the start-up check now stop where the serializer stops, and TryParse tries the whole value as a name before splitting. That reorder cannot change any contract that was legal before, since none of them could contain a comma — and the suite agrees: three tests failed, all three the ones pinning the old decision. They are replaced by fixtures rather than by flipped assertions. Two go into the derived parity corpus, which compares every casing and every ordered pair of a,b,"a,b" against the serializer; reverting TryParse fails them with the divergence spelled out. One goes into the OpenAPI host, where the name reaches the published closed list and the server has to answer to it — and where "news, world" is refused, so the two readings are held apart rather than assumed distinct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5U2BZXpHQr7YcfNHVx9dA
Reefact
force-pushed
the
claude/emn0004-justification
branch
from
August 11, 2026 10:57
5e015a9 to
8c50dd9
Compare
17 tasks
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.
Summary
EMN0004reported a comma inside a declared name on every enum, and the start-up check refused one, on the reading that a comma separates values everywhere so a name carrying one can never be read back. Measured against the serializer, that is false off[Flags]— and the binder did worse than refuse the shape: once past the check it read"a,b"asa | b, a different member, silently.What
System.Text.Jsonactually doesMeasured, not read off a specification:
a,banda,b"a,b"Ab— the whole name wins over the split"a, b"a | b— no name is spelled with that spacenews,world"news,world"round-trips[Flags], declaring"read,write"InvalidOperationException: "Flags enums must additionally not contain commas"So the serializer looks the trimmed value up as one name before it splits anything, and refuses a comma inside a name on
[Flags]alone. Refusing it everywhere made a registered enum stricter than the same enum left alone — whichEnumContract's own remarks call the one thing this package promises never to do.Type of change
Changes
EnumContract.TryParselooks the whole trimmed value up as a name before splitting, and splits only when that misses. The reorder cannot change any contract that was legal before, since none of them could contain a comma — and the suite agrees: only the three tests pinning the old decision failed.MalformedNameProblemtakesisFlags; the comma test is the one that reads it, because it is the one the serializer scopes that way.Problem.CommaInName→CommaInFlagsName, message reworded.EnumContractAnalyzerresolvesSystem.FlagsAttributeat compilation start and reportsEMN0004only on an enum carrying it.CommaInName→CommaInFlagsName. Title, message and description inResources.resxall say what the rule is scoped to.CommaInsideAName(a,b,a,b) andCommaBesideCsharpNamesjoinReadParityTests, whose derived corpus compares every casing and every ordered pair against the serializer;CommaInsideANamejoinsFormattingParityTests. RevertingTryParsefails both with the divergence spelled out —'a,b': System.Text.Json reads 4, this library reads 3.Topicjoins the OpenAPI host, where the name reaches the published closed list: the document advertisesnews,world, the server binds it as one value, andnews, worldis refused — so the two readings are held apart rather than assumed distinct.[Flags]one whose other members are ordinary, and its message names[Flags].EMN0004.en/fr.mdrewritten (the "Why it matters" section was two false sentences), both index tables, theEMN0002cross-reference, and a paragraph incontract-rules.en/fr.mdstating the whole-name-first order. The[Unreleased]changelog entry celebrating the widening is replaced by one describing the correction, in both languages — nothing had shipped, so leaving the pair would only contradict itself.Testing
dotnet build -c Release— clean, warnings are errors heredotnet test -c Release— 923 passed, 0 failedtests/PackageSmokeTest/run.sh— run because the analyzers changed; "The published package behaves as documented."Also run:
tools/style/lint-layout.sh(nothing to report), and the parity fixtures against the previousTryParse, to confirm they fail without the fix.Public API
The renamed descriptor is on the analyzers assembly, which carries no baseline; both packable projects are untouched.
Documentation
docs/updatedCHANGELOG.mdanddocs/for-users/CHANGELOG.fr.mdboth updated🤖 Generated with Claude Code
https://claude.ai/code/session_01M5U2BZXpHQr7YcfNHVx9dA
Generated by Claude Code