Skip to content

macros: give #[pyre_methods] shims a Signature and drop the marker-dict peel - #1108

Merged
youknowone merged 4 commits into
mainfrom
rtyper-legacy
Aug 8, 2026
Merged

macros: give #[pyre_methods] shims a Signature and drop the marker-dict peel#1108
youknowone merged 4 commits into
mainfrom
rtyper-legacy

Conversation

@youknowone

@youknowone youknowone commented Aug 7, 2026

Copy link
Copy Markdown
Owner

This branch carries four commits on top of origin/main. The top commit is the
#[pyre_methods] signature port described below; the three beneath it are
prior in-flight rtyper-legacy work (annotator enum-variant projection, front-end
type-vs-variant leaf harden bucket, getattr owner-stamp/getdictvalue residuals).

Summary

Ports upstream's BuiltinCode/Signature gateway to #[pyre_methods]-generated
shims so builtin keyword arguments resolve by name at call setup into positional
PY_NULL-padded slots, and drops the split_builtin_kwargs marker-dict peel from
the method wrapper preamble. This is the first slice of deleting the __pyre_kw__
marker-dict keyword ABI (a documented PRE-EXISTING-ADAPTATION).

#[pyre_methods] now builds a per-method Signature from its parameter tables and
registers every arm through a signature-aware constructor:
make_builtin_function_maybe_sig (default), the new
make_builtin_function_with_text_signature_and_sig (all-required instance arm), and
the new make_new_descr_maybe_sig (__new__). Instance methods prepend self as a
positional-only slot; a raw &[PyObjectRef] whole-args method carries None. The
method wrapper preamble then computes __pyre_positional_count as the leading
non-null run of args rather than peeling a trailing marker dict.

134 method shims move off the marker ABI. Parity with upstream (keyword resolution at
BuiltinCode/Signature setup, no marker dict) is the primary result.

Scope

  • Methods only. Functions register at their call sites via bare
    make_builtin_function, which drops the macro-computed signature, so their preamble
    keeps the marker-tolerant path. The single marker producer and split_builtin_kwargs
    remain for null-signature functions and the ~50 hand-written direct callers.
  • The #[pyre_function] follow-up is deliberately deferred: only 3 real standalone
    functions exist (all arity 0-1 that never receive keywords), and the macro cannot own
    their external registration, so a marker-free function preamble would be fragile with
    near-zero yield.

Test changes

Rewrites the base-owned jitcode_dispatch test
keyword_builtin_wrapper_finds_colored_argument_slice_item_descr (now
signature_bound_wrapper_reads_argument_slice_with_distinct_item_descr): the
__pyre_wrap_getrandbits wrapper no longer opens with the split_builtin_kwargs call
and reads its argument array off the wrapper input register, so the test asserts the
entry is not an inline_call_* and keeps the item-descr/length-descr distinctness
invariant. De-stales the wrapper_args_item_descr_index doc comment accordingly.

Verification

  • cargo test --all --features dynasm: green (adds a dual-path parity test proving a
    keyword-only method binds identically positionally and by name).
  • python3 pyre/check.py (dynasm/cranelift/wasm): this branch introduces zero new
    failures
    . The base commit is red on this host on 5 pre-existing fixtures — two
    jit-stats snapshots not re-recorded by the rebase (del_cellvar_walk_commit,
    pypy_type_surface) and three GC-crash / wrong-output fixtures (closure_per_call,
    list_length_hint_validate, make_function_inline). Each was confirmed base-owned by
    a branch-minus-commit control: with the 6 changed files restored to the parent commit,
    all five reproduce identically. This branch touches no struct layout, type_id, or GC
    surface. jit-stats snapshots are intentionally not re-recorded.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved Python method signatures, including positional-only, keyword-only, default, and variadic arguments.
    • Added more accurate signature information for built-in methods and constructors.
    • Improved enum variant registration and payload field handling.
  • Bug Fixes

    • Prevented storage migration when dictionary reads fail.
    • Corrected enum type-leaf processing and preserved variant behavior.
  • Tests

    • Expanded coverage for signature binding, defaults, keyword calls, and optimized wrapper behavior.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR canonicalizes enum variant registration, adjusts enum type-leaf bucketing, adds signature-aware builtin and descriptor construction, updates generated method wrappers, adds binding and JIT tests, and propagates checked dictionary-read errors before migration.

Changes

Enum translation updates

Layer / File(s) Summary
Canonical enum variant registration
majit/majit-translate/src/annotator/bookkeeper.rs
Enum variants use the canonical resolver, including payload projection and pending struct-row handling.
Enum leaf bucketing
majit/majit-translate/src/front/mir.rs
Enum-base parent and leaf pairs are excluded from type-leaf buckets. Other variant leaves remain included.

Signature-aware Python wrappers

Layer / File(s) Summary
Signature-aware runtime constructors
pyre/pyre-interpreter/src/builtins.rs, pyre/pyre-interpreter/src/gateway.rs, pyre/pyre-interpreter/src/typedef.rs
The interpreter adds leading non-null argument counting and optional signature support for builtins and __new__ descriptors.
Generated method signatures and wrappers
pyre/pyre-macros/src/lib.rs
#[pyre_methods] emits keyword-aware signatures and counts positional arguments from bound slots without manual keyword rebinding.
Registration and wrapper validation
pyre/pyre-macros/src/lib.rs, pyre/pyre-interpreter/src/module/_random/macro_smoke.rs, pyre/pyre-jit-trace/src/jitcode_dispatch/*
Method registration passes signatures to constructors. Smoke and JIT tests validate keyword-only defaults and argument-slice descriptor handling.

Checked dictionary reads

Layer / File(s) Summary
Read-before-migration error handling
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
Checked dictionary reads now return errors before boxed-storage migration runs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MethodMacro
  participant BuiltinGateway
  participant DescriptorBuilder
  participant BoundMethod
  participant SmokeAndJITTests
  MethodMacro->>BuiltinGateway: provide generated Signature
  BuiltinGateway->>DescriptorBuilder: select signature-aware constructor
  DescriptorBuilder->>BoundMethod: create configured builtin or __new__ descriptor
  BoundMethod->>SmokeAndJITTests: bind positional, keyword-only, and default arguments
Loading

Possibly related issues

  • youknowone/pyre#809 — The PR advances Signature adoption and removes wrapper-side keyword binding through pyre_methods and constructors.

Possibly related PRs

  • youknowone/pyre#904 — Both changes update method-wrapper argument handling and related gateway helpers.
  • youknowone/pyre#1094 — Both changes update signature-aware builtin and descriptor registration in typedef.rs.
  • youknowone/pyre#1106 — Both changes modify JIT inline-call wrapper handling, but address different argument-flow behavior.

Suggested reviewers: fregataa

Poem

A rabbit binds each carrot right,
With keyword leaves in neat delight.
Enum branches join their base,
Failed reads stop before migration takes place.
The wrappers hop through tests tonight.

🚥 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 summarizes the main changes: adding Signature support to #[pyre_methods] shims and removing marker-dictionary peeling.
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 rtyper-legacy

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 7, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 30b16fa).
Updated: 2026-08-08T02:43:43.295Z

Files in the reviewed diff
majit/majit-translate/src/annotator/bookkeeper.rs
majit/majit-translate/src/front/mir.rs
pyre/pyre-interpreter/src/builtins.rs
pyre/pyre-interpreter/src/gateway.rs
pyre/pyre-interpreter/src/module/_random/macro_smoke.rs
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
pyre/pyre-interpreter/src/typedef.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs
pyre/pyre-macros/src/lib.rs

Codex did not produce a report (exit 1). Last log lines:

are available locally (under `rpython/` and `pypy/` in this repository).

If anything was ported incorrectly, report every instance in detail. After
collecting all differences, organize the report into separate sections:

1. Cases where our patch regressed PyPy parity compared to main
2. Other mismatches introduced by our patch
3. Mismatches that already existed before this patch
4. Structural adaptations

Exceptions: some differences cannot be ported 1:1 because of Python 3.11 vs
3.14 differences, opcode mismatches caused by using a CPython-compatible
compiler, GIL/free-threading differences, and fundamental implementation-
language differences between RPython and Rust. Mark those separately under
"Structural adaptations".

Scope discipline: before writing the report, run
`git diff upstream/main --name-only -- . ':(exclude)*.jitstats'` and treat that
file list as the authoritative definition of "this patch" (when an authoritative
changed-file list is appended below, use that instead of re-deriving it). The
excluded `*.jitstats` files are `pyre/check.py`'s recorded jit-stats baselines —
generated golden data with no RPython/PyPy counterpart, so no parity finding can
cite one, and a bulk re-record of them is not a change to review. Findings under
sections 1 and 2 MUST cite our-side files from that list; a divergence in any
file NOT in the list is by definition not introduced by this patch — report
it under section 3 instead, or omit it. Verify every section-1/2 citation
against the list before finalizing the report.

---

Output format requirements (so the report can be parsed mechanically and
posted/triaged automatically). Use these four headings VERBATIM, in this
order, and nothing else at heading level 2:

## 1. Regressions to PyPy parity introduced by this patch
## 2. Other mismatches introduced by this patch
## 3. Pre-existing mismatches (already present before this patch)
## 4. Structural adaptations

Under each heading, list every finding as a bullet. For each finding cite the
concrete `our_file.rs:line ↔ rpython_or_pypy_file.py:line` pair and quote the
divergence concisely. If a section has no findings, still emit the heading
followed by a single line `None.` so all four sections are always present.
Do not modify any files; produce the report only.

Authoritative changed-file list for this patch (git diff upstream/main --name-only,
minus 0 generated `*.jitstats` baseline file(s)):
majit/majit-translate/src/annotator/bookkeeper.rs
majit/majit-translate/src/front/mir.rs
pyre/pyre-interpreter/src/builtins.rs
pyre/pyre-interpreter/src/gateway.rs
pyre/pyre-interpreter/src/module/_random/macro_smoke.rs
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
pyre/pyre-interpreter/src/typedef.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs
pyre/pyre-macros/src/lib.rs
warning: Codex could not find bubblewrap on PATH. Install bubblewrap with your OS package manager. See the sandbox prerequisites: https://developers.openai.com/codex/concepts/sandboxing#prerequisites. Codex will use the bundled bubblewrap in the meantime.
ERROR: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at 3:35 AM.
ERROR: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at 3:35 AM.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@majit/majit-translate/src/annotator/bookkeeper.rs`:
- Around line 759-786: Update the enum-base check in the surrounding
pre-registration logic to call reg.is_enum_base with the qualified root symbol
rather than the bare leaf alias, ensuring retained roots still pre-register
their variants after duplicate-leaf metadata hardening. Add a regression
covering two qualified enums sharing a leaf name but having different
discriminant maps, and verify both enums’ variant ClassDefs exist before
inheritance-ID assignment.

In `@pyre/pyre-interpreter/src/builtins.rs`:
- Around line 4234-4248: Stop using leading_non_null_count in the bound-argument
validation path, since a non-null prefix does not represent positional
provenance after bind_kwargs_to_signature. In pyre/pyre-macros/src/lib.rs lines
1989-1997, preserve raw positional provenance before binding or validate
required bound slots individually while keeping raw-input arity checks separate
from full bound scopes; update pyre/pyre-interpreter/src/builtins.rs lines
4234-4248 so leading_non_null_count is not documented or used as the true
positional count. Add a regression in
pyre/pyre-interpreter/src/module/_random/macro_smoke.rs lines 210-246 covering
an omitted optional slot before a required parameter supplied by keyword.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d822105e-62ae-4ddf-87fc-e1436f534088

📥 Commits

Reviewing files that changed from the base of the PR and between 0b1dee0 and 16310a7.

📒 Files selected for processing (10)
  • majit/majit-translate/src/annotator/bookkeeper.rs
  • majit/majit-translate/src/front/mir.rs
  • pyre/pyre-interpreter/src/builtins.rs
  • pyre/pyre-interpreter/src/gateway.rs
  • pyre/pyre-interpreter/src/module/_random/macro_smoke.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs
  • pyre/pyre-macros/src/lib.rs

Comment on lines +4234 to +4248
/// Length of the leading non-null run of `args`.
///
/// `bind_kwargs_to_signature` pads the flat argument slice out to the full
/// parameter count with PY_NULL for keyword-only slots and absent optionals,
/// so the true positional count is the prefix before the first PY_NULL.
/// A single named function keeps the count off the annotator's shared
/// iterator-adapter graph: a `take_while` closure inlined per wrapper gives
/// every `__pyre_wrap_*` shim its own closure type, and merging those
/// distinct types on one `TakeWhile` graph's input has no common base class.
pub(crate) fn leading_non_null_count(args: &[PyObjectRef]) -> usize {
let mut count = 0;
while count < args.len() && !args[count].is_null() {
count += 1;
}
count

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not infer positional count from a signature-bound scope.

bind_kwargs_to_signature retains parameter slots, but it does not retain argument origin. For fn f(&self, #[default(0)] a: i64, b: i64), f(b=1) reaches the wrapper as [self, PY_NULL, 1]. leading_non_null_count returns 1, and the generated arity check rejects the call even though b is present.

  • pyre/pyre-interpreter/src/builtins.rs#L4234-L4248: Do not describe the non-null prefix as the true positional count after binding.
  • pyre/pyre-macros/src/lib.rs#L1989-L1997: Preserve positional provenance before binding, or validate required bound slots individually. Keep raw-input arity checks separate from full bound scopes.
  • pyre/pyre-interpreter/src/module/_random/macro_smoke.rs#L210-L246: Add a regression test with an omitted optional slot before a required parameter passed by keyword.
📍 Affects 3 files
  • pyre/pyre-interpreter/src/builtins.rs#L4234-L4248 (this comment)
  • pyre/pyre-macros/src/lib.rs#L1989-L1997
  • pyre/pyre-interpreter/src/module/_random/macro_smoke.rs#L210-L246
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/builtins.rs` around lines 4234 - 4248, Stop using
leading_non_null_count in the bound-argument validation path, since a non-null
prefix does not represent positional provenance after bind_kwargs_to_signature.
In pyre/pyre-macros/src/lib.rs lines 1989-1997, preserve raw positional
provenance before binding or validate required bound slots individually while
keeping raw-input arity checks separate from full bound scopes; update
pyre/pyre-interpreter/src/builtins.rs lines 4234-4248 so leading_non_null_count
is not documented or used as the true positional count. Add a regression in
pyre/pyre-interpreter/src/module/_random/macro_smoke.rs lines 210-246 covering
an omitted optional slot before a required parameter supplied by keyword.

pre_register_enum_variant_classes resolved each variant with a bare
intern_enum_variant_host + getuniqueclassdef, which mints the variant
classdef identity but does not project its payload rows. A variant field
typed *mut PyObject stayed an untyped FORCE shell until subject-flow
narrowing first reached it (enum_variant_narrowing_knowntypedata ->
getuniqueclassdef_for_enum_variant), projecting the pointee struct
mid-fixpoint.

Route each variant through getuniqueclassdef_for_enum_variant, which
materializes the discriminant-only base, interns the variant subclass
under the same ::-qualified key, projects the variant payload rows, and
drains the pending struct-row queue before returning — all at prologue
time, before assign_inheritance_ids and any subject flow. The variant
subtree still numbers as one contiguous bracket.

Assisted-by: Claude
harden_duplicate_leaf_metadata's first pass groups struct-field keys by
their last :: segment and withdraws a bare leaf alias when two qualified
keys sharing that leaf carry divergent rows. A {Enum}::{Variant} key
whose variant leaf also names an enum base
(typedef::BytesSubArg::Buffer, rows [__pos_0], leaf Buffer) collided
with the same-named enum type buffer::Buffer (rows [__discriminant]) and
withdrew the bare "Buffer" alias on that spurious divergence.

The variant-leaf pass below already withdraws variant aliases keyed on
the Enum::Variant tail, so such a variant key does not belong in the
type-leaf bucket. Skip a variant key only when its leaf is itself an
enum base — the genuine type-vs-variant name collision — leaving every
other variant-leaf bucket intact.

Restores reg.fields.contains_key("Buffer"), so derive_subject_inputcells
seeds a &Buffer receiver as SomeInstance(Buffer) through
getuniqueclassdef_for_struct_root.

Assisted-by: Claude
instance_node_getdictvalue_checked read the mapdict node into a scoped
Result, ran the unconditional maybe_migrate_to_boxed tail, then returned
that Result — a shell forwarding through a side-effecting intervening
block. lower_result_exc_returns declines that shape (the Err rewrite's
set_raise_values would drop the migrate call), so the whole
dont_look_inside callee was dropped from the SemanticProgram and its
callers failed "not registered in PyreCallRegistry". Propagate the read
with `?` and return Ok(w) after the migrate: the read raises only on the
find_map_attr miss path (mapdict.py:846-847 -> 58 -> 312-313 returns None,
never reaching _direct_read's migration tail), and maybe_migrate_to_boxed
re-derives that None and no-ops, so the migrate is unobservable when the
read raised. The callee now lowers cleanly and registers as its opaque
residual.

stamp_builtin_owner reads the per-type `static OWNER` from the
method_owner table — a field-bearing frozen instance carrying a fn
pointer, which the annotator cannot model as a prebuilt constant (the
address-fold path is gated to zero-field structs). Mark it
dont_look_inside so the owner-stamp setup is a residual-call boundary and
getattr_str_impl's bound-method assembly is not dragged into the
unmodellable static read.

phaseA 1312 -> 1308, no phaseA regressions; check.py bit-exact 3/3
(dynasm 391, cranelift 391, wasm 387).

Assisted-by: Claude
…ct peel

`#[pyre_methods]` now builds a per-method `Signature` from its parameter
tables and registers every arm through a signature-aware constructor:
`make_builtin_function_maybe_sig` (default), the new
`make_builtin_function_with_text_signature_and_sig` (all-required instance
arm), and the new `make_new_descr_maybe_sig` (`__new__`). Instance methods
prepend `self` as a positional-only slot; a raw `&[PyObjectRef]` whole-args
method carries `None`. Removes the dead `is_new` raw_fn branch and
generalizes the former kwonly-only signature arm to every method arm.

With a Signature attached, the call path resolves keywords into positional
PY_NULL-padded slots before the wrapper runs, so the method wrapper preamble
drops `split_builtin_kwargs`/`has_builtin_kwargs`/`bind_builtin_kwargs` and
computes `__pyre_positional_count` as the leading non-null run of `args`.

The leading non-null count is a single named function
`builtins::leading_non_null_count` (an explicit loop) rather than an inlined
`take_while(|s| !s.is_null())` closure. A closure inlined per wrapper gives
every `__pyre_wrap_*` shim its own closure type; merging those distinct
types on one shared `TakeWhile` graph input has no common base class and
fails RPython annotation. A named function keeps the count off that shared
iterator-adapter graph. Both the function-side and method-side preambles
call it.

New gateway.rs/typedef.rs composers thread the optional Signature.

macro_smoke.rs gains `Demo::combine` (positional-or-keyword + keyword-only
default) and a dual-path test asserting positional and keyword-bound calls
agree through `bind_kwargs_to_signature`.

Rewrites the base-owned jitcode_dispatch test
`keyword_builtin_wrapper_finds_colored_argument_slice_item_descr` (now
`signature_bound_wrapper_reads_argument_slice_with_distinct_item_descr`):
the `__pyre_wrap_getrandbits` wrapper no longer opens with the
`split_builtin_kwargs` call and reads its argument array off the wrapper
input register, so the test asserts the entry is not an `inline_call_*` and
keeps the item-descr/length-descr distinctness invariant. De-stales the
`wrapper_args_item_descr_index` doc comment accordingly.

Functions registered at their call sites are unchanged; the marker producer
and `split_builtin_kwargs` remain for null-signature functions and the
hand-written direct callers.

Assisted-by: Claude

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30b16fa161

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2211 to +2215
crate::gateway::make_builtin_function_with_text_signature_and_sig(
#py_name,
#wrapper_name,
#text_signature,
#method_sig,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain the fixed-arity path when adding a signature

For every all-required instance method emitted by this branch, this helper ultimately calls builtin_code_new_with_signature, which sets fast_natural_arity = HOPELESS; consequently even an ordinary exact positional call now enters finish_builtin_code_positional, runs bind_kwargs_to_signature, and allocates a Vec. Before this change the same calls bypassed binding, and upstream BuiltinCode.__init__ still selects its BuiltinCodeN fast class after constructing sig. Preserve the known receiver-plus-parameter arity alongside the signature (analogous to the existing make_builtin_function_with_arity_and_maybe_sig) so adding keyword support does not impose allocation and rebinding on every positional method call.

AGENTS.md reference: AGENTS.md:L231-L233

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
pyre/pyre-interpreter/src/builtins.rs (1)

5413-5449: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Forward kwargs when delegating to the winning metaclass.

At line 5449, call_function_impl_result(w_metaclass_new, &new_args) only passes positionals. When kwargs is present and w_winner != default_meta, real class keywords in kwargs are left behind and the delegation cannot deliver metaclass(name, bases, dict, **kwds). Build the delegate args from the original args/kwargs split or use a kwargs-aware call path before invoking w_metaclass_new.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/builtins.rs` around lines 5413 - 5449, Update the
delegation in the winning-metaclass branch of the class-construction flow to
preserve and forward the original kwargs alongside positional arguments. Replace
the positional-only call to call_function_impl_result for w_metaclass_new with
the established kwargs-aware invocation path, using the original args/kwargs
split so metaclass keywords reach the delegated __new__.
majit/majit-translate/src/front/mir.rs (1)

1823-1873: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the type-vs-variant leaf collision this comment describes.

The comment at lines 1876-1887 documents a concrete, previously-mishandled scenario: a variant leaf (typedef::BytesSubArg::Buffer) colliding with an unrelated enum's bare name (buffer::Buffer). The file already has targeted unit tests for sibling scenarios in this function (harden_withdraws_shape_divergent_bare_alias_and_tombstones_origin, harden_withdraws_discriminant_divergent_bare_enum_alias, harden_withdraws_shape_divergent_variant_leaf_alias), but none constructs this specific "leaf is itself an enum base" case that the new && struct_fields.is_enum_base(leaf) condition targets.

Add a test that registers two enum bases under the same bare leaf name shape (one being a variant tail of a different enum, one being a real enum's own name) and asserts the bare alias for the colliding leaf survives, while confirming an unrelated equal-leaf, non-enum-base collision still gets withdrawn as before. This pins the fix and prevents a silent regression back to the old over-broad skip.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-translate/src/front/mir.rs` around lines 1823 - 1873, Add a
regression test alongside the existing harden_duplicate_leaf_metadata tests,
covering a variant tail such as typedef::BytesSubArg::Buffer colliding with an
unrelated enum base such as buffer::Buffer. Register both enum bases and assert
the shared bare Buffer alias is preserved, while also verifying an equal-leaf
collision that is not an enum base is still withdrawn. Use the existing test
helpers and naming conventions.
pyre/pyre-interpreter/src/objspace/std/mapdict.rs (1)

4612-4627: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Root w_dict before calling self.delitem.

box_str_constant and maybe_migrate_to_boxed can move GC objects. The root set pins w_obj, the key, and the value, but not w_dict. Line 4627 can therefore pass a stale w_dict pointer to self.delitem.

Proposed fix
 let _roots = pyre_object::gc_roots::push_roots();
-let obj_slot = pyre_object::gc_roots::pin_roots(&[w_obj]);
+let slots = pyre_object::gc_roots::pin_roots(&[w_dict, w_obj]);
+let dict_slot = slots;
+let obj_slot = slots + 1;
 ...
-self.delitem(w_dict, pyre_object::gc_roots::shadow_stack_get(key_slot));
+self.delitem(
+    pyre_object::gc_roots::shadow_stack_get(dict_slot),
+    pyre_object::gc_roots::shadow_stack_get(key_slot),
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/objspace/std/mapdict.rs` around lines 4612 - 4627,
Root w_dict before the self.delitem call because box_str_constant and
maybe_migrate_to_boxed may move it during the surrounding mapdict operation.
Extend the existing GC root/pinning setup to include w_dict, then retrieve the
current rooted w_dict immediately before invoking self.delitem, while preserving
the existing rooted key handling.
majit/majit-translate/src/annotator/bookkeeper.rs (2)

6454-6454: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the eprintln! debug probes.

Both statements are labelled "probe". They print on every test run, including passing runs. The adjacent assert! calls already carry failure messages that include the value.

♻️ Proposed change
                     "{field} pre-seed must be untyped force shell, got {value:?}"
                 );
-                eprintln!("probe {field} after force seeding, before projection: {value:?}");
             }
             let value = &attrs.attrs.get(field).expect("tuple field").s_value;
-            eprintln!("probe {field} after constructor setattr: {value:?}");
             assert!(

Also applies to: 6492-6492

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-translate/src/annotator/bookkeeper.rs` at line 6454, Remove both
`eprintln!` debug probes labelled “probe” near the adjacent `assert!` calls,
including the probes after force seeding and before projection and at the
additional referenced location. Leave the assertions and their failure messages
unchanged.

3775-3782: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align the prefix and wrapper stripping with project_pyre_field_type.

is_nullable_sum_spelling decides which shaped-tuple rows the projector must skip. It must therefore recognize exactly the spellings that project_pyre_field_type models as payload-plus-None. It currently does not.

Two divergences exist:

  • Raw pointers. project_pyre_field_type strips *const / *mut at Line 2516 and projects the pointee. is_nullable_sum_spelling strips only & and mut . A row spelled *mut Option<T> projects to the nullable union but is not filtered.
  • Transparent wrappers. project_pyre_field_type unwraps Box<, Rc<, Arc<, RefCell< and the rest at Line 2664 and then reaches the Option< / Result< arms. is_nullable_sum_spelling does not unwrap them. A row spelled Box<Option<Vec<*mut PyObject>>> projects to list-or-none but is not filtered.

In both cases the row survives the retain at Line 2186, filtered_nullable_fields omits it, and the force shell is not reset. The constructor setattr then unions Instance(Option<…>::None) against the projected payload. That is the generalize_attr failure the comment at Line 2176 describes.

🐛 Proposed fix
 fn is_nullable_sum_spelling(field_ty: &str) -> bool {
-    let t = field_ty
+    let mut t = field_ty
         .trim()
         .trim_start_matches('&')
         .trim_start_matches("mut ")
+        .trim_start_matches("*const ")
+        .trim_start_matches("*mut ")
         .trim();
+    // Mirror `project_pyre_field_type`'s transparent-wrapper unwrap so both
+    // sides agree on which spellings model as payload-plus-`None`.
+    loop {
+        let mut peeled = None;
+        for wrapper in [
+            "Rc<", "Arc<", "Box<", "RefCell<", "Cell<", "Pin<", "NonNull<",
+            "NonZero<", "MaybeUninit<", "ManuallyDrop<", "UnsafeCell<",
+            "Wrapping<", "Reverse<",
+        ] {
+            if let Some(inner) = strip_generic_one(t, wrapper) {
+                peeled = Some(inner.trim());
+                break;
+            }
+        }
+        match peeled {
+            Some(inner) => t = inner,
+            None => break,
+        }
+    }
     strip_generic_one(t, "Option<").is_some() || strip_generic_one(t, "Result<").is_some()
 }

A shared helper that both project_pyre_field_type and this predicate call for the strip-and-unwrap step would prevent the two lists from diverging again.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-translate/src/annotator/bookkeeper.rs` around lines 3775 - 3782,
Update is_nullable_sum_spelling to share the same prefix-stripping and
transparent-wrapper-unwrapping helper used by project_pyre_field_type, including
raw pointers (*const/*mut) and wrappers such as Box, Rc, Arc, and RefCell.
Ensure both paths reach the existing Option/Result detection consistently,
without duplicating divergent stripping logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@majit/majit-translate/src/annotator/bookkeeper.rs`:
- Line 786: Handle the Result returned by getuniqueclassdef_for_enum_variant in
the prologue pre-mint instead of discarding it with let _. Propagate the error,
or record it through the annotator backlink using try_annotator when no
annotator may be attached, while preserving the requirement that all variant
subtree members are materialized before assign_inheritance_ids.

---

Outside diff comments:
In `@majit/majit-translate/src/annotator/bookkeeper.rs`:
- Line 6454: Remove both `eprintln!` debug probes labelled “probe” near the
adjacent `assert!` calls, including the probes after force seeding and before
projection and at the additional referenced location. Leave the assertions and
their failure messages unchanged.
- Around line 3775-3782: Update is_nullable_sum_spelling to share the same
prefix-stripping and transparent-wrapper-unwrapping helper used by
project_pyre_field_type, including raw pointers (*const/*mut) and wrappers such
as Box, Rc, Arc, and RefCell. Ensure both paths reach the existing Option/Result
detection consistently, without duplicating divergent stripping logic.

In `@majit/majit-translate/src/front/mir.rs`:
- Around line 1823-1873: Add a regression test alongside the existing
harden_duplicate_leaf_metadata tests, covering a variant tail such as
typedef::BytesSubArg::Buffer colliding with an unrelated enum base such as
buffer::Buffer. Register both enum bases and assert the shared bare Buffer alias
is preserved, while also verifying an equal-leaf collision that is not an enum
base is still withdrawn. Use the existing test helpers and naming conventions.

In `@pyre/pyre-interpreter/src/builtins.rs`:
- Around line 5413-5449: Update the delegation in the winning-metaclass branch
of the class-construction flow to preserve and forward the original kwargs
alongside positional arguments. Replace the positional-only call to
call_function_impl_result for w_metaclass_new with the established kwargs-aware
invocation path, using the original args/kwargs split so metaclass keywords
reach the delegated __new__.

In `@pyre/pyre-interpreter/src/objspace/std/mapdict.rs`:
- Around line 4612-4627: Root w_dict before the self.delitem call because
box_str_constant and maybe_migrate_to_boxed may move it during the surrounding
mapdict operation. Extend the existing GC root/pinning setup to include w_dict,
then retrieve the current rooted w_dict immediately before invoking
self.delitem, while preserving the existing rooted key handling.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8f1d6818-4252-48c9-8d4f-bf62e9d32d4d

📥 Commits

Reviewing files that changed from the base of the PR and between 16310a7 and 30b16fa.

📒 Files selected for processing (7)
  • majit/majit-translate/src/annotator/bookkeeper.rs
  • majit/majit-translate/src/front/mir.rs
  • pyre/pyre-interpreter/src/builtins.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs

// the pending drain inside the call — makes the variant's
// payload classdefs order-independent, the same contract the
// struct-root loop above relies on.
let _ = self.getuniqueclassdef_for_enum_variant(&root, variant);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not discard the resolver error in the prologue pre-mint.

getuniqueclassdef_for_enum_variant now performs base materialization, variant interning, and payload-row projection. Each step returns Result. let _ = drops any failure. The doc comment above states that this pre-mint must leave every subtree member present and UNNUMBERED before assign_inheritance_ids. A swallowed error leaves the variant classdef absent. The single numbering pass then misses that subtree, and the later lazy mint produces the Skip-classified instantiation this method exists to prevent.

Record or propagate the error so the failure is visible.

♻️ Proposed change
-                let _ = self.getuniqueclassdef_for_enum_variant(&root, variant);
+                if let Err(err) = self.getuniqueclassdef_for_enum_variant(&root, variant) {
+                    self.warning(format!(
+                        "pre_register_enum_variant_classes: {root}::{variant} failed to \
+                         pre-mint before assign_inheritance_ids: {err}"
+                    ));
+                }

warning routes through the annotator backlink; use try_annotator guarding if the prologue can run without an attached annotator.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-translate/src/annotator/bookkeeper.rs` at line 786, Handle the
Result returned by getuniqueclassdef_for_enum_variant in the prologue pre-mint
instead of discarding it with let _. Propagate the error, or record it through
the annotator backlink using try_annotator when no annotator may be attached,
while preserving the requirement that all variant subtree members are
materialized before assign_inheritance_ids.

@youknowone
youknowone merged commit 59dfaf3 into main Aug 8, 2026
14 of 17 checks passed
@youknowone
youknowone deleted the rtyper-legacy branch August 8, 2026 10:50
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