Skip to content

feat(dummies): cover the .NET simple types and multi-target net8.0 - #173

Merged
Reefact merged 10 commits into
mainfrom
claude/dummies-simple-types
Jul 18, 2026
Merged

feat(dummies): cover the .NET simple types and multi-target net8.0#173
Reefact merged 10 commits into
mainfrom
claude/dummies-simple-types

Conversation

@Reefact

@Reefact Reefact commented Jul 18, 2026

Copy link
Copy Markdown
Owner

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-targets netstandard2.0 + net8.0, the modern leg adding DateOnly, TimeOnly, Int128, UInt128 and Half. 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

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation
  • Build / CI / tooling

Changes

  • Shared engines so the conflict logic lives once: OrdinalIntervalSpec (order-preserving ulong ordinal space — all integers ≤64 bits, TimeSpan, DateTime, DateTimeOffset, DateOnly, TimeOnly), WideIntervalSpec (UInt128 ordinals for the 128-bit integers), ContinuousIntervalSpec (double-backed, riding Single and Half through a quantization seam) and DecimalIntervalSpec. AnyInt32 was rebased on the ordinal engine with its public surface and messages unchanged.
  • 18 new generators: SByte, Byte, Int16, UInt16, UInt32, Int64, UInt64 (signed surfaces carry Positive/Negative; unsigned deliberately do not), TimeSpan, DateTime (generated values are UTC), DateTimeOffset (zero offset; OneOf returns supplied values as given, offset included — AnyDateTime.OneOf likewise preserves the supplied Kind), Double/Single/Decimal (finite only — NaN and infinities are neither generated nor accepted), Bool, Guid (seed-reproducible identifiers), Enum<T> (declared members only), Char (mirrors AnyString's character families), plus DateOnly, TimeOnly, Int128, UInt128, Half under NET8_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).
  • Multi-target fix caught by the new tests: on the net8.0 leg the framework's 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 renamed NextInt64Inclusive/NextInt32Inclusive and the hazard documented.
  • Simplify pass applied (four-angle review: reuse, simplification, efficiency, altitude): exclusive-bound arithmetic moved into the continuous engines (per-type nextUp seam; 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); shared EnsureFinite guard; character pools and the CharacterSet/LetterCasing enums consolidated into CharacterPools; dead branches and always-true clauses removed. Deferred with rationale: hosting the samplers on SeededRandom, test-side helper folding, a shared engine for the set-based family (flagged for maintainer judgement).
  • Tests: 45 new facts across six family suites (inclusive bounds reached on tiny domains, strict sign constraints, exclusions never yielded, OneOf containment 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.
  • Package readme refreshed to advertise the full surface and the net8.0-only types.

Testing

  • dotnet build FirstClassErrors.sln
  • dotnet test FirstClassErrors.sln
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests)

950 tests green (127 in Dummies.UnitTests), zero warnings, both target legs built.

Documentation

  • Public API / error documentation updated (XML docs on the whole new public surface; package README.nuget.md)
  • README / doc/ updated
  • French translation (doc/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)
  • No documentation change required

Architecture decisions

  • No architectural decision in this pull request — the increment implements within the boundaries ADR-0011 already records; the standalone no-FirstClassErrors-reference rule still holds and stays guarded by the architecture test
  • New decision recorded — ADR drafted as Proposed: ADR-____
  • Supersedes an existing ADR — successor proposed, status not flipped: ADR-____
  • ⚠️ Conflicts with an existing ADR — flagged for the maintainer: ADR-____

Related issues

Follow-up to #170 (Dummies increment 1 and the dum release train).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UhPrBqngpiSwdsF4DWvNPw


Generated by Claude Code

claude added 9 commits July 17, 2026 18:08
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
@Reefact

Reefact commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

@codex need an exhaustive code review on the global Dummies project

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Dummies/AnyEnum.cs
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@Reefact
Reefact merged commit 9b14a4f into main Jul 18, 2026
13 checks passed
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