feat(dummies): cover the .NET simple types and multi-target net8.0 - #173
Conversation
Every discrete interval-shaped type (integers, ticks-based time types) shares the same domain algebra: descriptor-tracked inclusive bounds, an optional allow-list, exclusions, eager conflict detection and one-draw generation. Extract it into OrdinalIntervalSpec over an order-preserving ulong ordinal space, parameterized by the type's display name and value renderer, so the conflict logic lives once before the sibling integer and time generators arrive. AnyInt32 becomes a thin wrapper; its public surface and messages are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
Ten new discrete generators over the shared ordinal engine: SByte, Byte, Int16, UInt16, UInt32, Int64, UInt64 (signed surfaces carry Positive/Negative; unsigned ones deliberately do not), TimeSpan (Positive/Negative/Zero/NonZero over the full range), DateTime and DateTimeOffset (After/Before/Between vocabulary; generated values are UTC; DateTimeOffset's OneOf returns the supplied values as given, offset included). No clock-relative constraints by design: a reproducible test pins its reference instants explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
AnyDouble, AnySingle and AnyDecimal complete the numeric families. Finite values only: NaN and the infinities are never generated nor accepted. Exclusive bounds use the next representable value (double, float) or an inclusive bound plus a point exclusion (decimal); sampling works around the interval midpoint so wide ranges never overflow, and a draw colliding with an excluded point is walked to a neighbouring value within a bounded budget — deterministic, not a retry loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
AnyBool pins with True()/False(); AnyGuid draws reproducible, practically-never-empty identifiers from the seedable source with Empty()/NonEmpty()/OneOf/Except; AnyEnum<TEnum> draws uniformly from the declared members only, and an emptied pool fails eagerly; AnyChar mirrors AnyString's character families (Alpha/Numeric/AlphaNumeric, LowerCase/UpperCase) over a letters-and-digits default pool. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
The package now ships netstandard2.0 plus a net8.0 leg carrying the types that do not exist downlevel: DateOnly and TimeOnly (on the shared ordinal engine), Int128 and UInt128 (on a dedicated 128-bit ordinal engine, since their space exceeds the ulong core), and Half (on the continuous engine through a quantization step). Facade and context gain the matching entry points under NET8_0_OR_GREATER. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
Multi-targeting made the extension method NextInt64(min, max) collide with the framework's own Random.NextInt64 instance method on net8.0 — which wins overload resolution and treats the upper bound as EXCLUSIVE, silently shrinking string-length draws by one. Rename the samplers to NextInt64Inclusive/NextInt32Inclusive so no instance method can ever shadow them, and document why. Caught by the WithLengthBetween boundary test the moment the tests ran on the net8.0 leg. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
Per-family suites for the eighteen new generators: inclusive bounds reached on tiny domains, strict sign constraints, pins, exclusions never yielded, OneOf containment (DateTimeOffset preserving supplied offsets), Utc kind and zero-offset defaults, finite-only floating draws, argument validation, and eager conflicts naming both sides. The reproducibility batch now spans the new types too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
The ASCII pools, their classification helpers and the CharacterSet and LetterCasing enums were duplicated between StringSpec and AnyChar; they now live once in CharacterPools, so the two generators can never drift apart on what letters-and-digits means. The package readme now advertises the full generator surface and the net8.0-only types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
Cross-cutting cleanups from a four-angle review (reuse, simplification, efficiency, altitude) of the increment: - Engines own exclusive bounds everywhere: the continuous engine gains a per-type nextUp seam and WithMinimumAbove/Below (the decimal engine composes bound-plus-exclusion internally), so the float/Half bit ladders and the decimal strategy leave the leaf wrappers. - 'Constrain once, draw many' held: sorted in-range exclusions, filtered allow-lists and the char/enum pools are materialized at construction instead of being rebuilt on every draw; the declared members of an enum are cached per closed generic type. - AnyDateTime.OneOf now returns supplied values as given (Kind included), matching its DateTimeOffset sibling instead of silently normalizing to Utc. - Dedups: shared EnsureFinite guard, NextUInt128 and NextInt64Inclusive delegate to the byte sampler, Join uses V in the integer wrappers, single-caller ranged NextUInt64 overload inlined, always-true clause dropped from the four DescribeExhaustion, dead branch removed from AnyGuid.Empty, OneOf constraint strings hoisted, AnyTimeSpan speaks its own Ord vocabulary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
|
@codex need an exhaustive code review on the global Dummies project |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ffa69ff1c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| string constraint = $"OneOf({Join(values)})"; | ||
| if (_allowedConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {constraint} because {_allowedConstraint} is already defined."); } | ||
|
|
||
| return Validated(new AnyEnum<TEnum>(_source, _declared, values.Distinct().ToArray(), constraint, _excluded), constraint); |
There was a problem hiding this comment.
issue: Keep Enum.OneOf within declared members
When OneOf is called with an enum value that is not actually declared, for example Any.Enum<OrderStatus>().OneOf((OrderStatus)42), this stores the supplied value directly as the allowed pool, so Generate() can return an undeclared enum value. That violates the new Enum<T> contract advertised in the XML docs and package README as “declared members only”; intersect the allowed values with _declared or reject undeclared inputs before building the pool.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Resolved in 374c928. Undeclared values are rejected with an ArgumentException naming the offending value (a caller bug, not a constraint conflict — silently intersecting with the declared members would make the supplied value vanish without explanation). Regression test added: OneOf((OrderStatus)42) and the mixed declared/undeclared case both throw.
Generated by Claude Code
OneOf stored the supplied values as the allowed pool without checking them against the declared members, so OneOf((OrderStatus)42) could make Generate() yield an undeclared numeric value — violating the documented declared-members-only contract. Undeclared values are a caller bug, not a constraint conflict: reject them with an ArgumentException naming the offending value. Found by Codex review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
Summary
Second increment of
Dummies: the library now covers the .NET simple types exhaustively — every integer width, the floating-point and decimal families,Bool/Guid/Enum<T>/Char, and the time types with a domain vocabulary (After/Before/Between) — and multi-targetsnetstandard2.0+net8.0, the modern leg addingDateOnly,TimeOnly,Int128,UInt128andHalf. All generators keep the library's contract: constraints express invariants, conflicts fail eagerly naming both sides, values are built to satisfy in one draw, and every draw is seed-reproducible.Type of change
Changes
OrdinalIntervalSpec(order-preservingulongordinal space — all integers ≤64 bits,TimeSpan,DateTime,DateTimeOffset,DateOnly,TimeOnly),WideIntervalSpec(UInt128ordinals for the 128-bit integers),ContinuousIntervalSpec(double-backed, ridingSingleandHalfthrough a quantization seam) andDecimalIntervalSpec.AnyInt32was rebased on the ordinal engine with its public surface and messages unchanged.SByte,Byte,Int16,UInt16,UInt32,Int64,UInt64(signed surfaces carryPositive/Negative; unsigned deliberately do not),TimeSpan,DateTime(generated values are UTC),DateTimeOffset(zero offset;OneOfreturns supplied values as given, offset included —AnyDateTime.OneOflikewise preserves the suppliedKind),Double/Single/Decimal(finite only — NaN and infinities are neither generated nor accepted),Bool,Guid(seed-reproducible identifiers),Enum<T>(declared members only),Char(mirrorsAnyString's character families), plusDateOnly,TimeOnly,Int128,UInt128,HalfunderNET8_0_OR_GREATER. No clock-relative constraints by design: a reproducible test pins its reference instants explicitly. Deliberately not covered:nint/nuint(platform-dependent domain),BigInteger(no meaningful "arbitrary" on an unbounded domain),Complex/Rune(niche).Random.NextInt64(long, long)instance method (exclusive upper bound) silently won overload resolution over the library's same-named inclusive extension, shrinking string-length draws by one; the samplers are renamedNextInt64Inclusive/NextInt32Inclusiveand the hazard documented.nextUpseam; the float/Half bit ladders and the decimal bound-plus-exclusion strategy leave the leaf wrappers); "constrain once, draw many" made real (sorted in-range exclusions, filtered allow-lists and the char/enum pools materialized at construction; enum members cached per closed generic type); sharedEnsureFiniteguard; character pools and theCharacterSet/LetterCasingenums consolidated intoCharacterPools; dead branches and always-true clauses removed. Deferred with rationale: hosting the samplers onSeededRandom, test-side helper folding, a shared engine for the set-based family (flagged for maintainer judgement).OneOfcontainment and offset/Kind preservation, UTC defaults, finite-only draws, argument validation, eager conflicts naming both sides); the seed-reproducibility batch now spans the new types.Testing
dotnet build FirstClassErrors.slndotnet test FirstClassErrors.slnFirstClassErrors.Analyzers.UnitTests)950 tests green (127 in
Dummies.UnitTests), zero warnings, both target legs built.Documentation
README.nuget.md)doc/updateddoc/handwritten/for-users/README.fr.md) updated if user-facing behavior changed — not applicable: no FirstClassErrors user-facing change; the Dummies user guide remains deferred until the surface stabilizes (ADR-0011 follow-up)Architecture decisions
Proposed: ADR-____Related issues
Follow-up to #170 (Dummies increment 1 and the
dumrelease train).🤖 Generated with Claude Code
https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw
Generated by Claude Code