Skip to content

Remove ErrorCode runtime registry; make ErrorContextKey registration idempotent#39

Merged
Reefact merged 2 commits into
mainfrom
claude/static-registries-design-6awl4z
Jul 6, 2026
Merged

Remove ErrorCode runtime registry; make ErrorContextKey registration idempotent#39
Reefact merged 2 commits into
mainfrom
claude/static-registries-design-6awl4z

Conversation

@Reefact

@Reefact Reefact commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Removes the process-wide runtime registry behind ErrorCode.Create (duplicate codes are harmless at run time thanks to value equality, and FCE001 keeps flagging literal duplicates at build time) and makes ErrorContextKey registration idempotent: re-declaring a key with the same name and value type returns the already-registered instance, while a same-named key with a different value type is still rejected. This eliminates the opaque TypeInitializationException failures raised from consumer static initializers and repairs plugin-reload / re-run-fixture scenarios, without giving up the name↔type guarantee that protects typed context reads.

Type of change

  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation

Changes

  • ErrorCode.cs: removed the static Registered set, its lock, and the internal ResetForTests(). Create() no longer throws InvalidOperationException for a code created twice; it only rejects null/blank codes. XML docs describe the value semantics and point to FCE001.
  • ErrorContextKey.cs: Create<T> is now get-or-create. Same name + same value type returns the registered instance (the first registration is canonical, so its description or provider wins); same name + different value type still throws, with a message that now names both types. GetRegisteredKeys() and ResetForTests() are unchanged. XML docs updated.
  • DuplicateErrorCodeAnalyzer.cs / Descriptors.cs: FCE001 rationale updated — a duplicated code now silently merges two errors into one identity instead of throwing at run time; the rule and its severity are unchanged.
  • doc/analyzers/FCE001.en.md + FCE001.fr.md: same rationale update; the Details section now states that ErrorCode.Create does not enforce uniqueness at run time and that cross-assembly duplicates surface as a documentation-pipeline warning.
  • ErrorCodeTests.cs: dropped IDisposable/reset plumbing; the duplicate-rejection test became "creating the same code twice is allowed and produces equal instances"; the concurrency test now asserts that all parallel creations succeed and are equal.
  • ErrorContextKeyTests.cs: idempotent re-declaration returns the same reference with a single registry entry; the first registered description wins; the type-conflict test asserts the new message; the concurrency test asserts all parallel registrations share one instance.
  • ErrorTests.cs, ErrorContextImmutabilityTests.cs, ErrorDocumentationBuilderTests.cs: removed the now-nonexistent ErrorCode.ResetForTests() calls (their ErrorContextKey resets remain).

Testing

  • dotnet build FirstClassErrors.slnnot run: no .NET SDK was available in the authoring environment (network policy blocks installing one)
  • dotnet test FirstClassErrors.slnnot run: FirstClassErrors.UnitTests — including the rewritten ErrorCodeTests / ErrorContextKeyTests — has not been built or executed anywhere yet; please run this before merging
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests) — run by the analyzers CI workflow on the head commit (green); that workflow also builds FirstClassErrors.Usage with the analyzers active, which compiles the main library

Documentation

  • Public API / error documentation updated — XML docs on ErrorCode.Create and both ErrorContextKey.Create overloads describe the new contract
  • README / doc/ updated — doc/analyzers/FCE001.en.md and doc/analyzers/FCE001.fr.md
  • French translation (doc/README.fr.md) updated if user-facing behavior changed — not needed: neither README describes the runtime registry behavior; the French analyzer page was updated in sync with the English one

Related issues

None

🤖 Generated with Claude Code

https://claude.ai/code/session_01E6eKjRdyosK1CfnTg1XV4Z

claude added 2 commits July 6, 2026 22:36
ErrorCode.Create maintained a process-wide HashSet and threw
InvalidOperationException the second time a code was created. That
runtime guard cost more than it was worth: a duplicate surfaced as an
opaque TypeInitializationException from a consumer's static initializer,
it forced an internal ResetForTests plus non-parallel test collections,
and it broke collectible AssemblyLoadContext / plugin reload scenarios.
Meanwhile ErrorCode already compares by value, so duplicate codes are
harmless, and FCE001 catches literal duplicates at build time with far
better ergonomics.

Remove the registry, its lock and ResetForTests; Create now only rejects
null/blank codes and returns a value. Update the ErrorCode tests
(creating the same code twice is now allowed and yields equal instances),
drop the now-needless ErrorCode resets from the other suites, and refresh
the FCE001 analyzer description and documentation (EN/FR) that described
the removed runtime throw.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6eKjRdyosK1CfnTg1XV4Z
…e and type

ErrorContextKey.Create threw on any same-name re-registration, including
the re-execution of an identical declaration (reloaded plugin, re-run
test fixture) where nothing conflicts. Yet the registry does guard
something real: keys compare by name inside error contexts, so two keys
sharing a name with different value types would silently collide in the
context dictionary and make the stored value unreadable through the
typed API.

Split the two cases instead of rejecting both. Create is now
get-or-create: re-declaring a key with the same name and value type
returns the already-registered instance (the first registration is
canonical, so its description or provider wins by construction), while a
same-named key with a different value type still throws — with a message
that now names both types. GetRegisteredKeys and the documentation
pipeline are unchanged.

Update the ErrorContextKey tests accordingly: idempotent re-declaration
returns the same instance, the first description wins, the type conflict
keeps throwing, and concurrent same-key registrations all succeed and
share one registration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6eKjRdyosK1CfnTg1XV4Z
@Reefact Reefact merged commit 7462e39 into main Jul 6, 2026
2 checks passed
@Reefact Reefact deleted the claude/static-registries-design-6awl4z branch July 6, 2026 23:17
@Reefact Reefact changed the title Make ErrorCode a value type; remove registration requirement Remove ErrorCode runtime registry; make ErrorContextKey registration idempotent Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants