← board

\ before a newline inside a string literal is not a line continuation

The bug

Python's lexer consumes \ + newline inside a string literal, emitting nothing. NilPy emits both characters literally.

Repro

s = '''\
AB
CD'''
print(repr(s))
t = "x\
y"
print(repr(t))

pxx (pinned, HEAD df15ae3fe) prints '\\\nAB\nCD' then 'x\\\ny'.

CPython prints 'AB\nCD' then 'xy'.

Both literal forms are affected, so it is the escape handling in the string scanner, not anything specific to triple quotes.

Why it matters more than it looks

This is a silent wrong value, not an error. The string still builds, the program still runs, and the damage is two extra characters in the middle of text — the kind of thing that surfaces far from the cause, which devdocs/dev/debugging-playbook.md calls the expensive case.

'''\ at the start of a triple-quoted block is a common Python idiom precisely because it lets the first line of the content start at column 0, so this is not an exotic corner.

How it was found — a worked example of the damage

library_candidates/webencodings/webencodings/mklabels.py is a code generator that prints a Python module. Run under pxx and under CPython against the same local HTTP endpoint, the two outputs differ by exactly one line: pxx's generated file starts with a stray \ line. Everything else — all the label mappings, the alignment padding, the JSON decoding — is identical.

So the generated module would be a syntax error in the file it generates, from a generator that ran without complaint. That is the whole failure mode in one artifact.

Reproduce it with any local server:

sed 's|http://encoding.spec.whatwg.org/encodings.json|http://127.0.0.1:PORT/enc.json|' \
  library_candidates/webencodings/webencodings/mklabels.py > mklabels_local.py
pinned mklabels_local.py out && ./out > pxx.txt
python3 mklabels_local.py > cpy.txt
diff cpy.txt pxx.txt      # one line: a leading '\'

with enc.json holding the WHATWG shape, e.g. [{"encodings":[{"labels":["utf-8","utf8"],"name":"UTF-8"}],"heading":"x"}].

Not a urllib bug

Worth stating because of where it was found: urlopen is not involved. The generator's HTTP fetch, its .read().decode('ascii') and its json.loads all produce byte-identical results to CPython — the diff is entirely in the literal that the generator prints.


RESOLVED 2026-08-19 — genuinely lexer-sized, one missing case arm

Reproduced at HEAD exactly as filed. Root cause is a missing arm, not a wrong one: the escape case in compiler/pylexer.inc (the single- and triple-quoted string scanner) handles n t r a b f v \ ' " x and octal, and has no arm for a newline — so \ + newline fell through to the lax else that emits a literal backslash followed by the character. That else is the deliberate lax-dialect fallback for unknown escapes, which is why the failure was silent and produced a plausible wrong VALUE rather than an error.

Added #10 and #13 arms that consume the newline and emit nothing.

Two details that are easy to get wrong and are covered:

Raw literals are deliberately unchanged

Python keeps backslash-newline verbatim in an r-string, and the rawStr arm above already did. Confirmed rather than assumed — r"a\<nl>cd" still yields 'a\\\ncd', matching CPython.

Verified — every literal form, diffed against CPython

form pxx CPython
"x\<nl>y" 'xy' 'xy'
'''\<nl>AB<nl>CD''' 'AB\nCD' 'AB\nCD'
r"a\<nl>cd" 'a\\\ncd' 'a\\\ncd'
b'a\<nl>cd' b'acd' b'acd'
f"a\<nl>{v}" 'a5' 'a5'
"a\\nb" (ordinary \n) 'a\\nb' 'a\\nb'

Regression test — WIRED, not just written

test/test_nilpy_str_line_continuation.npy + .expected, covering all six rows above. The expectation is generated by CPython, not hand-written.

Wired into Makefile beside test_nilpy_str_isnumeric_istitle, and the wiring was verified the way [[frank2-a-test-file-is-not-a-test]] says to: make -n compiler/pascal26 to prove the Makefile still parses without going near the suite, then running the two wired lines verbatim. A .npy in test/ that no build rule names is green today and uncovered forever.

Gate

make compiler/pascal26 converged after 1 round (self-host fixedpoint), the six probes above diffed against CPython, and tools/gate.sh quick.

Log