← board

NilPy str() prints a string's POINTER and a bool's 1

Found 2026-07-19 building [[feature-nilpy-fstrings]].

name = "bob"
print(str(name))    # CPython: bob     pxx: 4276256
print(str(5))       # CPython: 5       pxx: 5
print(str(True))    # CPython: True    pxx: 1
print(str(1.5))     # CPython: 1.5     pxx: 1.5

Both wrong cases are SILENT — plausible-looking output, no diagnostic.

str() on a class instance is the same shape: it prints the pointer as a decimal number where CPython prints <__main__.W object at 0x...>. That one cannot be diffed against CPython (the address varies), so it is not a test-gate item — but it belongs to the same lowering, and uforth calls str(t).upper() on an Any, so whatever this fix chooses for the object case decides what that produces.

Cause

str() is parsed in parser.inc (gated on isNilPy) into AN_CALL with ASTIVal = -201, and lowered in ir.inc to one of StrInt / FloatToStr / VariantToStr. The lowering has no case for tyString / tyAnsiString — the argument falls through to StrInt and the string HANDLE is formatted as an integer — and no case for tyBoolean, which reaches StrInt as 0/1 rather than Python's False/True.

Both files are Track A, which is why this is filed rather than fixed under N.

Shape

Add the two cases to the cpi = -201 lowering: a string argument is already the answer (identity), and a boolean picks the Python spelling. pylib now has pystr_of overloads that do exactly this per type and could be reused as the lowering target rather than growing the intrinsic.

Note

[[feature-nilpy-fstrings]] does NOT wait for this: an f-string's whole job is producing text, so the expander emits pystr_of(...) — argument type picks the spelling through ordinary overload resolution — and f-strings are correct today. Bare str() in user code is still wrong, which is this ticket.

Gate

make test + self-host byte-identical, test-nilpy green, and the four lines above matching CPython.

Log