Skip to content

error, baseobjspace, function: four more surrogate-bearing surfaces, and the parity coverage for them - #1116

Merged
youknowone merged 3 commits into
mainfrom
wasm-jit
Aug 9, 2026
Merged

error, baseobjspace, function: four more surrogate-bearing surfaces, and the parity coverage for them#1116
youknowone merged 3 commits into
mainfrom
wasm-jit

Conversation

@youknowone

@youknowone youknowone commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Follow-up to #1089, addressing its review findings. The PR merged before the
review was worked through, so the fixes land separately.

Four more surrogate-bearing surfaces (4ac2cc9)

wrap_dict_key_hash_error and wrap_set_element_hash_error built the outer
message from message_text(), whose display-side escape put the six characters
spelling \udcff into the new exception's own args[0]. They take
message_wtf8() instead, so the wrapped value is carried as the value it is and
escaping stays with whatever displays it.

method_repr read the bound function's name with w_str_get_value_opt, so a
__qualname__ holding a lone surrogate collapsed the whole name to ?. It now
reads the name as WTF-8 under an is_str filter, which is what makes the
unchecked read sound.

write_syntax_error_object replaced a constructor-supplied text that is not
valid UTF-8 with <unprintable>. The line is kept as WTF-8 and the caret
columns are counted in code points, so the caret still lands under the reported
column.

write_traceback_chain wrote a frame's co_filename as the filesystem bytes,
which left the report a mix of those bytes and the WTF-8 around it. The name is
decoded for display with the same handler that read it off the filesystem, and
the raw bytes are retained separately for opening the source, so the buffer is
WTF-8 throughout and both stderr sinks spend the same backslashreplace encode
on it.

python3.14 renders all four cases with the escape, byte-identically.

Parity coverage (d360a18)

surrogate_name_messages gains the bound-method repr and the dict-key /
set-element hash wrappers. Its ContextVar LookupError case named the variable
with Repr.__name__, which held no surrogate and so could not have failed; it
now uses the surrogate-bearing string.

surrogate_traceback_render gains the constructor-supplied SyntaxError text.

Two try blocks in mapdict_devolved_raising_eq asserted that a raising
__eq__ propagates but had no else arm, so a read that returned a value was
reported as a pass.

Review findings not fixed here

Two were checked against python3.14 and the current behaviour is correct, so
nothing changed: the property __qualname__ spelling and the ImportError
case.

Three are filed as follow-up tasks rather than widened into this diff:

  • the borrowed-&str probe in dictmultiobject wants a reentrancy audit under
    IndexMap::get
  • dict-key / set-element hash errors name the type by __name__ rather than
    __qualname__ — verified byte-identical on main, so it predates this work
    and is a separate divergence from the one fixed above
  • pyrex should keep the script path an OsString through parse_args

Summary by CodeRabbit

  • Bug Fixes

    • Preserved unusual filenames and lone surrogate characters in tracebacks, syntax errors, exception messages, and bound method representations.
    • Improved caret positioning and diagnostic output for source code containing invalid characters.
    • Dictionary and set errors now display affected keys accurately without lossy character replacement.
    • Exceptions raised during key or attribute comparisons now propagate correctly instead of appearing as missing entries.
    • Improved handling of far-future file timestamps while respecting filesystem limits.
  • Tests

    • Added coverage for invalid-character error messages, comparison exceptions, and timestamp behavior.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 70dcc4e3-0c85-4c62-8e41-49adc3f3a6b4

📥 Commits

Reviewing files that changed from the base of the PR and between eba36d1 and 228d41f.

📒 Files selected for processing (7)
  • pyre/extra_tests/parity_tests/mapdict_devolved_raising_eq.py
  • pyre/extra_tests/parity_tests/os_utime_pathconf_truncate.py
  • pyre/extra_tests/parity_tests/surrogate_name_messages.py
  • pyre/extra_tests/parity_tests/surrogate_traceback_render.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/error.rs
  • pyre/pyre-interpreter/src/function.rs

Walkthrough

The interpreter now preserves WTF-8 names and lone surrogates in exception, traceback, syntax-error, and bound-method output. Parity tests cover these cases, raising comparisons, and platform-specific timestamp clamping.

Changes

WTF-8 preservation

Layer / File(s) Summary
Exception and traceback output
pyre/pyre-interpreter/src/baseobjspace.rs, pyre/pyre-interpreter/src/error.rs
Error messages, filenames, stderr output, and syntax-error source text now preserve WTF-8 bytes and lone surrogates.
Surrogate names and parity checks
pyre/pyre-interpreter/src/function.rs, pyre/extra_tests/parity_tests/surrogate_name_messages.py, pyre/extra_tests/parity_tests/surrogate_traceback_render.py, pyre/extra_tests/parity_tests/mapdict_devolved_raising_eq.py
Bound method representations and error text preserve surrogate names. Tests cover syntax errors, hash failures, lookup errors, and raising comparisons.

Platform parity assertions

Layer / File(s) Summary
Far-future timestamp assertions
pyre/extra_tests/parity_tests/os_utime_pathconf_truncate.py
The utime test accepts filesystem clamping and verifies bounded, positive, round-tripping nanosecond timestamps.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Poem

A rabbit saw surrogates stay,
Through errors, names, and paths of gray.
No replacement marks appeared,
While clamped timestamps were cleared.
“Hop, hop—parity wins today!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the four surrogate-bearing surfaces and the added parity coverage, which matches the main changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wasm-jit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 228d41f).
Updated: 2026-08-09T06:56:35.002Z

Files in the reviewed diff
pyre/extra_tests/parity_tests/mapdict_devolved_raising_eq.py
pyre/extra_tests/parity_tests/os_utime_pathconf_truncate.py
pyre/extra_tests/parity_tests/surrogate_name_messages.py
pyre/extra_tests/parity_tests/surrogate_traceback_render.py
pyre/pyre-interpreter/src/baseobjspace.rs
pyre/pyre-interpreter/src/error.rs
pyre/pyre-interpreter/src/function.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

None.

3. Pre-existing mismatches (already present before this patch)

None.

4. Structural adaptations

  • pyre/pyre-interpreter/src/baseobjspace.rs:146 ↔ pypy/objspace/std/dictmultiobject.py:740: Pyre retains CPython 3.14’s contextual wrapping of an exact TypeError raised by hashing ("cannot use … as a dict key"); PyPy simply invokes space.hash(w_key) and propagates its exception. The patch only changes the wrapped message from display-escaped text to its original WTF-8 value at baseobjspace.rs:159-165. This is a Python 3.11-versus-3.14 language-version adaptation, not a faulty PyPy port. The equivalent set path is baseobjspace.rs:170-187.

  • pyre/pyre-interpreter/src/function.rs:2819-2829 ↔ pypy/interpreter/function.py:585-599: Pyre uses Rust’s WTF-8 string representation to splice a surrogate-bearing __qualname__ into a bound-method repr; PyPy uses its RPython UTF-8 byte representation (space.utf8_w). The resulting intent is equivalent—preserve the Unicode value—while the storage/API shape necessarily differs between Rust and RPython.

  • pyre/pyre-interpreter/src/error.rs:1902-1952 ↔ pypy/interpreter/pyparser/error.py:81-103: Pyre’s standalone Rust traceback renderer counts WTF-8 code points and writes its own diagnostic buffer, whereas PyPy converts parser byte offsets to Unicode offsets before constructing SyntaxError. Supporting constructor-supplied lone-surrogate SyntaxError.text is a Rust/WTF-8 adaptation; PyPy’s parser-source path is byte-oriented and not a 1:1 implementation match.

  • pyre/pyre-interpreter/src/error.rs:2875-2885 ↔ pypy/objspace/std/objspace.py:438-439: Pyre retains filenames as syscall bytes for opening source, then decodes them with filesystem surrogateescape only for rendering; PyPy’s newfilename() immediately applies fsdecode() to its byte string. This byte/text split is required by the Rust host/filesystem boundary and preserves the same displayed filename semantics.

  • pyre/pyre-interpreter/src/error.rs:1493-1501 ↔ pypy/interpreter/error.py:335-345: Pyre converts its assembled WTF-8 diagnostic buffer to a guest string before calling sys.stderr.write; PyPy directly runs app-level traceback printing. The changed fallback preserves surrogate escapes instead of replacing invalid bytes with U+FFFD, an implementation-language/host-seam adaptation rather than a semantic divergence.

…aces as WTF-8

`wrap_dict_key_hash_error` and `wrap_set_element_hash_error` built the outer
message from `message_text()`, whose display-side escape put the six
characters spelling `\udcff` into the new exception's own `args[0]`;
they take `message_wtf8()` instead.

`method_repr` read the bound function's name with `w_str_get_value_opt`, so a
`__qualname__` holding a lone surrogate collapsed the whole name to `?`.

`write_syntax_error_object` replaced a constructor-supplied `text` that is
not valid UTF-8 with `<unprintable>`; the line is kept as WTF-8 and the
caret columns are counted in code points.

`write_traceback_chain` wrote a frame's `co_filename` as the filesystem
bytes, which left the report a mix of those bytes and the WTF-8 around it.
The name is decoded for display and the bytes are kept for opening the
source, so the buffer is WTF-8 throughout and both sinks spend the same
`backslashreplace` encode on it.

python3.14 renders all four cases with the escape, byte-identically.

Assisted-by: Claude
…ertions

`surrogate_name_messages` gains the bound-method repr and the dict-key /
set-element hash wrappers, and its `ContextVar` LookupError case now names
the variable with the surrogate-bearing string instead of `Repr.__name__`,
which held no surrogate and could not have failed.

`surrogate_traceback_render` gains the constructor-supplied `SyntaxError`
text.

Two `try` blocks in `mapdict_devolved_raising_eq` asserted that a raising
`__eq__` propagates but had no `else` arm, so a read that returned a value
was reported as a pass.

Assisted-by: Claude
`os_utime_pathconf_truncate.py` asserted `st_mtime_ns == int(st_mtime) *
1_000_000_000` for a time past what the filesystem can hold. An APFS
timestamp is an int64 of nanoseconds and clamps to `2**63 - 1`, whose
remainder is 854775807ns, so the assertion failed on macOS under cpython,
dynasm and cranelift alike.

Assert instead that the count neither wrapped nor names a later instant
than the one asked for, and that `st_mtime` is the double the same
`(second, nanosecond)` pair derives.

Assisted-by: Claude
@youknowone
youknowone merged commit 6d50ef7 into main Aug 9, 2026
15 of 17 checks passed
@youknowone
youknowone deleted the wasm-jit branch August 9, 2026 07:54
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