feat(dummies): add WithChars custom alphabet to AnyString#291
Merged
Conversation
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
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
AnyStringcould only draw its filler from the built-inAlpha/Numeric/AlphaNumericsets, so non-ASCII text (accents, other scripts) was reachable only through aStringMatchingliteral. This addsWithChars(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
Changes
AnyString.WithChars(string pool): draws the filler from an explicit character pool, the general form ofAlpha/Numeric/AlphaNumeric.WithChars, and (because the pool is the whole character definition) withLowerCase/UpperCase— every conflict naming both sides.ArgumentExceptionthat points toOneOf(...), keepingWithCharsvalid by construction and within the Basic Multilingual Plane.SurfaceParityTestsalgebra forAnyString.Testing
dotnet build FirstClassErrors.slndotnet test FirstClassErrors.slnFirstClassErrors.Analyzers.UnitTests)Added
AnyStringtests 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
doc/updateddoc/handwritten/for-users/README.fr.md) updated if user-facing behavior changedThe Dummies package README (
Dummies/README.nuget.md) and the method's XML docs are updated. The for-usersREADME.fr.mddocuments FirstClassErrors, not the Dummies constraint surface (the Dummies user guide is tracked separately), so no French translation applies here.Architecture decisions
Proposed: ADR-____WithCharsfollows 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 existingOneOfsurface.Related issues
Refs #226
🤖 Generated with Claude Code
https://claude.ai/code/session_01FPAdCRsiaVo2orjJwCaDnb
Generated by Claude Code