JustDummies: flag-enum combinations behind an opt-in, and a pin for the offset/OneOf divergence - #307
Merged
Merged
Conversation
Any.Enum<T>() draws from the declared members, so a [Flags] dummy carries at most one bit and a branch reading two is never exercised — the hidden assumption the library exists to surface, installed by the generator itself. AllowingCombinations() widens the draw to the OR-closure of the declared members, plus the zero value when the enum declares one. Taking the members as the generating set absorbs a declared composite without deciding which members "are" bits, and never invents a zero the type does not define. The universe is materialized rather than drawn bit by bit: a per-member coin flip is uniform over subsets, not over values, so a declared composite would skew the draw, and it could report no distinct cardinality — dropping distinct collections from an eager conflict to a bounded draw. Enumeration is exponential, so it is refused past a ceiling instead of silently degrading, as it is on a non-[Flags] enum. Exclusions keep comparing by equality: Except(Read) forbids Read and leaves Read | Write drawable. Refs: #226
WithOffset governs the constructed draw only: OneOf returns its values verbatim, offset included, because it is a terminal enumeration of exact values. ADR-0037 records that as an accepted risk, but no test held it, so a future change in either direction would have gone unnoticed. Refs: #297
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
Any.Enum<T>()draws from the declared members, so a[Flags]dummy carries at most one bit and a branch reading two is never exercised — the hidden assumption the library exists to surface, installed by the generator itself. This addsAllowingCombinations(), an explicit opt-in widening the draw to every combination, and leaves the default untouched. It also pins an accepted risk recorded in ADR-0037 that no test held.Type of change
Changes
AnyEnum<TEnum>.AllowingCombinations()widens the draw to the OR-closure of the declared members, plus the zero value when the enum declares one. The whole class already worked over an "universe" field, soOneOf,Except, the pool,ICardinalityHintandGeneratefollow unchanged.ReadWrite = Read | Writeadds nothing) without deciding which members "are" bits, and never invents a zero value for an enum that declares none ({ Left, Right }yields three values, not four).ICardinalityHintexact, so a distinct collection over combinations still fails eagerly instead of falling back to a bounded draw.[Flags]. Both refusals happen at declaration and name their cause.Except/DifferentFromkeep comparing by equality under the opt-in:Except(Read)forbidsReadand leavesRead | Writedrawable. A bit-level exclusion, if ever wanted, gets its own name rather than mutating this one.OneOfaccepts a combination once the opt-in is applied; before it, theArgumentExceptionnames the missing constraint instead of failing flatly.WithOffsetdoes not rewrite aOneOfvalue's own offset, in both declaration orders. The divergence was documented but untested, so a change in either direction would have gone unnoticed.Testing
dotnet build FirstClassErrors.sln— succeeded, 0 warnings, 0 errorsdotnet test FirstClassErrors.sln— 13 projects, 1772 tests, 0 failuresFirstClassErrors.Analyzers.UnitTests) — 132 passed, within the solution runNew tests: 13 examples in
JustDummies.UnitTests(533 total, was 519) and 5 properties inJustDummies.PropertyTests(215 total, was 210), placed per ADR-0040 — the universe of each named enum shape and the conflict wording as examples, the invariants quantified over the constraint sets as properties. One of them,FlagsEnumDrawsDeclaredMembersByDefault, exists to lock the default rather than the new behaviour.Only the
net10.0leg was run locally; thenet472floor runs in CI.Documentation
doc/updated — package README, and ADR-0041 in English and Frenchdoc/handwritten/for-users/README.fr.md) updated if user-facing behavior changeddoc/handwritten/for-users/README.fr.mdwas not touched: it does not cover JustDummies at all (the package is still absent from the repository README and has no user guide — issue #218). The user-facing surface for this change is the package README, which has no French counterpart; the French half of the change is the ADR translation.Architecture decisions
Proposedand since accepted by @Reefact (status flipped in62e606a)ADR-0041 records four choices that outlive the implementation: the default stays declared-members-only even for a
[Flags]enum (making it depend on the attribute would make the draw a function of type metadata rather than of what the test wrote — the class of implicitness ADR-0020 removed);Exceptkeeps equality semantics; the universe is the OR-closure of the members with the zero-value rule; and the ceiling refuses rather than degrades.Related issues
Refs #226 — the demand-driven backlog entry this resolves. Not closing it: the entry covers several other nice-to-haves that remain open.
🤖 Generated with Claude Code
https://claude.ai/code/session_01CQjueXGjtdbzdpLs1fQi3y