A folded string literal reaches a managed parameter raw
print(int("1" + "2")) # CPython 12
# pxx: ValueError: invalid literal for int() with base 10:
# '1212ab\xdc\xb3a...'
print(str("a" + "b")) # CPython ab pxx printed an empty string
x = "a" + "b"
print(str(x)) # ab — correct, via a name
Found 2026-08-15 by a CPython differential sweep. Track A: the fold is in
ir.inc and is target-independent, so the same mismatch is reachable from any
frontend whose parser types a literal + chain tyAnsiString.
Cause — the AST's type and the lowered VALUE disagreed
IRLowerAST's binop arm folds 'a' + 'b' to one interned literal and returns
an IR_CONST_STR typed tyString — deliberately, and the note there records
why tagging it tyAnsiString is not the fix (a static literal pointer treated as
a heap handle is released at scope exit).
But the AST node it replaces was typed tyAnsiString by the parser, and
IRLowerCallArg decides how to pass a string argument from ASTTk[argAST].
Seeing tyAnsiString it concluded "already a managed handle, pass it through",
so the callee received a pointer to static bytes and read its length from the
eight bytes in front of the text — hence a ValueError quoting the digits
followed by heap junk, and an empty string from pystr_of.
The fold now materialises through a hidden OWNING local when the replaced
expression was tyAnsiString — the store carries the literal-to-managed
conversion, the load hands over a real handle, and scope exit releases it.
That is exactly what IRPromoInitFromLiteral already does, for the same
reason, three thousand lines up.
Third instance today of one rule: take the kind from the value, not from the
AST — see project_variant_store_kind_came_from_the_ast_not_the_value and
[[bug-nilpy-int-of-a-division-reads-the-doubles-bits]].
Consumers that were already right stay right, and that asymmetry is what made
this survivable: len, ==, .upper(), list(), .encode() and a further
+ all take the literal at a position that re-reads its type, so only the
managed-parameter path saw the mismatch.
Gate
test/test_nilpy_folded_literal_as_argument.npy (+.expected, in the
Makefile), byte-identical to CPython: int, str and float over a folded
literal; the implicit "1" "2" spelling the lexer turns into the same +; via
a name as the control; the consumers that were already correct; a folded literal
as an ordinary def argument, inside a list, as a dict value and as a dict KEY;
one used twice; and a three-part fold. gate.sh quick GREEN — self-host
converged in two rounds, the compiler's own build being a consumer of the fold.