Repository navigation
tests: lexer observation totality — the Stryker pilot's seven survivor classes C1–C7 (numeric literals, token positions, line endings, f-string arms, LiteralLoss scanner, end of file, keyword roster) #2300
Description
Class
Contract: every lexer arm — each numeric-literal form, each token's position, each line-ending, each f/t-string and dedented-literal arm, the Lexer.LiteralLoss.cs scanner's arms, end of file with and without a trailing newline, and every keyword in the spec's roster — is observed by a test that fails when the arm is removed. The Stryker pilot (#2289, R-GB) measured Lexer/ at 73.27 % @ 258a7b6 (measured): 2,146 of 2,929 mutants detected, 783 survived; a seeded sample of 30 read as 23 test gaps / 3 plausible / 3 equivalent / 1 out of reach. Two leading-dot-float mutants (M1 drops the .1__2 refusal, M2 drops the exponent letter so .5e-3 dies with SPY0909) pass the WHOLE solution 79207/0/4 — no test anywhere lexes a leading-dot float. Report: docs/audits/stryker-lexer-pilot-2026-10-11.md (7f0502d).
This is a tests-only class: the compiler's behaviour on every cell below is believed correct (the C3 probes print 3 7 on crlf/cr @ 258a7b6); the defect is the suite's aperture. Lane tooling, reach probe.
Ruling R-GR (owner, 2026-10-12)
(a) ONE class tracker with seven rows, drained by one tests-only group (P68, directly after P63) — C2 shares P67's position instrument, whichever lands first builds it. Rejected: (b) seven issues, one per class (seven one-row chores; the open count rises for tests-only work); (c) a cell table on #2289 and a chore drain with no new issue (#2289 is the pilot's tracker and closes when Semantic/Validation/ is done — the work would lose its home).
Cells (survivor classes of the pilot, counts by mutated method @ 258a7b6)
| class | survivors | contract a guard states | representative cells | harness | lane | reach | found by |
|---|---|---|---|---|---|---|---|
| C1 numeric literals | 152 (ReadNumber, hex/octal/binary, prefixed-integer suffix) |
every numeric-literal form × {underscore placement, exponent sign, suffix, end of file} lexes to CPython's verdict and value (Sharpy suffixes on their own rows) | leading-dot floats .5e3 .5e-3 .1__2 .12_ .5e .5e1_; 0xFFu + any following character; trailing-dot 1. |
generator FIRST: a python3 tokenize twin over form × prefix × suffix × separator with an allowlist ratchet; Sharpy-only suffix rows are the hand axis |
tooling | probe | #2289 pilot |
| C2 token positions | 146 column + span/SourceLength mutants |
every token's (line, column, position, length) maps back to its source slice | a >> b; a token after b"…", dr"…", """…""" |
the position-reconstruction instrument P67 (#2295) builds over the corpus — tokens AND node ends; shared, built by whichever lands first | tooling | probe | #2289 pilot |
| C3 line-ending axis | NextToken, ConsumeLineBreak, IsPreCloseNewline, hole trivia |
{LF, CRLF, CR} at every line-break site gives the same tokens | backslash continuation over CRLF; newline inside brackets on CR; CRLF in a triple-quoted literal | a line-ending twin axis on the existing lexer theories (every theory runs ×3) | tooling | probe | #2289 pilot |
| C4 f/t-string and dedented-literal arms | 266 | every f-string arm (spec escape, {{/}}, == in a field, nested braces, a comment in hole trivia, dedented triple prescan) is exercised |
f"{a==b}" (the mutant reads a self-documenting =); f"{x:\">4}"; a df"""…""" hole holding a triple-quoted string or a tab |
hand cells on the f-string theories (closed axis) | tooling | probe | #2289 pilot |
C5 Lexer.LiteralLoss.cs scanner |
60 | the hand-model scanner agrees with the lexer on every escape and brace arm | a backslash escape and nested { } in a field on a dropped line; spec \" |
the differential the scanner never got: scanner verdict vs lexer verdict over the P22 corpus | tooling | probe | #2289 pilot |
| C6 end of file | 89 | every literal and operator form lexes the same at end of file with and without a trailing newline | dr"abc at EOF (mutant: IndexOutOfRange instead of the unterminated-string diagnostic) |
an EOF twin axis beside C3's (with / without trailing newline) | tooling | probe | #2289 pilot |
| C7 keyword roster | 6 | every keyword in the spec's list lexes to its token type, anchored to literals, never to Keywords.Keys |
union, delegate, super, Self, self |
a roster test anchored to the spec's literal list (a totality count from the same enum is vacuous) | tooling | probe | #2289 pilot |
Acceptance
- Each row's harness lands with its mutation record (one arm removed → red; restored → green) in the commit body; the C1 and C5 generators ratchet on an allowlist that cites this issue and drains to 0.
- Re-run the pilot on
Lexer/(build_tools/stryker/run.sh,--sinceform if measured) and recordscore @ sha (measured)against the 73.27 % baseline; the message-text / logger-string mutants get the ignore list R-GB asked for, with a reason per entry. - No production change: a cell that turns out to be a lexer defect is filed by class (contract §10), never fixed under this issue.
Labels: class-tracker, lane:tooling, reach:probe, testing.
Charter: .claude/plans/remediation-round-2026-09-20-batching.md §3.6c — P68 lexer observation totality (directly after P63). Siblings: #2289 (the pilot, stays open for Semantic/Validation/), #2295 (P67 builds the shared position instrument).
Activity
antonsynd commentedon Oct 21, 2026
P68 record post: lexer observation totality (220259b..0d58024, 20 commits)
State: this issue stays open. Two acceptance items are not done:
/batch-issues.Acceptance bullets → evidence
2eeb1b2e2(+92f4cc430,40d771acd,d15d7194a) · C2/C3a3838d719,93c6c92ca· C67792537c0· C55baaeb0b7,e316dda15,ffdd7f7d3,e7bb3cfa6· C478ddc4bc1· C722234d00e(+ spec76b7acca7). The roster rows are in0b3528716.docs/audits/stryker-lexer-pilot-2026-10-11.md. The lead also re-ran the hex:979(red 1/91),IsPreCloseNewline(red 42/317) and{{(red 2/48) mutants per wave.git diff --stat 220259b51..HEAD -- src/Sharpy.Compiler=Lexer/Lexer.csonly, 8+/7− (e621d9e94). The token dump over 4,053 fixtures is identical.Sharpy.Compiler.Tests: +643 tests, of which +640 pass and +3 are skipped (the #2337 df cells). The other four projects are identical.0d58024ddis docs-only.build_toolspytest ·check_spec_blocks.sh· allowlist issue-state · formatdotnet format whitespace --verify-no-changesexit 0delegateremoved) red 4, C3 (lone CR swallows the next char) red 6, C6 (ReadByteStringbound<=) red 15; restored green 2214/0/3. Bogus, uncited and stale allowlist rows fail in all three ratchets. Claim 4 was partly refuted (see the chore note).Stryker instrument (Phase 0, measured @ e621d9e)
ignore-methodsskips argument mutations. All 4ReportError("…")message-string mutants inLexer.Indentation.csareIgnoredwith "Removed by method filter".lexer.jsonwill carryignore-methods.lexer-ignore.jsonplustriage.pystay authoritative for what the config cannot express.Token.cs+LiteralSpans.cs+Lexer.Indentation.cs(213 tested: 144 killed, 31 timeout, 38 survived).TokenizeAllCS0165 safe-mode hole is closed. There are 85 safe-mode lines in a compile of all 73,308 mutants, and none is inLexer/.perTesteach mutant now runs the corpus twin, the injection differential and the CPython twin.Cells (allowlist rows and ledger)
01leading-zero decimal (also0_1)print(01)1numeric-literal-twin-allowlist.txtcite #2300. Rule at/batch-issues: adeviations.yamlrow (the spelling moves to the twin's exclusion table) or promotion to a lexer-defect class issue.keywords.mdtorowkeywords.md:46type_casting.md); neither the lexer nor the parser knows it76b7acca7(the row and four sentences); the C7 roster anchors to the corrected tables.f"a⏎b"(alsot",df", CRLF, CR,f"a {x}⏎b")b"as an unterminated byte stringdf"""⏎ a}b⏎ """(also{x!q},{x!r x},{x)},{x]})"""FStringText/FStringFormatSpec/FStringConversioncarry noSourceLength(Value is decoded text;!rspans 2 butGetSpan()covers 1). A field's spec-introducing:lies in no token's span.TokenPositionReconstructionreads those spans asnext.Position − Position. The limit is recorded; a span carrier for interior tokens is a candidate for P67 (#2295).Filed from this round's ledger (one per class):
FStringArmMatrixTests.lexer-position-allowlist.txt, and SPY0015 is reported at (2:5) instead of (3:5).3.14Lruns as float64;.5fand.5jgive SPY0103.{{fill, the\"fill, a newline in a single-quoted spec).The scanner allowlist (
literal-loss-scanner-allowlist.txt) is empty: 283,851 injection cells and 149,331 shape cells, 0 disagreements. The}}arm (Lexer.LiteralLoss.cs:401) is an accepted equivalent mutant.Oracle altitude (Design Decision 1)
The C1 twin asks
ast.parse(s, mode="eval")for a numericConstant(value or refused). It does not compare token streams withtokenize, because CPython refuses.5eat parse time while Sharpy refuses in the lexer. Comparing token boundaries would invent divergences the language does not have.01,0_1).j, trailing dot) is pinned as hand rows citing the spec, not left to the twin.Hand-offs and chores
P67 (parser: a node whose last token spans several lines ends on that token's START line — LineEnd/ColumnEnd ignore the lexer's span (multi-line strings) #2295):
Sharpy.TestInfrastructure/Lexing/TokenPositionReconstructionis the token half of the position authority. P67 extends it with node ends. The f-string interior span limit above is the open input.Chore: the three existing python twins still soft-skip when python 3.12 is absent:
CPythonDifferentialParseTests.PythonOracle.TryLocateFormatSpecDifferentialTests.PyOracleDifferentialExecutionTestsOnly the new numeric twin honours
SHARPY_REQUIRE_PYTHON_ORACLE=1, which CI sets for every Compiler shard (d15d7194a). The retrofit is moving each of them ontoSharpy.TestInfrastructure.Oracles.PythonInterpreter.TryLocate(minMinor: 12)and the required mode. It is seam-free; it goes in the chores band.The same chore covers the "skip" itself (found by the refuting verifier @ 0d58024). With no Python 3.12 and the env var unset, every python twin is counted Passed, not Skipped:
NumericLiteralTwinTests.cs:464),PythonInterpreterTests, and the three existing twins all writeSKIP:toITestOutputHelperand return. That output is visible only at detailed verbosity.SHARPY_REQUIRE_PYTHON_ORACLE=1is set at thecompiler:job level and makes the numeric twin fail (verified with PATH reduced to python 3.9: 1 failed,PythonOracleMissingException).Xunit.SkippableFact, orAssert.Skipafter an xUnit v3 move), applied to all five sites in one place.Close criterion: no allowlist row cites #2300, and the re-run's score is recorded.
antonsynd commentedon Oct 22, 2026
P68
/verify-implementationrecord (220259b..96d7a27)Gate: 81093 passed / 0 failed / 7 skipped @ be4664e (measured). The same count as @ 2c15c47. 96d7a27 changes one code comment. Format clean.
build_toolspytest 1232 passed.check_allowlist_issue_state.shclean. Spy, spy-test and spec-block staleness gates are clean @ 0d58024.Four refuting auditors ran: completeness, regression, guard mutation and sibling cells. No regression. The production edit (TokenizeAll) is behaviour-neutral by reading, and by eight fixtures run.
Fixed in this round (each mutation-tested, recorded in the commit body)
01lexing as'017'passed, and a wrong Value on the #2338FStringEndwas absorbed01 => int:1) or the token's Value and rules (FStringEnd '"""'@L5:C24 R3,R7). FStringEnd Value mutant red, pin edits red:=exclusion (LiteralLoss.cs:178) went unobserved in the Lexer namespaces@walrusclean twin, mutant red. A first spelling (walrus at line start) did not kill it and was measured and replacedRunBatchbroken pipe produced a bare IOException with no stderrwithinfullmatch→search, unscored-status gating, and the oracle's int formatting survived pytestlexer.jsoncarriesignore-methods; it has noneCells for this tracker (lane · reach)
lexes as Identifier `''` with no diagnostic. `run` then dies on a Debug.Assert, "Identifier.Name cannot be null or empty" (`Parser/Ast/Expression.cs:506`), exit -6.a b= 1,1x= 1andx.are SPY0599 behind Roslyn.identifiers.md: backticks are "equivalent to C#'s @ prefix", which needs an identifier. The EOF roster's "closed backtick" construct only checks that it lexes./batch-issues, not a lexer defect yet.𝒳 = 1(U+1D4B3) is SPY0015, and the message names a lone'\uD835'.identifiers.md:19says "Unicode letters are allowed". CPython accepts it. Axiom 1 (Roslyn's identifier alphabet) may decide.Skip=, so those arms are unobserved until lexer: df dedented f-strings skip both SPY0029 refusals that d applies — a shallow content line and a close not on its own line run and print altered text #2337 lands. A pin-today's-behaviour twin was considered and NOT written, because it would assert the bug.Filed elsewhere
1e400/1e39fare SPY0599,1e29mis a SPY0909 ICE.\r(an extra SPY0026 under CRLF), and thed"""position cell.Still open on this tracker
P5T3, the Stryker re-run, is owner-slotted for the weekend. The
01/0_1rows still cite #2300 (Design Decision 4). #2300 must not close while they do: the issue-state gate would turn red.docs(spec): drop the stale `to` keyword row and the four "transitiona…
test(stryker): survivor triage script with the R-GB ignore list as da…
test(lexer): the clean lexer's line facts (opener crossing, continuat…
test(lexer): injection differential — the literal-loss scanner's verd…
test(lexer): the P22 shape builders over the corpus judge the lexer's…
test(lexer): an @unbalanced-brace clean twin — a lone {{ then a # in …
docs(lexer): the scanner allowlist's documented key format names the …
test(infra): one python-oracle locator for the differential twins; a …
test(build-tools): CPython numeric-literal oracle (ast.parse at the c…
7 remaining items
docs(gap-discovery): roster rows for the P68 lexer harnesses (refs #2300
refactor(lexer): TokenizeAll reads the token inside its try so Stryke…
docs(stryker): measure what ignore-methods skips on a two-file probe;…
docs(stryker): the TokenizeAll CS0165 safe-mode hole is measured clos…
test(lexer): allowlist rows pin today's defect — a second change on a…
test(lexer): injection differential at a fixed bulk stride inside the…
test(infra): RunBatch reports the script's exit and stderr when it st…
test(build-tools): triage's `within` is a full callee match and the l…
docs(stryker): the ignore-methods decision is for the re-run — lexer.…
docs(lexer): the injection stride halves the test's CPU; it does not …
Merge pull request #2341 from antonsynd/dev
antonsynd commentedon Oct 22, 2026
Batching 2026-10-21 @ e7e0a0e — charter
.claude/plans/remediation-round-2026-09-20-batching.md§3.6c (P68 IMPLEMENTED + VERIFIED, gate 81093/0/7 @ be4664e measured; moved to §2) and §3.0 (two ledger rows ride the chores band). #2300 stays OPEN for the P5T3 Stryker re-run (owner: WEEKEND slot, concurrency 4, ~16.7 h derived;build_tools/stryker/lexer.jsongainsignore-methodsfirst) and the two01/0_1rows innumeric-literal-twin-allowlist.txt(lines 16–17) until R-HT below moves them.Ruling R-HT (owner, 2026-10-21; D1 a): RECORD
01/0_1— adocs/deviations.yamlrow (leading-zero-decimal-integer, syntax, python audience, severity none, example executed at HEAD) plus oneinteger_literals.mdsentence "leading zeros are digits (C#'s rule)"; the two allowlist rows move intoNumericLiteralTwinTests.SharpyOnlyRows_LexAsTheSpecSaysciting the row; no compiler change. Measured @ e7e0a0e:print(01, 0_1, 007)runs printing1 1 7; CPython 3.12.13SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers(00and0accepted by both); C# accepts007; the spec has no grammar block and is silent (fidelity-scope D5: DEVIATION until promoted). Rejected: (b) promote — refuse like CPython with a0osteer as a cell on the #2339 chore (the analysis's recommendation). Door: (a) accepts a spelling users may come to depend on; a → b later breaks programs — the one-way choice, taken knowingly; do not re-ask.Ruling R-HW (owner, 2026-10-21; D4 a): RECORD supplementary-plane identifier letters — a
docs/deviations.yamlrow (identifier-letters-bmp-only, syntax, python audience) plus oneidentifiers.mdsentence beside line 19: identifier letters are letters of the Basic Multilingual Plane, C#'s identifier alphabet (Axiom 1); the message naming half a surrogate pair is written into the row'ssharpy_behavioras a message-lane cell (printing the full code point is a one-line courtesy the #2339 chore may take, not a requirement). Measured @ e7e0a0e:𝒳 = 1(U+1D4B3) → SPY0015 "Unexpected character: '�'" at 2:5 naming the lone high surrogate\uD835; controlété = 1runs printing1; CPython accepts𝒳. Roslyn'sSyntaxFacts.IsIdentifierStartCharacter(char)/IsIdentifierPartCharacter(char)take a UTF-16 unit (grep-only of the 5.9.0 API doc; a surrogate's category is Cs), so C# has no spelling for the identifier, not even a\uescape. Rejected: (b) promote — accept and mangle to a C#-legal name (a new mangling is an interop surface C# consumers can depend on; one-way; a spelling nobody writes). (a) is relaxable to (b) later.The other cells of the record and verify posts, re-homed (no ruling): the backtick-content cell (empty
→ Identifier `''` → `Debug.Assert` at `Parser/Ast/Expression.cs:506`, exit -6;a b/1x`` → SPY0599) is posted on #2287 (P62, whose construct axis lists backtick identifiers); the two recovery-cascade message cells (f"a⏎b"→ SPY0002 then SPY0026; the df-triple refusal then SPY0001) are posted on #2274 (the permanent lexer known-limit tracker, R-FQ); the C2 instrument limit (f-string interior tokens `FStringText`/`FStringFormatSpec`/`FStringConversion` carry no `SourceLength`, so `TokenPositionReconstruction` cannot judge them) is posted on #2295 (P67 extends the instrument); the three format-spec cells stay on #2044 (P22c). The lexer DEFECTS filed by the rounds are placed by R-HX: #2337 → P22c (pulled to row 6c‴ by R-HY), #2338 → P67, #2339 → chore; #2340 → chore (R-HU R-HV); the python-twin `SKIP:`-and-return note is chore #2342 (R-HZ).docs(deviations): leading-zero decimal integers and BMP-only identifi…
antonsynd commentedon Oct 22, 2026
Landed (chores band 2026-10-22) on
dev@ 4d22212, PR #2346 to mainline. Whole-solution gate 81669/0/7 @ 4d22212 (measured), against 81093/0/7 @ be4664e.Ledger rows, commit 9e6dcb2 (R-HT, R-HW):
leading-zero-decimal-integer, with aninteger_literals.mdsentence and an output-asserted spec block.identifier-letters-bmp-only, with anidentifiers.mdsentence.The
01/0_1rows are deleted fromnumeric-literal-twin-allowlist.txt. They now pin the SharpyOnly table, with the Sharpy verdict per row. The oracle ledger is regenerated:--checkreports 104 deviations, up to date.Mutations:
SharpyAcceptsturned 1 row red.Stays open for P5T3, the Stryker re-run on
Lexer/. It is scheduled for the weekend at concurrency 4.Merge pull request #2346 from antonsynd/dev