Skip to content

feat(dummies): add WithChars custom alphabet to AnyString#291

Merged
Reefact merged 4 commits into
mainfrom
claude/issue-226-discussion-b11lt2
Jul 23, 2026
Merged

feat(dummies): add WithChars custom alphabet to AnyString#291
Reefact merged 4 commits into
mainfrom
claude/issue-226-discussion-b11lt2

Conversation

@Reefact

@Reefact Reefact commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

AnyString could only draw its filler from the built-in Alpha/Numeric/AlphaNumeric sets, so non-ASCII text (accents, other scripts) was reachable only through a StringMatching literal. This adds WithChars(string pool) — the general form of the named character sets — so a generated string can be drawn from a caller-supplied alphabet. One item of the demand-driven Nice-to-Have backlog.

Type of change

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

Changes

  • Add AnyString.WithChars(string pool): draws the filler from an explicit character pool, the general form of Alpha/Numeric/AlphaNumeric.
  • It occupies the character-family slot — declared once, conflicts with the named sets and with a second WithChars, and (because the pool is the whole character definition) with LowerCase/UpperCase — every conflict naming both sides.
  • Anchored fragments (prefix, suffix, contained value) are validated against the pool eagerly, so an out-of-pool character surfaces at declaration, not at generation. Duplicate characters collapse; each distinct character is drawn uniformly.
  • Reject a surrogate-bearing pool (an emoji or other astral code point) with an ArgumentException that points to OneOf(...), keeping WithChars valid by construction and within the Basic Multilingual Plane.
  • Declare the method in both per-TFM public-API baselines and extend the SurfaceParityTests algebra for AnyString.
  • Document it in the package README (Custom alphabets) and the XML docs.

Testing

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

Added AnyString tests cover soundness (every character is drawn from the pool), reachability (every pool character appears), i18n, both conflict orders (charset and casing), fragment-outside-pool, dedup, argument validation, seeded reproducibility, and astral rejection. Full solution: 10 test projects, 0 failures (Dummies.UnitTests 463).

Documentation

  • Public API / error documentation updated
  • README / doc/ updated
  • French translation (doc/handwritten/for-users/README.fr.md) updated if user-facing behavior changed
  • No documentation change required

The Dummies package README (Dummies/README.nuget.md) and the method's XML docs are updated. The for-users README.fr.md documents FirstClassErrors, not the Dummies constraint surface (the Dummies user guide is tracked separately), so no French translation applies here.

Architecture decisions

  • No architectural decision in this pull request
  • 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-____

WithChars follows the existing character-family pattern; the BMP/code-unit scope is a local design choice, not a lasting cross-cutting decision — astral and grapheme needs are served by the existing OneOf surface.

Related issues

Refs #226

🤖 Generated with Claude Code

https://claude.ai/code/session_01FPAdCRsiaVo2orjJwCaDnb


Generated by Claude Code

claude and others added 4 commits July 22, 2026 21:26
AnyString could only draw its filler from the built-in Alpha/Numeric/
AlphaNumeric sets, so non-ASCII text (accents, other scripts) was
reachable only through a StringMatching literal. Add WithChars(string
pool), the general form of the named character sets: the generated
string is drawn from the caller's explicit pool.

WithChars occupies the same character-family slot as the named sets, so
it is declared once and conflicts with Alpha/Numeric/AlphaNumeric and
with a second WithChars, naming both sides. Because the pool is the whole
character definition it also conflicts with LowerCase/UpperCase — put
only the casing you want in the pool. Anchored fragments (prefix, suffix,
contained value) are validated against the pool eagerly, exactly like the
named sets, so an out-of-pool character surfaces at declaration, not at
generation. Duplicate characters collapse and each distinct character is
drawn uniformly.

Because Dummies draws the character itself from the pool — no CultureInfo
or Uri/IdnMapping involved — a seeded WithChars draw stays reproducible
across target frameworks. The pool is a sequence of UTF-16 code units: a
code point outside the Basic Multilingual Plane is two units and is not
guaranteed to be drawn as an indivisible unit (a first-class Rune builder
remains future work).

Declare the method in both per-TFM public-API baselines, extend the
surface-parity algebra for AnyString, and cover behaviour, reachability,
i18n, both conflict orders, dedup, argument validation and seeded
reproducibility.

Refs: #226
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPAdCRsiaVo2orjJwCaDnb
WithChars draws UTF-16 code units independently, so a pool holding a
surrogate (an emoji or other astral code point) could be drawn — and
split — one unit at a time, yielding a broken surrogate. That silently
breaks the library's arbitrary-yet-valid contract.

Reject a surrogate-bearing pool eagerly with an ArgumentException whose
message names the fix: draw such values as whole strings with OneOf(...).
This keeps WithChars valid by construction and leaves astral and grapheme
needs to the existing string-set surface (and a future net8-only Rune
builder). BMP pools — accents, Greek, Cyrillic, CJK — are unaffected.

Refs: #226
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPAdCRsiaVo2orjJwCaDnb
Refine the package readme's Custom alphabets entry so it teaches the one
non-obvious point: WithChars stays within the Basic Multilingual Plane and
rejects a surrogate, so an emoji or other astral character is drawn as a
whole string with OneOf(...) rather than as a character family.

Refs: #226
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPAdCRsiaVo2orjJwCaDnb
@Reefact
Reefact merged commit 5dd8714 into main Jul 23, 2026
16 checks passed
@Reefact
Reefact deleted the claude/issue-226-discussion-b11lt2 branch July 23, 2026 08:49
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