Skip to content

px-2ms: refuse \U like \u in string escapes - #227

Merged
johnnyt merged 1 commit into
mainfrom
px-2ms-refuse-upper-u-escape
Sep 14, 2026
Merged

johnnyt merged 1 commit into
mainfrom
px-2ms-refuse-upper-u-escape

Conversation

@johnnyt

@johnnyt johnnyt commented Sep 14, 2026

Copy link
Copy Markdown
Member

Resolves px-2ms (campaign RF047, RQ-RF047-8: refuse \U, the document-only arm declined).

What changed

The lexer's escape refusal in take_string/6 matched lowercase ?u only, so
"caf\U00e9" fell through the generic escape clause and lexed silently to
"cafU00e9". The clause now guards on u in [?u, ?U] and returns the same
ParseError message for both spellings. This is a narrowing of the existing
refusal - exactly the surface the ruling names - and nothing else about escapes
changes: "a\qb" is still aqb and "Unknown \x escape" is still
"Unknown x escape".

  • lib/predicator/lexer.ex - the guard and the comment above it.
  • test/predicator/lexer_test.exs - one negative test pinning \U refused with
    the byte-identical \u message, with a one-line sabotage note above it.
  • docs/reference/language.md - the "any other escaped character stands for
    itself" rule now names \u and \U as the refused spellings (the narrowing
    the change makes to that rule), and the refusal example carries a doctest for
    each spelling. Both run under test/docs_examples_test.exs.
  • changelog.d/px-2ms.md - a ### Fixed fragment (px changelog.d/README.md
    :22-27: "a bug fix a user could have noticed").

No version bump (RQ-RF046-13 stands: no predicator 9.4.2; the fix ships with the
next release). @version is unchanged at 9.4.1.

Sabotage

Mutation: drop ?U from the when u in [?u, ?U] guard. Run red - the new test
fails with {:ok, [{:string, 1, 1, 8, "U0041", :double, {1, 9}}, ...]} instead of
an error. Reverted from a copy taken before the mutation; mix test test/predicator/lexer_test.exs green after the revert. (px's
gate.sabotage.test_roots is a fixed file list that excludes lexer_test.exs,
so the gate does not demand the note; the bead does, so it is written.)

Gate

mix quality on the committed tree, green:

✓ Format: No changes needed (357ms)
✓ Compile: dev + test compiled (warnings as errors) (884ms)
✓ Dependencies: No unused dependencies (390ms)
✓ Credo: No issues (3.8s)
✓ Tests: 2,914 of 2,914 passed, 95.6% coverage (4.8s)
✓ Dialyzer: No warnings (PLT built this run) (49.3s)
✓ All quality checks passed!

gate=green. The repo gate lock and a machine slot were held across the gate run
and the commit; the committed tree is byte-identical to the tree that ran green
(git write-tree = 5e715dabd811de82c520a8feea96a428d6a1c31f before and after
git commit), so the commit step's internal gate re-run was taken in the
bare-commit shape citing this run.

Firewall

Terminology scan over the full outbound diff plus the new fragment: hard=0,
soft=0. Constructed positive control: one term from the firewall's hard pattern
(the vendor/integration-name arm) injected into a throwaway copy of the scanned
text; the scan printed a match on that copy, and the copy was deleted when the
result was read.

Provenance

Files touched are exactly the brief's file map minus conformance/:
lib/predicator/lexer.ex, test/predicator/lexer_test.exs,
changelog.d/px-2ms.md, docs/reference/language.md. conformance/ carries no
\u or \U case (grep over conformance/ returns zero hits), so the also-gated
path is untouched. No sibling PR was rebased over; the branch is cut from
origin/main at d06ece1 and is one commit ahead. docs/adr/ is untouched
(git diff --stat origin/main -- docs/adr/ is empty).

Scout finding F4 is addressed: the general rule at docs/reference/language.md
that the \q doctest pins is restated so it stays true under the narrowing, and
the \q and \x siblings stay green.

The lexer's escape refusal matched lowercase ?u only, so "caf\U00e9" fell
through the generic escape clause and lexed silently to "cafU00e9". Two
spellings of one escape refused differently is the defect: the refusal
clause in take_string/6 now guards on `u in [?u, ?U]` and returns the same
error for both. Nothing else about escapes changes - `\x` still stands for
itself.

The language reference's "any other escaped character" rule names the two
refused spellings, and the refusal example carries a doctest for each.

Refs: px-2ms
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
lib/predicator/lexer.ex 97.86% <100.00%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@johnnyt
johnnyt merged commit 71ae4da into main Sep 14, 2026
2 checks passed
@johnnyt
johnnyt deleted the px-2ms-refuse-upper-u-escape branch September 14, 2026 11:03
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.

1 participant