Skip to content

typedef parity: code/function/method/cell/property/class/staticmethod (3.14) - #610

Merged
youknowone merged 15 commits into
mainfrom
rename
Jul 17, 2026
Merged

youknowone merged 15 commits into
mainfrom
rename

Conversation

@youknowone

@youknowone youknowone commented Jul 16, 2026 •

Copy link
Copy Markdown
Collaborator

Completes typedef parity for the callable/descriptor family and the code
object against Python 3.14, and adds parity tests for each.

Type objects

  • code — pycode.rs/typedef.rs: the 3.14 code type layout, members,
    and methods (replace, co_* attributes) with a GC offset coverage test fix.
  • function — function.rs/pyre-object/src/function.rs: function typedef
    members and __dict__/__weakref__ exposure.
  • method — bound-method typedef.
  • cell — cell typedef; visible cells are preserved.
  • property — property typedef and the descriptor protocol.
  • classmethod / staticmethod — descriptor typedefs.
  • range — rich comparison parity.

check.py

Measures each interpreter's empty-program startup time and subtracts it from
the reported perf ratios and gates.

Tests

New parity tests under pyre/extra_tests/parity_tests/
(code_python314.py, function_python314.py, method_python314.py,
property_protocol.py, classmethod_protocol.py, staticmethod_protocol.py,
updated closure_cell_protocol.py, range_python314.py) and synthetic
benchmarks for the property/classmethod protocols.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Improved Python 3.14 compatibility across descriptors, functions, methods, properties, modules, ranges, cells, and mapping proxies.
    • Added support for richer function metadata (annotations, type parameters, built-in linkage) and descriptor dictionaries.
    • WebAssembly backend now performs runtime invalidation checks for relevant guards/bridges.
  • Bug Fixes
    • Refined callable and operand handling to better match CPython descriptor precedence and error behavior.
    • Improved code-object firstlineno preservation and related replace() behavior.
  • Tests
    • Expanded Python 3.14 parity/regression coverage for multiple runtime and descriptor APIs.
    • Added/updated benchmarks for classmethod and property protocol behavior.

@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

https://github.com/youknowone/pyre/blob/ccd23eb93ee51321a7cc86f3cec588e4391a7564/pyre/pyre-interpreter/src/pycode.rs#L970
P2 Badge Validate replaced code argument counts

When co_argcount (and the adjacent positional/kw-only count fields) is overridden, code_replace accepts the new value without re-running the constructor invariants such as posonly <= argcount and argcount + kwonly <= len(co_varnames). A call like f.__code__.replace(co_argcount=f.__code__.co_nlocals + 1) can therefore create an internally inconsistent code object; later function-call error handling indexes code_ref.varnames[i] for each argument slot and can panic instead of raising the ValueError that CodeType/CPython reject at replacement time.


https://github.com/youknowone/pyre/blob/ccd23eb93ee51321a7cc86f3cec588e4391a7564/pyre/pyre-interpreter/src/pycode.rs#L1032
P2 Badge Rebuild locations when replacing line tables

Replacing co_linetable only swaps the raw bytes while leaving code.locations cloned from the original code object. For inputs such as func.__code__.replace(co_linetable=b'') (or a changed first line), the public co_positions(), co_lines(), co_lnotab, and traceback/debug-range data still report the old decoded locations even though co_linetable says otherwise; this should decode the new linetable using the effective first line and instruction length before reboxing.

ℹ️ 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 +684 to +687
&& crate::pyframe::code_constants(a)
.iter()
.zip(crate::pyframe::code_constants(b).iter())
.all(|(left, right)| constant_strong_equal(left, right))

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 Include line metadata in code equality

code_data_equal returns true after comparing constants but never compares linetable or exceptiontable, and code_hash similarly omits them. Python treats code objects that differ only by co_linetable or co_exceptiontable as unequal (and hashes them differently), so code.replace(co_linetable=b'') == code and dictionary/set behavior will be wrong for generated or instrumented code objects.

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds Python 3.14 parity tests and benchmarks, updates descriptor and object-space behavior, removes implicit cell dereferencing, extends function and code-object metadata, and adds runtime invalidation checks to the WASM backend.

Changes

Protocol parity and runtime integration

Layer / File(s) Summary
Protocol benchmarks
pyre/bench/synth/*
Adds classmethod and property runtime exercises with hot loops and descriptor metadata output.
Python 3.14 parity coverage
pyre/extra_tests/parity_tests/*
Adds structural and behavioral checks for descriptors, functions, methods, modules, cells, code objects, ranges, and mapping proxies.
Cell-transparent dispatch removal
pyre/pyre-interpreter/src/baseobjspace.rs, builtins.rs, display.rs, objspace/*, opcode_ops.rs, pyre/pyre-jit*/**
Stops implicit cell dereferencing across object-space, arithmetic, display, iteration, attribute, callable, and default-resolution paths.
Descriptor and object runtime
pyre/pyre-interpreter/src/{call.rs,function.rs,pycode.rs,typedef.rs}, pyre/pyre-object/src/*
Adds Python 3.14-compatible function storage, annotation and type-parameter handling, method dispatch, property behavior, direct members, module descriptors, and code-object fields.
Runtime and WASM wiring
majit/majit-backend-wasm/*, pyre/pyre-interpreter/src/{executioncontext.rs,importing.rs,eval.rs,pyopcode.rs,shared_opcode.rs}
Connects live WASM invalidation flags, builtin module ownership, GC tracing, and function type-parameter assignment.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit bounds through code tonight,
Cells stay whole and guards burn bright.
Descriptors bind, annotations bloom,
WASM flags wake from silent gloom.
“OK!” sings the hare with delight—
Python’s new shapes now fit just right.

🚥 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 accurately summarizes the main 3.14 parity work across code, function, method, cell, property, classmethod, and staticmethod.
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 rename

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 Jul 17, 2026 •

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit fa67ef5).
Updated: 2026-07-17T05:46:46.259Z

Files in the reviewed diff
majit/majit-backend-wasm/src/codegen.rs
majit/majit-backend-wasm/src/lib.rs
majit/majit-backend-wasm/tests/codegen_test.rs
pyre/bench/synth/classmethod_protocol_hot.py
pyre/bench/synth/property_protocol_hot.py
pyre/extra_tests/parity_tests/builtin_function_python314.py
pyre/extra_tests/parity_tests/classmethod_protocol.py
pyre/extra_tests/parity_tests/closure_cell_protocol.py
pyre/extra_tests/parity_tests/code_python314.py
pyre/extra_tests/parity_tests/function_member_descriptors_python314.py
pyre/extra_tests/parity_tests/function_python314.py
pyre/extra_tests/parity_tests/getset_descriptor_python314.py
pyre/extra_tests/parity_tests/mappingproxy_python314.py
pyre/extra_tests/parity_tests/method_python314.py
pyre/extra_tests/parity_tests/module_type_python314.py
pyre/extra_tests/parity_tests/property_protocol.py
pyre/extra_tests/parity_tests/range_python314.py
pyre/extra_tests/parity_tests/staticmethod_protocol.py
pyre/pyre-interpreter/src/baseobjspace.rs
pyre/pyre-interpreter/src/builtins.rs
pyre/pyre-interpreter/src/call.rs
pyre/pyre-interpreter/src/display.rs
pyre/pyre-interpreter/src/eval.rs
pyre/pyre-interpreter/src/executioncontext.rs
pyre/pyre-interpreter/src/function.rs
pyre/pyre-interpreter/src/importing.rs
pyre/pyre-interpreter/src/module/_codecs/mod.rs
pyre/pyre-interpreter/src/module/_weakref/interp__weakref.rs
pyre/pyre-interpreter/src/objspace/descroperation.rs
pyre/pyre-interpreter/src/objspace/std/mapdict.rs
pyre/pyre-interpreter/src/opcode_ops.rs
pyre/pyre-interpreter/src/pycode.rs
pyre/pyre-interpreter/src/pyframe.rs
pyre/pyre-interpreter/src/pyopcode.rs
pyre/pyre-interpreter/src/shared_opcode.rs
pyre/pyre-interpreter/src/typedef.rs
pyre/pyre-jit-trace/src/trace_opcode.rs
pyre/pyre-jit/src/call_jit.rs
pyre/pyre-object/src/descriptor.rs
pyre/pyre-object/src/function.rs
pyre/pyre-object/src/typedef.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • baseobjspace.rs:3260 ↔ function.py:783: getdict/setdict now require the exact FUNCTION_TYPE, excluding BuiltinFunction. PyPy’s BuiltinFunction inherits Function’s __dict__ descriptor and copies w_func_dict; pyre therefore fails to expose or replace a builtin function’s inherited dictionary.

  • baseobjspace.rs:10665 ↔ descriptor.py:256: property_copy treats an explicitly supplied Python None as an omitted accessor and retains the old accessor. PyPy distinguishes omitted RPython None from wrapped space.w_None; p.getter(None) must construct a property whose getter is None.

  • pycode.rs:897 ↔ pyopcode.py:1256: code_branches always computes target = next + delta. Conditional backward jumps therefore report a forward target; the interpreter’s corresponding backward-jump handling subtracts its delta.

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

None.

4. Structural adaptations

  • typedef.rs:9121 ↔ typedef.py:770: CPython 3.14 exposes several function fields as member_descriptors, whereas PyPy exposes the comparable fields through GetSetProperty. This is a stated Python-version surface adaptation.

  • pycode.rs:623 ↔ pycode.py:698: pyre reconstructs CPython compiler localspluskinds from RustPython’s CodeObject; PyPy computes cell-variable placement from its own bytecode/code-object representation. This is a compiler/implementation-language adaptation.

@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: 5299f6ec70

ℹ️ 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".

let stacksize = stacksize_value as u32;
let flags_bits = flags_value as u32;
let instructions = unsafe { read_code_units(args[7])? };
let constants = unsafe { read_code_consts(args[8])? };

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 Preserve supported constants in CodeType constructor

When the new code.__new__ path reconstructs a code object from an existing co_consts tuple, read_code_consts rejects Python objects for constants that pyre already emits and reads back, such as frozenset and complex (pyobject_from_constant handles both). As a result, cloning ordinary code with a set-membership or complex literal via type(code)(..., code.co_consts, ...) raises ValueError instead of producing an equivalent code object.

Useful? React with 👍 / 👎.

Comment on lines +833 to +837
index -= code.varnames.len() as i64;
if let Some(name) = code.cellvars.get(index as usize) {
return Ok(w_str_new(name));
}
index -= code.cellvars.len() as i64;

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 Skip cellvar aliases in _varname_from_oparg

For functions where a cell variable is also a local or argument, CPython's localsplus table marks that existing local slot as a cell and only appends pure cellvars after co_varnames. This implementation indexes through every co_cellvars entry after the locals, so e.g. an outer function whose argument is captured reports that alias at an out-of-range oparg and shifts any following freevars; _varname_from_oparg should follow localsplus/pure-cell ordering rather than co_varnames + co_cellvars + co_freevars.

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: 7

🤖 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 `@pyre/check.py`:
- Around line 925-948: Preserve unrounded baseline timing values through the
performance-gate comparisons in the relevant retry and ratio-check flows,
including the logic around _retry_performance_gate and the affected baseline
assignments. Move two-decimal formatting to the reporting/display boundary only,
so _exec_time receives raw t_cpython/t_pypy floats and the 5 ms startup floor is
not distorted.

In `@pyre/pyre-interpreter/src/baseobjspace.rs`:
- Around line 10906-10910: Validate that the `__delete__` call receives a second
argument before accessing it in the surrounding property deletion
implementation. Update the `__delete__` registration to require arity 2, or
safely retrieve the object via `args.get(1)` and return the expected TypeError
when absent; preserve the existing deleter validation for valid calls.

In `@pyre/pyre-interpreter/src/function.rs`:
- Around line 2059-2149: The layout-specific builtins read method, range, or
function fields without validating their receivers. In
pyre/pyre-interpreter/src/function.rs:2059-2149, add method-receiver checks
before every w_method_get_* access in descr_method_eq, descr_method_ne,
descr_method_repr, descr_method_getattribute, descr_method_hash, and
descr_method__reduce__; in pyre/pyre-interpreter/src/typedef.rs:2364-2383,
validate args[0] is a range before w_range_eq; and in
pyre/pyre-interpreter/src/typedef.rs:8553-8560, validate args[0] is a function
before function_get_qualname, returning the established type error for invalid
receivers.

In `@pyre/pyre-interpreter/src/pycode.rs`:
- Around line 823-840: Update code_varname_from_oparg so the cellvar lookup uses
the non-overlapping cellvar count represented by npure_cellvars, or filters
cellvars against code.varnames before indexing. Preserve the existing
varname-first resolution while ensuring captured parameters are not counted
twice and subsequent cellvar/freevar slots resolve correctly.
- Line 1044: Update the replacement-code flow before
box_code_constant_with_firstlineno so code.locations is recomputed from the
updated co_linetable and instruction count after cloning or modifying the code
object. Ensure code.positions(), code.lines(), and co_lnotab observe the rebuilt
locations while preserving the existing co_firstlineno_raw update.
- Around line 891-913: Update the conditional-branch handling in co_branches()
to derive target from the decoded jump target rather than converting
read_arg(index) directly. Reuse the existing jump-target decoder from
pyopcode.rs, while preserving the current fallthrough and branch-row
construction.

In `@pyre/pyre-interpreter/src/typedef.rs`:
- Around line 10003-10011: Update staticmethod_descr_repr and the corresponding
classmethod descriptor repr path to use the Python 3.14 function repr
implementation instead of display::py_repr for non-null functions. Ensure exact
function descriptors include the function name and address-style suffix, while
preserving the existing <NULL> handling and outer descriptor formatting.
🪄 Autofix (Beta)

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

Run ID: 0f708640-fb59-48dd-97e8-71d2cb01288f

📥 Commits

Reviewing files that changed from the base of the PR and between 5da9cc6 and 5299f6e.

📒 Files selected for processing (31)
  • pyre/bench/synth/classmethod_protocol_hot.py
  • pyre/bench/synth/property_protocol_hot.py
  • pyre/check.py
  • pyre/extra_tests/parity_tests/classmethod_protocol.py
  • pyre/extra_tests/parity_tests/closure_cell_protocol.py
  • pyre/extra_tests/parity_tests/code_python314.py
  • pyre/extra_tests/parity_tests/function_python314.py
  • pyre/extra_tests/parity_tests/mappingproxy_python314.py
  • pyre/extra_tests/parity_tests/method_python314.py
  • pyre/extra_tests/parity_tests/property_protocol.py
  • pyre/extra_tests/parity_tests/range_python314.py
  • pyre/extra_tests/parity_tests/staticmethod_protocol.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/builtins.rs
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/display.rs
  • pyre/pyre-interpreter/src/eval.rs
  • pyre/pyre-interpreter/src/function.rs
  • pyre/pyre-interpreter/src/module/_codecs/mod.rs
  • pyre/pyre-interpreter/src/module/_weakref/interp__weakref.rs
  • pyre/pyre-interpreter/src/objspace/descroperation.rs
  • pyre/pyre-interpreter/src/opcode_ops.rs
  • pyre/pyre-interpreter/src/pycode.rs
  • pyre/pyre-interpreter/src/pyframe.rs
  • pyre/pyre-interpreter/src/pyopcode.rs
  • pyre/pyre-interpreter/src/shared_opcode.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-jit-trace/src/trace_opcode.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-object/src/descriptor.rs
  • pyre/pyre-object/src/function.rs
💤 Files with no reviewable changes (2)
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-interpreter/src/opcode_ops.rs

Comment thread pyre/check.py
Comment on lines +925 to +948
baseline_cmd, expected_output, baseline_key,
):
"""Check one performance ratio, retrying a failure by median."""
if elapsed <= baseline_time * limit + BENCH_COMPARE_BUFFER_S:
"""Check one performance ratio, retrying a failure by median.

The pass/fail decision compares execution-only (startup-subtracted)
times; the returned elapsed/baseline stay raw for reporting.
"""
if (
self._exec_time(backend, elapsed)
<= self._exec_time(baseline_key, baseline_time) * limit
+ BENCH_COMPARE_BUFFER_S
):
return True, elapsed, baseline_time, ""

retry = self._retry_performance_gate(
backend, script, timeout, baseline_cmd, expected_output,
)
if retry is not None:
median_elapsed, median_baseline = retry
if median_elapsed <= median_baseline * limit + BENCH_COMPARE_BUFFER_S:
if (
self._exec_time(backend, median_elapsed)
<= self._exec_time(baseline_key, median_baseline) * limit
+ BENCH_COMPARE_BUFFER_S
):

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

Preserve raw baseline precision before subtracting startup.

These paths subtract startup from t_cpython/t_pypy, but those values were already rounded to two decimals at Lines 1106 and 1115. For short benchmarks, that rounding can move the adjusted baseline to the 5 ms floor or double it, producing incorrect ratios and gate results. Keep raw baseline floats through comparison and format only for display.

Also applies to: 1004-1055

🤖 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/check.py` around lines 925 - 948, Preserve unrounded baseline timing
values through the performance-gate comparisons in the relevant retry and
ratio-check flows, including the logic around _retry_performance_gate and the
affected baseline assignments. Move two-decimal formatting to the
reporting/display boundary only, so _exec_time receives raw t_cpython/t_pypy
floats and the 5 ms startup floor is not distorted.

Comment on lines +10906 to +10910
let prop = property_require_obj(args.first().copied().unwrap_or(PY_NULL), "__delete__")?;
let obj = args[1];
let fdel = w_property_get_fdel(prop);
if fdel.is_null() || is_none(fdel) {
return Err(property_no_accessor(prop, obj, "deleter"));
return Err(property_no_accessor(prop, obj, "deleter")?);

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 | 🔴 Critical | ⚡ Quick win

Validate the __delete__ target before indexing.

__delete__ is registered without fixed arity, so property.__delete__(prop) reaches args[1] and panics instead of raising TypeError. Register arity 2 or use args.get(1).

🤖 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/baseobjspace.rs` around lines 10906 - 10910,
Validate that the `__delete__` call receives a second argument before accessing
it in the surrounding property deletion implementation. Update the `__delete__`
registration to require arity 2, or safely retrieve the object via `args.get(1)`
and return the expected TypeError when absent; preserve the existing deleter
validation for valid calls.

Comment thread pyre/pyre-interpreter/src/function.rs Resolved
Comment on lines +823 to +840
pub unsafe fn code_varname_from_oparg(
obj: PyObjectRef,
index: PyObjectRef,
) -> Result<PyObjectRef, crate::PyError> {
let code = unsafe { require_code(obj, "_varname_from_oparg")? };
let mut index = unsafe { crate::builtins::space_index_w(index)? };
if index >= 0 {
if let Some(name) = code.varnames.get(index as usize) {
return Ok(w_str_new(name));
}
index -= code.varnames.len() as i64;
if let Some(name) = code.cellvars.get(index as usize) {
return Ok(w_str_new(name));
}
index -= code.cellvars.len() as i64;
if let Some(name) = code.freevars.get(index as usize) {
return Ok(w_str_new(name));
}

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section and nearby helpers.
sed -n '760,900p' pyre/pyre-interpreter/src/pycode.rs

echo
echo '--- search for locals-plus construction and varname lookup ---'
rg -n "varnames|cellvars|freevars|localsplus|locals_plus|co_varnames|co_cellvars|_varname_from_oparg" pyre/pyre-interpreter/src -S

echo
echo '--- inspect any code that builds these vectors ---'
rg -n "cellvars.*varnames|varnames.*cellvars|localsplus" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 33332


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- pyframe slot-layout helpers ---'
sed -n '1350,1455p' pyre/pyre-interpreter/src/pyframe.rs

echo
echo '--- code object construction around localspluskinds ---'
sed -n '610,665p' pyre/pyre-interpreter/src/pycode.rs

echo
echo '--- _varname_from_oparg registration / docs ---'
sed -n '9290,9325p' pyre/pyre-interpreter/src/typedef.rs

echo
echo '--- direct callers of _varname_from_oparg or related lookup ---'
rg -n "_varname_from_oparg|code_varname_from_oparg|varname_from_oparg" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 7769


🌐 Web query:

CPython _varname_from_oparg co_cellvars co_varnames semantics

💡 Result:

In CPython, the resolution of bytecode arguments to variable names is managed through specific structures within code objects, most notably via the interaction of co_varnames, co_cellvars, and co_freevars [1][2][3]. _varname_from_oparg The symbol _varname_from_oparg is not a core CPython VM instruction or internal C structure, but rather a helper function found in the Python standard library's dis (disassembler) module [4][5]. It serves as a callback or utility during disassembly to map a numeric opcode argument (oparg) to its corresponding human-readable variable name [4][5]. co_varnames and co_cellvars Semantics These attributes of a code object define how the Python virtual machine manages local and non-local variable storage [6][7]: 1. co_varnames: A tuple containing the names of local variables, including function arguments [1][3][8]. These are accessed by "FAST" instructions (e.g., LOAD_FAST, STORE_FAST) which use the oparg as an index into an array of local variables [6][7][9]. 2. co_cellvars: A tuple containing the names of local variables that are captured and referenced by nested functions (closures) [1][3][8]. 3. co_freevars: A tuple containing the names of variables referenced in the current scope that were defined in an enclosing scope [3][6][8]. Internal Mapping: localsplus Modern CPython (since version 3.11) optimizes this by consolidating these into a single contiguous array, often referred to internally as localsplus [2][6]. The code object maintains co_localsplusnames, a tuple that combines the names from co_varnames, co_cellvars, and co_freevars [2][6]. When the VM executes bytecode, the oparg for "FAST" and "DEREF" instructions acts as an index into this unified localsplus array to efficiently access both local variables and closure cells [6][10]. The dis module uses the logic encapsulated by _varname_from_oparg to reconstruct this mapping during analysis by referring to these underlying code object attributes [4][5].

Citations:


🌐 Web query:

site:github.com python _varname_from_oparg code object co_cellvars co_varnames

💡 Result:

In Python's CPython implementation, co_varnames and co_cellvars are attributes of a code object (PyCodeObject) that map variable indices used in bytecode instructions to their corresponding names [1][2]. co_varnames This attribute is a tuple containing the names of local variables and arguments [2]. Bytecode instructions like LOAD_FAST, STORE_FAST, and DELETE_FAST use an operand (oparg) as an index into this tuple to access the specified local variable [3]. co_cellvars This attribute is a tuple containing the names of local variables that are referenced by nested functions (i.e., variables captured by closures) [4][2]. When a variable is accessed via a closure, the LOAD_CLOSURE instruction uses an index that spans both co_cellvars and co_freevars [3]. Relationship to Opargs and Internal Mapping While co_varnames, co_cellvars, and co_freevars remain part of the public Python API, recent CPython versions (starting from 3.11) have optimized how the virtual machine handles these variables internally [4][5][6]. The interpreter now uses a unified "locals plus" storage mechanism [7][6]. Internally, the code object tracks these variables using combined structures (such as co_localsplusnames and co_localspluskinds), which map the "locals plus" array—where locals, cells, and free variables are stored together—to their respective names and kinds [1][7][6]. Accessing these fields in C code should be done using the official C API functions PyCode_GetVarnames and PyCode_GetCellvars, as direct access to the underlying fields in the PyCodeObject struct is no longer supported or recommended [1][4][5].

Citations:


Skip overlapping cellvars in _varname_from_oparg.
Use npure_cellvars here, or filter co_cellvars against co_varnames; otherwise captured parameters are counted twice and later cell/freevar names resolve to the wrong 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/pycode.rs` around lines 823 - 840, Update
code_varname_from_oparg so the cellvar lookup uses the non-overlapping cellvar
count represented by npure_cellvars, or filters cellvars against code.varnames
before indexing. Preserve the existing varname-first resolution while ensuring
captured parameters are not counted twice and subsequent cellvar/freevar slots
resolve correctly.

Comment on lines +891 to +913
while index < code.instructions.len() {
let op = code.instructions.read_op(index).deoptimize();
let next = index + 1 + op.cache_entries();
if op.has_jump() && !op.is_unconditional_jump() {
let delta = u8::from(code.instructions.read_arg(index)) as usize;
let target = next.saturating_add(delta);
// Python 3.14 inserts NOT_TAKEN at the fallthrough edge so branch
// instrumentation can distinguish the untaken path. `co_branches`
// reports the first real instruction after that marker.
let fallthrough = if next < code.instructions.len()
&& matches!(
code.instructions.read_op(next).deoptimize(),
crate::bytecode::Instruction::NotTaken
) {
next + 1
} else {
next
};
rows.push(w_tuple_new(vec![
w_int_new((index * 2) as i64),
w_int_new((fallthrough * 2) as i64),
w_int_new((target * 2) as i64),
]));

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant symbols in pycode.rs first.
ast-grep outline pyre/pyre-interpreter/src/pycode.rs --view expanded > /tmp/pycode_outline.txt
sed -n '1,220p' /tmp/pycode_outline.txt

# Inspect the relevant section around the comment.
nl -ba pyre/pyre-interpreter/src/pycode.rs | sed -n '860,940p'

# Search for bytecode jump decoding helpers and EXTENDED_ARG handling.
rg -n "EXTENDED_ARG|extended arg|jump.*target|target.*jump|deoptimize\(\)|cache_entries\(\)|read_arg\(" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 6486


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect bytecode instruction definitions that may already handle jump decoding.
fd -a ".*(bytecode|instruction).*" pyre/pyre-interpreter/src
rg -n "enum Instruction|struct Instruction|fn .*target|jump" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 15511


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# If the repository has tests or examples around co_branches, locate them.
rg -n "co_branches|co_lines|co_positions|NotTaken|jump target" pyre -S

Repository: youknowone/pyre

Length of output: 5652


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the branch generator and surrounding helper functions.
sed -n '887,920p' pyre/pyre-interpreter/src/pycode.rs

echo '--- pyopcode jump helpers ---'
sed -n '1598,1655p' pyre/pyre-interpreter/src/pyopcode.rs

echo '--- raw jump target helpers ---'
sed -n '3518,3545p' pyre/pyre-interpreter/src/pyopcode.rs

echo '--- instruction/argument readers ---'
rg -n "fn read_arg|fn read_op|struct CodeUnits|impl .*CodeUnits|cache_entries\(\)" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 4641


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the bytecode instruction types and code-unit accessors.
sed -n '1,260p' pyre/pyre-interpreter/src/bytecode.rs

echo '--- codeunits accessors ---'
sed -n '1,220p' pyre/pyre-interpreter/src/bytecode.rs | cat

Repository: youknowone/pyre

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focus on the exact branch generator and the helper implementations it relies on.
sed -n '887,918p' pyre/pyre-interpreter/src/pycode.rs
echo '---'
sed -n '1608,1650p' pyre/pyre-interpreter/src/pyopcode.rs
echo '---'
sed -n '3520,3536p' pyre/pyre-interpreter/src/pyopcode.rs
echo '---'
rg -n "struct CodeUnits|impl CodeUnits|read_op\(|read_arg\(" pyre/pyre-interpreter/src/bytecode.rs pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 3981


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the bytecode representation to understand whether read_arg already includes EXTENDED_ARG.
rg -n "read_arg|read_op|EXTENDED_ARG|cache_entries" pyre/pyre-interpreter/src/bytecode.rs pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 1536


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the bytecode accessors and the tests/parity expectations around co_branches.
sed -n '1,260p' pyre/pyre-interpreter/src/bytecode.rs

echo '--- pycode tests around co_branches ---'
sed -n '1740,1845p' pyre/pyre-interpreter/src/pycode.rs

echo '--- parity test references ---'
sed -n '1,170p' pyre/extra_tests/parity_tests/code_python314.py

Repository: youknowone/pyre

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find jump-classification helpers on Instruction.
rg -n "is_backward_jump|is_forward_jump|is_unconditional_jump|has_jump|has_arg" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 612


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether this opcode set actually has conditional backward jump variants.
rg -n "BackwardIf|BACKWARD_IF|POP_JUMP_BACKWARD|JUMP_BACKWARD_IF|is_backward_jump" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the bytecode source file and inspect read_arg/read_op semantics.
fd -a "bytecode.rs|mod.rs" pyre/pyre-interpreter/src

echo '--- read_arg/read_op definitions ---'
rg -n "fn read_arg|fn read_op|struct CodeUnits|impl .*CodeUnits|EXTENDED_ARG|cache_entries" pyre/pyre-interpreter/src -S

echo '--- co_branches tests/parity expectations ---'
sed -n '1740,1845p' pyre/pyre-interpreter/src/pycode.rs

echo '--- relevant pyframe/eval comments about EXTENDED_ARG ---'
sed -n '1176,1210p' pyre/pyre-interpreter/src/pyframe.rs
sed -n '1358,1370p' pyre/pyre-interpreter/src/eval.rs

Repository: youknowone/pyre

Length of output: 11268


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the actual bytecode source file and inspect the relevant APIs.
fd -a "bytecode.rs" pyre/pyre-interpreter/src
fd -a "mod.rs" pyre/pyre-interpreter/src | rg "/bytecode/|/bytecode\.rs$|/bytecode/"

# Search for read_arg/read_op and instruction decoding in the source tree.
rg -n "read_arg\(|read_op\(|decode_instruction_at|EXTENDED_ARG|ExtendedArg" pyre/pyre-interpreter/src -S

# Inspect the code_branches neighborhood again with line numbers.
nl -ba pyre/pyre-interpreter/src/pycode.rs | sed -n '887,910p'

Repository: youknowone/pyre

Length of output: 153


Use the decoded jump target here. read_arg(index) only sees the raw oparg byte, so co_branches() can misreport targets for branches that need EXTENDED_ARG. Reuse the bytecode jump-target decoder already used in pyopcode.rs.

🤖 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/pycode.rs` around lines 891 - 913, Update the
conditional-branch handling in co_branches() to derive target from the decoded
jump target rather than converting read_arg(index) directly. Reuse the existing
jump-target decoder from pyopcode.rs, while preserving the current fallthrough
and branch-row construction.

}

Ok(box_code_constant(&code))
Ok(box_code_constant_with_firstlineno(&code, firstlineno_raw))

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file around the referenced line and nearby helpers.
FILE=pyre/pyre-interpreter/src/pycode.rs
wc -l "$FILE"
sed -n '980,1085p' "$FILE"

# Find the helper definition and any uses of locations/linetable-related fields.
rg -n "box_code_constant_with_firstlineno|locations|linetable|co_positions|co_lines|co_lnotab|firstlineno_raw" "$FILE" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 13309


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=pyre/pyre-interpreter/src/pycode.rs

echo "=== file size ==="
wc -l "$FILE"

echo "=== relevant region ==="
sed -n '1000,1085p' "$FILE"

echo "=== symbol search ==="
rg -n "box_code_constant_with_firstlineno|locations|linetable|co_positions|co_lines|co_lnotab|firstlineno_raw" "$FILE" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 12588


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Narrow inspection of the helper and related code-object methods.
FILE=pyre/pyre-interpreter/src/pycode.rs

sed -n '1,220p' "$FILE"
echo
sed -n '220,420p' "$FILE"

Repository: youknowone/pyre

Length of output: 17758


🌐 Web query:

RustPython code.locations co_positions co_lines lnotab linetable_to_locations

💡 Result:

In RustPython, code object location information is primarily managed through the co_linetable attribute, which follows the CPython 3.11+ compact location format [1][2]. This format stores source position data (line, end line, column, and end column) for bytecode instructions [1][3][2]. Key attributes and methods in RustPython include: co_linetable: A bytes object containing the compact representation of source locations [1][4][5]. It is not to be confused with the deprecated co_lnotab [1][2]. In RustPython, co_lnotab is intentionally not implemented as it was deprecated in CPython 3.12 and removed [1]. co_positions: A method on the code object that returns an iterator yielding tuples of (line, end_line, column, end_column) for each instruction [1][4]. It processes the co_linetable internally, often using a reader to interpret the compact binary format [1][6]. co_lines: A method that returns an iterator over bytecode ranges, yielding (start, end, line) tuples [4][2]. This is more compact than co_positions but does not provide column or end-line information [2]. linetable_to_locations: While not a singular public function by this exact name in the public API, RustPython's internal logic (such as in crates/vm/src/builtins/code.rs) handles the conversion from the raw co_linetable bytes to usable location information [1][6]. This involves reading the header of each location entry to determine the instruction coverage and the location kind (short form, one-line form, no columns, or long form) [1][3]. The compiler is responsible for generating this table during bytecode assembly [7]. RustPython's implementation mirrors the CPython specification for location tables, ensuring that source-to-bytecode mapping is accurate for debugging and introspection [3][2][8]. Tools interacting with RustPython code objects should prefer these modern methods (co_positions, co_lines) over relying on legacy structures like co_lnotab [9][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=pyre/pyre-interpreter/src/pycode.rs

echo "=== helper ==="
sed -n '420,445p' "$FILE"

echo "=== constructor path around locations ==="
sed -n '620,670p' "$FILE"

echo "=== CodeObject definition / locations field ==="
rg -n "struct CodeObject|pub locations|locations:" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 2709


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=pyre/pyre-interpreter/src/pycode.rs

echo "=== co_lnotab / legacy_lnotab region ==="
sed -n '500,565p' "$FILE"

echo "=== co_positions / co_lines region ==="
sed -n '840,885p' "$FILE"

echo "=== code_data_equal / replace-adjacent region ==="
sed -n '700,790p' "$FILE"

Repository: youknowone/pyre

Length of output: 7911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where crate::CodeObject is defined and inspect the fields.
rg -n "struct CodeObject" pyre/pyre-interpreter/src -S
rg -n "locations:|linetable:|instructions:" pyre/pyre-interpreter/src -S

Repository: youknowone/pyre

Length of output: 153


Rebuild decoded locations before boxing the replacement
code.positions(), code.lines(), and co_lnotab read code.locations, so cloning the code object and only updating co_firstlineno_raw leaves those views stale after co_firstlineno, co_linetable, or co_code changes. Recompute locations from the updated linetable and instruction count before boxing.

🤖 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/pycode.rs` at line 1044, Update the
replacement-code flow before box_code_constant_with_firstlineno so
code.locations is recomputed from the updated co_linetable and instruction count
after cloning or modifying the code object. Ensure code.positions(),
code.lines(), and co_lnotab observe the rebuilt locations while preserving the
existing co_firstlineno_raw update.

Comment thread pyre/pyre-interpreter/src/typedef.rs Resolved

@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: 38966bbc65

ℹ️ 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".

w_kw_defs: PY_NULL,
w_module: PY_NULL,
w_func_globals_obj,
w_builtins,

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 Reload pinned builtins before storing the function

When constructing a function whose globals require builtin resolution, pick_builtin_obj and the later stable allocation can run GC while w_builtins is only protected through the shadow root pushed above. This store still uses the pre-GC local rather than re-reading the pinned root, so a forwarded func_builtins object can leave func.__builtins__ pointing at the old address; mirror the allocation patterns that re-read shadow-stack roots after try_gc_alloc_stable_raw before writing the Function payload.

Useful? React with 👍 / 👎.

@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: f5577c43a2

ℹ️ 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 +10541 to +10542
if w_klass.is_null() || unsafe { pyre_object::is_none(w_klass) } {
w_klass = r#type(w_obj).unwrap_or(PY_NULL);

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 Reject classmethod.get(None, None)

When classmethod.__get__ is called directly with obj as Python None and type omitted or also None, CPython raises TypeError: __get__(None, None) is invalid; this branch instead infers type(None) and returns a method bound to NoneType, so classmethod(f).__get__(None, None)() passes the wrong class rather than rejecting the invalid descriptor call. Please explicitly reject the obj is None + missing/None type case before falling back to r#type(w_obj).

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

🤖 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 `@pyre/pyre-interpreter/src/typedef.rs`:
- Around line 9889-9924: Update the __repr__ and __reduce__ callbacks in the
MemberDescriptorType definition to validate their receiver with the existing
is_member check before calling member_descriptor_repr, w_member_get_cls, or
w_member_get_name. Preserve the established invalid-receiver behavior used by
__name__, __objclass__, and __qualname__, and only perform unsafe member field
access after validation.
🪄 Autofix (Beta)

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

Run ID: b041120e-ebca-4227-8d79-3cb4930ac1c1

📥 Commits

Reviewing files that changed from the base of the PR and between 5299f6e and f5577c4.

📒 Files selected for processing (9)
  • pyre/extra_tests/parity_tests/function_member_descriptors_python314.py
  • pyre/extra_tests/parity_tests/getset_descriptor_python314.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/display.rs
  • pyre/pyre-interpreter/src/eval.rs
  • pyre/pyre-interpreter/src/function.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-object/src/typedef.rs

Comment on lines +9889 to +9924
unsafe {
pyre_object::dictmultiobject::w_dict_setitem_str_no_proxy(
ns,
"__repr__",
make_builtin_function_with_arity(
"__repr__",
|args| {
let member = args.first().copied().unwrap_or(pyre_object::PY_NULL);
Ok(pyre_object::w_str_new(&unsafe {
member_descriptor_repr(member)
}))
},
1,
),
)
};

unsafe {
pyre_object::dictmultiobject::w_dict_setitem_str_no_proxy(
ns,
"__reduce__",
make_builtin_function_with_arity(
"__reduce__",
|args| {
let member = args.first().copied().unwrap_or(pyre_object::PY_NULL);
let owner = unsafe { pyre_object::w_member_get_cls(member) };
let name =
pyre_object::w_str_new(unsafe { pyre_object::w_member_get_name(member) });
Ok(pyre_object::w_tuple_new(vec![
crate::baseobjspace::builtin_callable("getattr"),
pyre_object::w_tuple_new(vec![owner, name]),
]))
},
1,
),
)

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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="$(fd -a '^typedef\.rs$' pyre/pyre-interpreter/src | head -n1)"
ast-grep outline "$file" --items all \
  --match 'init_member_descriptor_type|member_descriptor_repr'
sed -n '9889,9924p' "$file"

rg -n -C3 \
  'MemberDescriptorType\.__(repr|reduce)__\s*\(' \
  pyre/extra_tests/parity_tests

Repository: youknowone/pyre

Length of output: 1664


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="pyre/pyre-interpreter/src/typedef.rs"

# Inspect the member-descriptor setup and helper implementations.
sed -n '9627,9985p' "$file"

# Find the builtin-callable/arity wrapper to see whether it binds or validates receivers.
rg -n -C4 'make_builtin_function_with_arity|builtin_callable\("getattr"\)|w_member_get_(cls|name)|member_descriptor_repr' pyre/pyre-interpreter/src

Repository: youknowone/pyre

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the builtin wrapper implementation and the member helpers.
rg -n -C4 'fn make_builtin_function_with_arity|make_builtin_function_with_arity\(|pub\(crate\) unsafe fn member_descriptor_repr|w_member_get_(cls|name)|is_member\(' pyre/pyre-interpreter/src

# Read only the relevant slices with line numbers.
sed -n '1,220p' pyre/pyre-interpreter/src/typedef.rs

Repository: youknowone/pyre

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the builtin-function wrapper implementation.
rg -n -C6 'fn make_builtin_function_with_arity|struct .*BuiltinFunction|impl .*BuiltinFunction|BuiltinFunction' pyre/pyre-interpreter/src

# Read the member-descriptor section only.
sed -n '9786,9945p' pyre/pyre-interpreter/src/typedef.rs

Repository: youknowone/pyre

Length of output: 47485


Guard MemberDescriptorType.__repr__ and __reduce__ against non-member receivers
types.MemberDescriptorType.__repr__(1) and .__reduce__(1) can reach w_member_get_cls / w_member_get_name with an arbitrary object. Add the same is_member check used by __name__, __objclass__, and __qualname__ before any unsafe field access.

🤖 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/typedef.rs` around lines 9889 - 9924, Update the
__repr__ and __reduce__ callbacks in the MemberDescriptorType definition to
validate their receiver with the existing is_member check before calling
member_descriptor_repr, w_member_get_cls, or w_member_get_name. Preserve the
established invalid-receiver behavior used by __name__, __objclass__, and
__qualname__, and only perform unsafe member field access after validation.

@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

https://github.com/youknowone/pyre/blob/90b20005e6095344d0389f87967327b28201a12b/pyre/pyre-interpreter/src/pycode.rs#L1033-L1034
P2 Badge Rebuild decoded locations after code.replace

When code.replace(co_linetable=...), co_firstlineno, or a different-length co_code is used, this only updates the raw bytes/instructions and leaves code.locations cloned from the original object. The new object's co_positions(), co_lines(), and derived co_lnotab therefore continue to report the old source positions (and can have the wrong number of rows when the bytecode length changes) instead of reflecting the replacement, so generated/instrumented code objects expose stale line metadata.


https://github.com/youknowone/pyre/blob/90b20005e6095344d0389f87967327b28201a12b/pyre/pyre-interpreter/src/pycode.rs#L1024-L1025
P2 Badge Validate varname/count invariants in code.replace

If co_varnames or an argument-count field is overridden, code_replace does not re-run the constructor checks (len(co_varnames) == co_nlocals and argcount + kwonlyargcount <= nlocals) and even accepts co_nlocals while ignoring it. For example, f.__code__.replace(co_varnames=()) can now create a code object with co_argcount == 1 and no local names; later calls and diagnostics index code.varnames[0], so pyre can panic or mis-execute instead of raising ValueError at replacement time.

ℹ️ 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".

@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: 40d5328deb

ℹ️ 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".

obj: PyObjectRef,
name: PyObjectRef,
) -> Result<PyObjectRef, crate::PyError> {
let Some(name) = (unsafe { pyre_object::w_str_get_value_opt(name) }) else {

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 Validate method attribute names before decoding

When a bound method's __getattribute__ is invoked directly with a non-string name, this calls w_str_get_value_opt before checking the object's type; that helper assumes the pointer is a W_UnicodeObject and dereferences it as one. Inputs such as (1).__add__.__getattribute__(123) can therefore crash/read invalid memory instead of raising TypeError; guard with is_str/text_w before extracting the string contents.

Useful? React with 👍 / 👎.

// CPython 3.14 `function.__type_params__`: tuple-only and not deletable.
let typeparams_getter = make_builtin_function("__type_params__", |args| {
let function = args.get(1).copied().unwrap_or(pyre_object::PY_NULL);
Ok(unsafe { crate::function::function_get_typeparams(function) })

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 Add a function receiver guard to the new getset

When the new __type_params__ descriptor is called directly with a non-function receiver, make_getset_property has no required-class guard, so this forwards arbitrary args[1] into function_get_typeparams, which blindly casts it to Function. For example, type(f).__dict__['__type_params__'].__get__(42, int) can read invalid fields or crash instead of raising the usual descriptor TypeError; use a class-checked getset or validate is_function before the cast.

Useful? React with 👍 / 👎.

let op = code.instructions.read_op(index).deoptimize();
let next = index + 1 + op.cache_entries();
if op.has_jump() && !op.is_unconditional_jump() {
let delta = u8::from(code.instructions.read_arg(index)) as usize;

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 Decode EXTENDED_ARGs when reporting branches

For conditional jumps whose offset needs an EXTENDED_ARG prefix, read_arg(index) only returns this instruction's low byte, while the interpreter executes the fully decoded oparg via the normal decode path. Large if/loop bodies with branch deltas above 255 will therefore make co_branches() report a target near the fallthrough instead of the real branch target, breaking coverage/introspection for those code objects.

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: 8

Caution

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

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

980-1032: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Rebuild locals-plus metadata in code.replace

code.replace mutates varnames / cellvars / freevars on a cloned CodeObject, but it never recomputes localspluskinds or re-runs the code_new arg-count checks. That can return a code object whose frame layout no longer matches its metadata.

🤖 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/pycode.rs` around lines 980 - 1032, Update the
code.replace construction flow around the varnames, cellvars, and freevars
assignments to rebuild locals-plus metadata after these fields change. Reuse the
same locals-plus derivation and code_new argument-count validation so the cloned
CodeObject’s frame layout and metadata remain consistent, and propagate any
validation error instead of returning the invalid object.
pyre/pyre-jit/src/call_jit.rs (1)

1468-1478: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Stale frame pointer in fail_values leads to use-after-free.

The current assignment bypasses applying ca_resume_frame whenever ca_resume_deadframe is Some. If the GC moved the frame (setting ca_resume_frame != 0), fail_values[0] retains the stale frame pointer from the deadframe snapshot. This leads to a use-after-free when actual_green_key recovery or the blackhole fallback reads it.

Restructure this to mirror the raw_deadframe logic, applying ca_resume_frame uniformly regardless of where the baseline array originated.

🐛 Proposed fix
-    let fail_values_raw = unsafe { std::slice::from_raw_parts(fail_values_ptr, num_fail_values) };
-    let mut fail_values_owned;
-    let fail_values = if let Some(values) = ca_resume_deadframe.as_deref() {
-        values
-    } else if ca_resume_frame != 0 {
-        fail_values_owned = fail_values_raw.to_vec();
-        fail_values_owned[0] = ca_resume_frame as i64;
-        fail_values_owned.as_slice()
-    } else {
-        fail_values_raw
-    };
+    let fail_values_raw = if let Some(values) = ca_resume_deadframe.as_deref() {
+        values
+    } else {
+        unsafe { std::slice::from_raw_parts(fail_values_ptr, num_fail_values) }
+    };
+    let mut fail_values_owned;
+    let fail_values = if ca_resume_frame != 0 && !fail_values_raw.is_empty() {
+        fail_values_owned = fail_values_raw.to_vec();
+        fail_values_owned[0] = ca_resume_frame as i64;
+        fail_values_owned.as_slice()
+    } else {
+        fail_values_raw
+    };
🤖 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-jit/src/call_jit.rs` around lines 1468 - 1478, Update the
fail_values construction around ca_resume_deadframe and ca_resume_frame to apply
a nonzero ca_resume_frame replacement uniformly, including when the baseline
comes from ca_resume_deadframe. Mirror the existing raw_deadframe handling:
select the appropriate source values, copy them when relocation is required,
replace fail_values[0], and otherwise preserve the original slice without
copying.
🤖 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 `@pyre/extra_tests/parity_tests/closure_cell_protocol.py`:
- Around line 63-109: Replace each `assert False, ...` fallback in the CellType
protocol checks with an explicit `raise AssertionError(...)`, preserving the
existing failure messages and exception-handling flow so these tests remain
effective under optimized Python execution.

In `@pyre/extra_tests/parity_tests/function_python314.py`:
- Around line 63-65: Rename the annotate function’s format parameter to a
non-shadowing name, and update its assertion accordingly while preserving the
existing return value and behavior.

In `@pyre/pyre-interpreter/src/baseobjspace.rs`:
- Around line 4990-5026: Update the property fast path in the is_property branch
of getattribute to catch AttributeError from each relevant get(descr, obj,
w_type) call and route it through instance_getattr_hook_or_err before returning.
Preserve successful descriptor results and propagate non-AttributeError failures
unchanged so property subclasses retain their __getattr__ fallback.

In `@pyre/pyre-interpreter/src/call.rs`:
- Line 1681: Update the TypeError construction in the callable handling branch
to use the runtime type name of the classmethod object, so subclasses report
“'<type>' object is not callable” consistently with the dynamic fallback path
instead of hardcoding “classmethod”.
- Around line 1669-1675: Update the staticmethod branch in the call dispatch
logic to unwrap and forward directly only when callable is exactly the builtin
staticmethod type. For staticmethod subclasses, skip w_staticmethod_get_func and
dispatch through the subclass’s own __call__ slot so overrides are honored,
while preserving the existing argument forwarding for exact builtin instances.

In `@pyre/pyre-interpreter/src/pycode.rs`:
- Around line 461-475: Update require_code to validate the code-body pointer’s
alignment before constructing a reference with &*ptr. Reuse the same
alignment-checking logic or helper used by w_code_new_with_hidden_applevel,
while preserving the existing null-pointer error handling and rejecting
unaligned test stubs with an appropriate error.
- Around line 575-588: Update code-object construction and replacement paths,
including read_code_u32 and the co_firstlineno handling, to validate integer
values against the supported C int/u32 range before casting. Return the
appropriate error on overflow or underflow instead of allowing wrapping,
truncation, or clamping; preserve existing behavior for values within range.

In `@pyre/pyre-interpreter/src/typedef.rs`:
- Around line 1639-1656: Update module_descr_init to validate that positional
contains the required self argument before indexing it. Handle an empty
positional slice by returning the appropriate TypeError, while preserving the
existing name validation and initialization flow when positional[0] is present;
follow the sibling staticmethod_descr_init, classmethod_descr_init, and
property_descr_init guards.

---

Outside diff comments:
In `@pyre/pyre-interpreter/src/pycode.rs`:
- Around line 980-1032: Update the code.replace construction flow around the
varnames, cellvars, and freevars assignments to rebuild locals-plus metadata
after these fields change. Reuse the same locals-plus derivation and code_new
argument-count validation so the cloned CodeObject’s frame layout and metadata
remain consistent, and propagate any validation error instead of returning the
invalid object.

In `@pyre/pyre-jit/src/call_jit.rs`:
- Around line 1468-1478: Update the fail_values construction around
ca_resume_deadframe and ca_resume_frame to apply a nonzero ca_resume_frame
replacement uniformly, including when the baseline comes from
ca_resume_deadframe. Mirror the existing raw_deadframe handling: select the
appropriate source values, copy them when relocation is required, replace
fail_values[0], and otherwise preserve the original slice without copying.
🪄 Autofix (Beta)

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

Run ID: 3f5a679a-3b05-40e5-91d5-0c26e689a3fa

📥 Commits

Reviewing files that changed from the base of the PR and between f5577c4 and 40d5328.

📒 Files selected for processing (35)
  • pyre/bench/synth/classmethod_protocol_hot.py
  • pyre/bench/synth/property_protocol_hot.py
  • pyre/extra_tests/parity_tests/classmethod_protocol.py
  • pyre/extra_tests/parity_tests/closure_cell_protocol.py
  • pyre/extra_tests/parity_tests/code_python314.py
  • pyre/extra_tests/parity_tests/function_member_descriptors_python314.py
  • pyre/extra_tests/parity_tests/function_python314.py
  • pyre/extra_tests/parity_tests/getset_descriptor_python314.py
  • pyre/extra_tests/parity_tests/mappingproxy_python314.py
  • pyre/extra_tests/parity_tests/method_python314.py
  • pyre/extra_tests/parity_tests/module_type_python314.py
  • pyre/extra_tests/parity_tests/property_protocol.py
  • pyre/extra_tests/parity_tests/range_python314.py
  • pyre/extra_tests/parity_tests/staticmethod_protocol.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/builtins.rs
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/display.rs
  • pyre/pyre-interpreter/src/eval.rs
  • pyre/pyre-interpreter/src/function.rs
  • pyre/pyre-interpreter/src/module/_codecs/mod.rs
  • pyre/pyre-interpreter/src/module/_weakref/interp__weakref.rs
  • pyre/pyre-interpreter/src/objspace/descroperation.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-interpreter/src/opcode_ops.rs
  • pyre/pyre-interpreter/src/pycode.rs
  • pyre/pyre-interpreter/src/pyframe.rs
  • pyre/pyre-interpreter/src/pyopcode.rs
  • pyre/pyre-interpreter/src/shared_opcode.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-jit-trace/src/trace_opcode.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-object/src/descriptor.rs
  • pyre/pyre-object/src/function.rs
  • pyre/pyre-object/src/typedef.rs
💤 Files with no reviewable changes (1)
  • pyre/pyre-interpreter/src/opcode_ops.rs

Comment on lines +63 to +109
try:
hash(one)
except TypeError:
pass
else:
assert False, "cell must be unhashable"

try:
CellType(contents=1)
except TypeError:
pass
else:
assert False, "cell constructor arguments are positional-only"

try:
CellType(1, 2)
except TypeError:
pass
else:
assert False, "cell accepts at most one content argument"

try:
type("CellSubclass", (CellType,), {})
except TypeError:
pass
else:
assert False, "cell is not an acceptable base type"

# A cell is an ordinary visible Python object. Storing one in another cell
# must not transparently unwrap the inner cell at an object-space boundary.
nested = CellType(one)
assert nested.cell_contents is one
assert nested != one
assert bool(empty) and bool(one)
assert not isinstance(one, int)
box = [None]
box[0] = one
assert box[0] is one
mapping = {"cell": one}
assert mapping["cell"] is one
try:
one + 1
except TypeError:
pass
else:
assert False, "cell arithmetic must not operate on its contents"

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 | 🟡 Minor | ⚡ Quick win

Replace assert False with raise AssertionError.

assert False statements are entirely removed when Python is run with optimizations (-O), which would silently allow these tests to pass even if the expected exception is not raised. Use raise AssertionError(...) instead to ensure the test fails reliably, matching the pattern you used in other files like code_python314.py.

♻️ Proposed fixes
 try:
     hash(one)
 except TypeError:
     pass
 else:
-    assert False, "cell must be unhashable"
+    raise AssertionError("cell must be unhashable")
 
 try:
     CellType(contents=1)
 except TypeError:
     pass
 else:
-    assert False, "cell constructor arguments are positional-only"
+    raise AssertionError("cell constructor arguments are positional-only")
 
 try:
     CellType(1, 2)
 except TypeError:
     pass
 else:
-    assert False, "cell accepts at most one content argument"
+    raise AssertionError("cell accepts at most one content argument")
 
 try:
     type("CellSubclass", (CellType,), {})
 except TypeError:
     pass
 else:
-    assert False, "cell is not an acceptable base type"
+    raise AssertionError("cell is not an acceptable base type")
 
 # A cell is an ordinary visible Python object. Storing one in another cell
 # must not transparently unwrap the inner cell at an object-space boundary.
 nested = CellType(one)
 assert nested.cell_contents is one
 assert nested != one
 assert bool(empty) and bool(one)
 assert not isinstance(one, int)
 box = [None]
 box[0] = one
 assert box[0] is one
 mapping = {"cell": one}
 assert mapping["cell"] is one
 try:
     one + 1
 except TypeError:
     pass
 else:
-    assert False, "cell arithmetic must not operate on its contents"
+    raise AssertionError("cell arithmetic must not operate on its contents")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
hash(one)
except TypeError:
pass
else:
assert False, "cell must be unhashable"
try:
CellType(contents=1)
except TypeError:
pass
else:
assert False, "cell constructor arguments are positional-only"
try:
CellType(1, 2)
except TypeError:
pass
else:
assert False, "cell accepts at most one content argument"
try:
type("CellSubclass", (CellType,), {})
except TypeError:
pass
else:
assert False, "cell is not an acceptable base type"
# A cell is an ordinary visible Python object. Storing one in another cell
# must not transparently unwrap the inner cell at an object-space boundary.
nested = CellType(one)
assert nested.cell_contents is one
assert nested != one
assert bool(empty) and bool(one)
assert not isinstance(one, int)
box = [None]
box[0] = one
assert box[0] is one
mapping = {"cell": one}
assert mapping["cell"] is one
try:
one + 1
except TypeError:
pass
else:
assert False, "cell arithmetic must not operate on its contents"
try:
hash(one)
except TypeError:
pass
else:
raise AssertionError("cell must be unhashable")
try:
CellType(contents=1)
except TypeError:
pass
else:
raise AssertionError("cell constructor arguments are positional-only")
try:
CellType(1, 2)
except TypeError:
pass
else:
raise AssertionError("cell accepts at most one content argument")
try:
type("CellSubclass", (CellType,), {})
except TypeError:
pass
else:
raise AssertionError("cell is not an acceptable base type")
# A cell is an ordinary visible Python object. Storing one in another cell
# must not transparently unwrap the inner cell at an object-space boundary.
nested = CellType(one)
assert nested.cell_contents is one
assert nested != one
assert bool(empty) and bool(one)
assert not isinstance(one, int)
box = [None]
box[0] = one
assert box[0] is one
mapping = {"cell": one}
assert mapping["cell"] is one
try:
one + 1
except TypeError:
pass
else:
raise AssertionError("cell arithmetic must not operate on its contents")
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 68-68: Assertion always fails, replace with pytest.fail()

(PT015)


[warning] 68-68: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


[warning] 75-75: Assertion always fails, replace with pytest.fail()

(PT015)


[warning] 75-75: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


[warning] 82-82: Assertion always fails, replace with pytest.fail()

(PT015)


[warning] 82-82: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


[warning] 89-89: Assertion always fails, replace with pytest.fail()

(PT015)


[warning] 89-89: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


[warning] 96-96: Assertion should be broken down into multiple parts

Break down assertion into multiple parts

(PT018)


[warning] 108-108: Assertion always fails, replace with pytest.fail()

(PT015)


[warning] 108-108: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)

🤖 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/extra_tests/parity_tests/closure_cell_protocol.py` around lines 63 -
109, Replace each `assert False, ...` fallback in the CellType protocol checks
with an explicit `raise AssertionError(...)`, preserving the existing failure
messages and exception-handling flow so these tests remain effective under
optimized Python execution.

Comment on lines +63 to +65
def annotate(format):
assert format == 1
return {"value": int}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename the protocol parameter to satisfy Ruff.

format shadows the builtin and triggers the configured A002 error.

Proposed fix
-def annotate(format):
-    assert format == 1
+def annotate(annotation_format):
+    assert annotation_format == 1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def annotate(format):
assert format == 1
return {"value": int}
def annotate(annotation_format):
assert annotation_format == 1
return {"value": int}
🧰 Tools
🪛 Ruff (0.15.21)

[error] 63-63: Function argument format is shadowing a Python builtin

(A002)

🤖 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/extra_tests/parity_tests/function_python314.py` around lines 63 - 65,
Rename the annotate function’s format parameter to a non-shadowing name, and
update its assertion accordingly while preserving the existing return value and
behavior.

Source: Linters/SAST tools

Comment on lines +4990 to +5026
// W_Property is a native W_Root allocation, but a property subclass is a
// normal heap type with an instance dict. Run the same five-step
// descriptor protocol as `W_ObjectObject` before the generic builtin
// TypeDef shortcut below: data descriptor, instance dict, non-data
// descriptor. This is the structural consequence of PyPy's
// `W_Property(W_Root)` + `getclass(space)`, and is what lets the subclass
// class's `__doc__ = None` entry be shadowed by the doc value that
// property.__init__ writes into the subclass instance dict.
unsafe {
if is_property(obj) {
if let Some(w_type) = crate::typedef::r#type(obj) {
let w_descr = lookup_in_type_where(w_type, name);
if let Some(descr) = w_descr {
if is_data_descr(descr) {
if let Some(result) = get(descr, obj, w_type)? {
return Ok(result);
}
}
}
let w_dict = getdict_backing(obj);
if !w_dict.is_null() {
if let Some(value) = pyre_object::w_dict_getitem_str(w_dict, name) {
return Ok(value);
}
}
if let Some(descr) = w_descr {
if crate::is_function(descr) {
return Ok(pyre_object::w_method_new(descr, obj, w_type));
}
if let Some(result) = get(descr, obj, w_type)? {
return Ok(result);
}
return Ok(descr);
}
}
}
}

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline pyre/pyre-interpreter/src/baseobjspace.rs \
  --items all --match 'getattr_str_impl|instance_getattr_hook_or_err|getattribute_if_not_from_object'

rg -n -C4 \
  'if is_property\(obj\)|getattribute_if_not_from_object|instance_getattr_hook_or_err' \
  pyre/pyre-interpreter/src/baseobjspace.rs

Repository: youknowone/pyre

Length of output: 6945


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '4950,5045p' pyre/pyre-interpreter/src/baseobjspace.rs | cat -n

Repository: youknowone/pyre

Length of output: 4856


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline pyre/pyre-interpreter/src/baseobjspace.rs --match 'fn getattr_str_impl'
sed -n '3527,5065p' pyre/pyre-interpreter/src/baseobjspace.rs | cat -n | sed -n '1,260p'

Repository: youknowone/pyre

Length of output: 14908


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '3970,4105p' pyre/pyre-interpreter/src/baseobjspace.rs | cat -n

Repository: youknowone/pyre

Length of output: 7638


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 'fn get\(|match .*PyErrorKind::AttributeError|instance_getattr_hook_or_err\(' pyre/pyre-interpreter/src/baseobjspace.rs

Repository: youknowone/pyre

Length of output: 2324


Honor the existing __getattr__ fallback in this property fast path pyre/pyre-interpreter/src/baseobjspace.rs:4999-5026
get(descr, ...) can raise AttributeError here, but unlike the main instance path this branch returns it directly. Route that error through instance_getattr_hook_or_err so property subclasses still see __getattr__.

🤖 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/baseobjspace.rs` around lines 4990 - 5026, Update
the property fast path in the is_property branch of getattribute to catch
AttributeError from each relevant get(descr, obj, w_type) call and route it
through instance_getattr_hook_or_err before returning. Preserve successful
descriptor results and propagate non-AttributeError failures unchanged so
property subclasses retain their __getattr__ fallback.

Comment on lines +1669 to +1675
// function.py:712-713 StaticMethod.descr_call — the wrapper contributes
// no implicit argument; forward the original positional and keyword
// collections unchanged to its w_function.
if unsafe { pyre_object::is_staticmethod(callable) } {
let func = unsafe { pyre_object::w_staticmethod_get_func(callable) };
return call_with_kwargs(frame, func, pos_args, kwargs);
}

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate existing staticmethod subtype-call parity coverage and dispatch helpers.
rg -n -C3 'class .*\(staticmethod\)|staticmethod.*__call__|is_staticmethod|staticmethod_call_override' \
  pyre/extra_tests pyre/pyre-interpreter/src

Repository: youknowone/pyre

Length of output: 12941


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding dispatch in call.rs and the helper/type semantics.
sed -n '1610,1705p' pyre/pyre-interpreter/src/call.rs

printf '\n--- typedef staticmethod call helper ---\n'
sed -n '10610,10640p' pyre/pyre-interpreter/src/typedef.rs

printf '\n--- staticmethod protocol tests around subclassing ---\n'
sed -n '130,190p' pyre/extra_tests/parity_tests/staticmethod_protocol.py

printf '\n--- exact helper definitions/uses ---\n'
rg -n -C2 'fn is_staticmethod|struct StaticMethod|staticmethod_descr_call|descr_call|__call__' pyre/pyre-interpreter/src

Repository: youknowone/pyre

Length of output: 48280


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the local helper definition and whether the parity tests exercise subclass __call__.
rg -n -C3 'fn is_staticmethod|pub fn is_staticmethod|StaticMethodSubclass|__call__' pyre/pyre-interpreter/src pyre/extra_tests/parity_tests/staticmethod_protocol.py

printf '\n--- CPython semantics probe ---\n'
python3 - <<'PY'
class S(staticmethod):
    def __call__(self, *a, **kw):
        print("subclass __call__", a, kw)
        return "subclass"

def f(*a, **kw):
    print("wrapped f", a, kw)
    return "wrapped"

s = S(f)
print(type(s).__name__)
print("call result:", s(1, x=2))
PY

Repository: youknowone/pyre

Length of output: 36696


🌐 Web query:

CPython staticmethod subclass __call__ override semantics callable subclass of staticmethod

💡 Result:

In CPython, the semantics regarding the callability of staticmethod objects changed significantly in Python 3.10 [1][2]. Prior to Python 3.10, staticmethod objects were not themselves callable [2][3]; while they acted as descriptors that returned the underlying function when accessed via a class or instance, calling the staticmethod object directly (e.g., via its reference in the class dictionary) would raise a TypeError [1][4][5]. Since Python 3.10, staticmethod objects are natively callable [1][2][3]. This was implemented by adding a call method to the C implementation of staticmethod, which simply forwards the arguments to the underlying wrapped callable [2]. Consequently, instances of staticmethod now behave as wrappers that are both descriptors (for integration into the attribute lookup process) and callable objects (acting as direct proxies for the wrapped function) [2][3]. Regarding subclassing and overriding: 1. Polymorphism and Inheritance: Static methods do not participate in standard polymorphism [6]. When you define a static method in a subclass with the same name as one in a base class, you are performing name shadowing [6]. Because static methods do not receive implicit self or cls arguments and do not utilize the descriptor get protocol to bind to the caller, there is no automatic dynamic dispatch [6]. 2. Overriding call: While you can create a subclass of staticmethod, overriding call to customize behavior is generally ineffective for the standard usage of static methods. When a class is accessed, the descriptor protocol's get method is invoked [7][8]. The default staticmethod.get returns the underlying wrapped object (the function), effectively bypassing your custom subclass's call logic unless you explicitly invoke that descriptor manually [3][7]. If you require polymorphic behavior or complex call-time logic, it is recommended to use standard instance methods, class methods, or descriptor objects that explicitly implement the desired get or call semantics, rather than subclassing the staticmethod descriptor itself [6][5].

Citations:


🌐 Web query:

site:github.com/python/cpython staticmethod subclass __call__ tp_call

💡 Result:

Starting in Python 3.10, objects created by the staticmethod decorator are callable [1][2]. This change was implemented by adding a tp_call slot to the staticmethod type in the CPython source code [1]. Before Python 3.10, staticmethod objects were not callable, and attempting to call one directly would result in a TypeError [2][3]. The implementation of this feature involved adding the sm_call function to the PyStaticMethod_Type definition, which serves as the handler for the tp_call slot [1][4]. The implementation of sm_call is straightforward: static PyObject* sm_call(PyObject *callable, PyObject *args, PyObject *kwargs) { staticmethod *sm = (staticmethod *)callable; return PyObject_Call(sm->sm_callable, args, kwargs); } When a staticmethod object is called, the tp_call slot (sm_call) is invoked, which in turn delegates the call to the underlying wrapped callable (sm->sm_callable) using PyObject_Call [1][4]. Subclassing staticmethod does not change this behavior; the subclass will inherit the same tp_call implementation unless it explicitly overrides it, ensuring that instances of the subclass remain callable as well.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect nearby classmethod handling to compare the dispatch strategy.
sed -n '1640,1688p' pyre/pyre-interpreter/src/call.rs

printf '\n--- classmethod-related call sites ---\n'
rg -n -C2 'is_classmethod|classmethod' pyre/pyre-interpreter/src/call.rs pyre/pyre-interpreter/src/typedef.rs pyre/extra_tests/parity_tests

Repository: youknowone/pyre

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the exact predicate implementation for staticmethod and related wrappers.
rg -n -C3 'fn is_staticmethod|pub fn is_staticmethod|is_staticmethod\(' pyre/pyre-interpreter/src

Repository: youknowone/pyre

Length of output: 10823


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any parity coverage that exercises the staticmethod subclass.
rg -n -C4 'StaticMethodSubclass|staticmethod subclass|subclass.*staticmethod|__call__' pyre/extra_tests

Repository: youknowone/pyre

Length of output: 11366


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the staticmethod predicate and type layout directly.
rg -n -C4 'pub unsafe fn is_staticmethod|fn is_staticmethod|STATICMETHOD_TYPE|w_staticmethod_new|typedef.*staticmethod' \
  pyre/pyre-interpreter/src/function.rs pyre/pyre-interpreter/src/typedef.rs pyre/pyre-interpreter/src/baseobjspace.rs

printf '\n--- surrounding function helpers ---\n'
sed -n '1,220p' pyre/pyre-interpreter/src/function.rs

Repository: youknowone/pyre

Length of output: 16300


Honor staticmethod subclass __call__ overrides before unwrapping. staticmethod subclasses can override __call__; this fast path unwraps first and skips that override. Keep the unwrap fast path only for the exact builtin type, and dispatch subclasses through their own call 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/call.rs` around lines 1669 - 1675, Update the
staticmethod branch in the call dispatch logic to unwrap and forward directly
only when callable is exactly the builtin staticmethod type. For staticmethod
subclasses, skip w_staticmethod_get_func and dispatch through the subclass’s own
__call__ slot so overrides are honored, while preserving the existing argument
forwarding for exact builtin instances.

if let Some(bound) = classmethod_call_override(callable)? {
return call_with_kwargs(frame, bound, pos_args, kwargs);
}
return Err(PyError::type_error("'classmethod' object is not callable"));

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant regions in call.rs
sed -n '1640,1705p' pyre/pyre-interpreter/src/call.rs
printf '\n----\n'
sed -n '2325,2360p' pyre/pyre-interpreter/src/call.rs

printf '\n---- SEARCH ----\n'
rg -n "classmethod|__name__|type\\(callable\\)|not callable" pyre/pyre-interpreter/src/call.rs

Repository: youknowone/pyre

Length of output: 5925


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1640,1705p' pyre/pyre-interpreter/src/call.rs
printf '\n----\n'
sed -n '2325,2360p' pyre/pyre-interpreter/src/call.rs
printf '\n---- SEARCH ----\n'
rg -n "classmethod|__name__|type\\(callable\\)|not callable" pyre/pyre-interpreter/src/call.rs

Repository: youknowone/pyre

Length of output: 5925


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=pyre/pyre-interpreter/src/call.rs

# Print the relevant line ranges with numbers.
nl -ba "$FILE" | sed -n '1665,1695p'
printf '\n----\n'
nl -ba "$FILE" | sed -n '2338,2355p'
printf '\n----\n'
# Show all occurrences of classmethod-related callable errors in this file.
rg -n "'classmethod' object is not callable|not callable|__name__" "$FILE"

Repository: youknowone/pyre

Length of output: 193


🌐 Web query:

Python classmethod subclass TypeError "object is not callable" subclass name

💡 Result:

The TypeError: 'classmethod' object is not callable occurs because you are attempting to directly call a classmethod object before it has been processed by Python's descriptor protocol [1][2][3]. In Python, the @classmethod decorator does not return a standard, callable function [4][3]. Instead, it returns a descriptor object (an instance of the classmethod type) [2][3]. This object is specifically designed to handle attribute access; when you access the method via a class (e.g., MyClass.my_method), the descriptor's get method is triggered [1][2][3]. This process "extracts" the underlying function and binds it to the class, returning a callable bound method [1][3][5]. If you bypass this lookup process—such as by accessing the method directly from the class dict or attempting to call it before the class is fully constructed—you are left with the raw, non-callable classmethod descriptor [2][6][3]. Common scenarios where this error arises include: 1. Accessing a classmethod directly from the class dict: Using MyClass.dict['my_method'] returns the raw descriptor, not the callable method [3]. 2. Calling it during class construction: Attempting to call a classmethod inside the body of the class definition (before the class object exists) fails because the descriptor has not yet been bound to a class [2]. 3. Decorator order: If you apply a custom decorator to a method, applying the custom decorator before @classmethod can cause the custom decorator to wrap the raw classmethod object instead of the bound function [3]. To resolve this, ensure you always access the method through the class (e.g., MyClass.my_method) so that the descriptor protocol is invoked, or access the underlying function directly via the func attribute if you must bypass the binding [1][4][7].

Citations:


🏁 Script executed:

python3 - <<'PY'
class C(classmethod):
    pass

def f(cls):
    return cls

x = C(f)
print(type(x).__name__)
try:
    x()
except Exception as e:
    print(type(e).__name__, e)
PY

Repository: youknowone/pyre

Length of output: 192


Use the runtime type name here. classmethod subclasses can reach this branch too, and the error should match the dynamic fallback path: TypeError: '<type>' object is not callable rather than always saying 'classmethod' object is not callable'.

🤖 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/call.rs` at line 1681, Update the TypeError
construction in the callable handling branch to use the runtime type name of the
classmethod object, so subclasses report “'<type>' object is not callable”
consistently with the dynamic fallback path instead of hardcoding “classmethod”.

Comment on lines +461 to +475
#[inline]
unsafe fn require_code(
obj: PyObjectRef,
descriptor: &str,
) -> Result<&'static crate::CodeObject, crate::PyError> {
if obj.is_null() || !unsafe { is_code(obj) } {
return Err(crate::PyError::type_error(format!(
"descriptor '{descriptor}' requires a 'code' object"
)));
}
let ptr = unsafe { w_code_get_ptr(obj) } as *const crate::CodeObject;
if ptr.is_null() {
return Err(crate::PyError::type_error("code object has no code body"));
}
Ok(unsafe { &*ptr })

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 | 🟠 Major | ⚡ Quick win

Reject unaligned code-body pointers before dereferencing.

w_code_new_with_hidden_applevel intentionally permits unaligned test stubs, but require_code only rejects null before forming &*ptr. Reuse the constructor’s alignment check to avoid undefined behavior when a descriptor receives such a stub.

🤖 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/pycode.rs` around lines 461 - 475, Update
require_code to validate the code-body pointer’s alignment before constructing a
reference with &*ptr. Reuse the same alignment-checking logic or helper used by
w_code_new_with_hidden_applevel, while preserving the existing null-pointer
error handling and rejecting unaligned test stubs with an appropriate error.

Comment on lines +575 to +588
let argcount = unsafe { read_code_u32(args[1], "argcount")? };
let posonly = unsafe { read_code_u32(args[2], "posonlyargcount")? };
let kwonly = unsafe { read_code_u32(args[3], "kwonlyargcount")? };
let nlocals = unsafe { read_code_u32(args[4], "nlocals")? };
let stacksize_value = unsafe { crate::builtins::space_index_w(args[5])? };
let flags_value = unsafe { crate::builtins::space_index_w(args[6])? };
if stacksize_value < 0 || flags_value < 0 {
return Err(crate::PyError::new(
crate::PyErrorKind::SystemError,
"Objects/codeobject.c: bad argument to internal function",
));
}
let stacksize = stacksize_value as u32;
let flags_bits = flags_value as u32;

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 \
  'space_index_w\(.*\)|as u32|clamp\(i32::MIN|read_code_u32' \
  pyre/pyre-interpreter/src/pycode.rs

curl -fsSL https://raw.githubusercontent.com/python/cpython/3.14/Objects/codeobject.c |
  grep -E 'code_new_impl|code_replace_impl' | head

Repository: youknowone/pyre

Length of output: 6115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact Rust conversion paths around the constructor and replacement helpers.
sed -n '560,690p' pyre/pyre-interpreter/src/pycode.rs
printf '\n---\n'
sed -n '960,1065p' pyre/pyre-interpreter/src/pycode.rs

printf '\n=== CPython codeobject signatures and clinic text ===\n'
python3 - <<'PY'
import urllib.request, re
url = "https://raw.githubusercontent.com/python/cpython/3.14/Objects/codeobject.c"
text = urllib.request.urlopen(url, timeout=20).read().decode()
for pat in [
    r'code_new_impl\(PyTypeObject \*type, int argcount, int posonlyargcount,',
    r'code_replace_impl\(PyCodeObject \*self, int co_argcount,',
    r'co_stacksize',
    r'co_flags',
    r'co_firstlineno',
]:
    print(f'-- {pat} --')
    for m in re.finditer(pat, text):
        start = max(0, m.start()-250)
        end = min(len(text), m.end()+600)
        print(text[start:end])
        print()
        break
PY

Repository: youknowone/pyre

Length of output: 12166


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '=== Rust helper ranges ===\n'
rg -n -C2 'fn space_index_w|read_code_u32|as u32|clamp\(i32::MIN' pyre/pyre-interpreter/src

printf '\n=== CPython codeobject signatures (raw source, no cert verify) ===\n'
python3 - <<'PY'
import urllib.request, ssl, re
ctx = ssl._create_unverified_context()
url = "https://raw.githubusercontent.com/python/cpython/3.14/Objects/codeobject.c"
text = urllib.request.urlopen(url, context=ctx, timeout=20).read().decode()
for name in ("code_new_impl", "code_replace_impl"):
    m = re.search(rf'{name}\((.*?)\)\s*{{', text, re.S)
    if not m:
        print(f"missing {name}")
        continue
    sig = m.group(1).replace("\n", " ")
    print(f"{name}({sig[:500]})")
print("\n=== relevant occurrences ===")
for pat in ("co_stacksize", "co_flags", "co_firstlineno"):
    print(f'-- {pat} --')
    for m in re.finditer(pat, text):
        start = max(0, m.start()-180)
        end = min(len(text), m.end()+300)
        print(text[start:end])
        print()
        break
PY

Repository: youknowone/pyre

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '=== Rust helper ranges ===\n'
rg -n -C2 'fn space_index_w|read_code_u32|as u32|clamp\(i32::MIN' pyre/pyre-interpreter/src

printf '\n=== CPython codeobject signatures (raw source, no cert verify) ===\n'
python3 - <<'PY'
import urllib.request, ssl, re
ctx = ssl._create_unverified_context()
url = "https://raw.githubusercontent.com/python/cpython/3.14/Objects/codeobject.c"
text = urllib.request.urlopen(url, context=ctx, timeout=20).read().decode()
for name in ("code_new_impl", "code_replace_impl"):
    m = re.search(rf'{name}\((.*?)\)\s*{{', text, re.S)
    if not m:
        print(f"missing {name}")
        continue
    sig = " ".join(m.group(1).split())
    print(f"{name}({sig[:700]})")
print("\n=== relevant occurrences ===")
for pat in ("co_stacksize", "co_flags", "co_firstlineno"):
    print(f'-- {pat} --')
    for m in re.finditer(pat, text):
        start = max(0, m.start()-180)
        end = min(len(text), m.end()+300)
        print(text[start:end])
        print()
        break
PY

Repository: youknowone/pyre

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '=== CPython codeobject signatures and checks ===\n'
curl -k -fsSL https://raw.githubusercontent.com/python/cpython/3.14/Objects/codeobject.c |
  awk '
    /code_new_impl\(PyTypeObject \*type, int argcount, int posonlyargcount,/ {show=1}
    /code_replace_impl\(PyCodeObject \*self, int co_argcount,/ {show=1}
    show {print}
    /}/ && show {exit}
  '

printf '\n=== Overflow / range-related fields in pycode.rs ===\n'
sed -n '575,668p' pyre/pyre-interpreter/src/pycode.rs
sed -n '1049,1061p' pyre/pyre-interpreter/src/pycode.rs

Repository: youknowone/pyre

Length of output: 1153


Reject oversized int inputs before casting
space_index_w(...)? as u32 and the co_firstlineno clamp silently truncate values outside the C int range. code.__new__ and code.replace() take int parameters in CPython, so these paths should fail on overflow instead of wrapping/clamping. The same check is needed in read_code_u32.

🤖 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/pycode.rs` around lines 575 - 588, Update
code-object construction and replacement paths, including read_code_u32 and the
co_firstlineno handling, to validate integer values against the supported C
int/u32 range before casting. Return the appropriate error on overflow or
underflow instead of allowing wrapping, truncation, or clamping; preserve
existing behavior for values within range.

Comment on lines +1639 to +1656
let Some(w_name) = w_name else {
return Err(crate::PyError::type_error(
"module.__init__() missing required argument 'name' (pos 1)".to_string(),
"module() missing required argument 'name' (pos 1)",
));
}
};
let self_ = positional[0];
let w_name = positional[1];
let w_doc = positional
.get(2)
.copied()
.unwrap_or_else(pyre_object::w_none);
let name = crate::baseobjspace::text_w(w_name)?;
if !unsafe { pyre_object::is_str(w_name) } {
let received = if unsafe { pyre_object::is_none(w_name) } {
"None".to_string()
} else {
crate::typedef::r#type(w_name)
.map(|tp| unsafe { pyre_object::w_type_get_name(tp) }.to_string())
.unwrap_or_else(|| unsafe { (*(*w_name).ob_type).name }.to_string())
};
return Err(crate::PyError::type_error(format!(
"module() argument 'name' must be str, not {received}"
)));
}

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 | 🟠 Major | ⚡ Quick win

positional[0] panics when module()/module.__init__ is called with no positional args. After split_builtin_kwargs, a keyword-only invocation such as types.ModuleType.__init__(name='x') yields positional == []; given is 0, the name kwarg satisfies the let Some(w_name) check, and execution reaches let self_ = positional[0], indexing an empty slice → panic. CPython raises a TypeError here instead. The sibling staticmethod_descr_init/classmethod_descr_init/property_descr_init all guard this with positional.first()/empty-checks; module_descr_init should too.

🛡️ Proposed fix
-    let self_ = positional[0];
+    let Some(self_) = positional.first().copied() else {
+        return Err(crate::PyError::type_error(
+            "descriptor '__init__' of 'module' object needs an argument",
+        ));
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let Some(w_name) = w_name else {
return Err(crate::PyError::type_error(
"module.__init__() missing required argument 'name' (pos 1)".to_string(),
"module() missing required argument 'name' (pos 1)",
));
}
};
let self_ = positional[0];
let w_name = positional[1];
let w_doc = positional
.get(2)
.copied()
.unwrap_or_else(pyre_object::w_none);
let name = crate::baseobjspace::text_w(w_name)?;
if !unsafe { pyre_object::is_str(w_name) } {
let received = if unsafe { pyre_object::is_none(w_name) } {
"None".to_string()
} else {
crate::typedef::r#type(w_name)
.map(|tp| unsafe { pyre_object::w_type_get_name(tp) }.to_string())
.unwrap_or_else(|| unsafe { (*(*w_name).ob_type).name }.to_string())
};
return Err(crate::PyError::type_error(format!(
"module() argument 'name' must be str, not {received}"
)));
}
let Some(w_name) = w_name else {
return Err(crate::PyError::type_error(
"module() missing required argument 'name' (pos 1)",
));
};
let Some(self_) = positional.first().copied() else {
return Err(crate::PyError::type_error(
"descriptor '__init__' of 'module' object needs an argument",
));
};
if !unsafe { pyre_object::is_str(w_name) } {
let received = if unsafe { pyre_object::is_none(w_name) } {
"None".to_string()
} else {
crate::typedef::r#type(w_name)
.map(|tp| unsafe { pyre_object::w_type_get_name(tp) }.to_string())
.unwrap_or_else(|| unsafe { (*(*w_name).ob_type).name }.to_string())
};
return Err(crate::PyError::type_error(format!(
"module() argument 'name' must be str, not {received}"
)));
}
🤖 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/typedef.rs` around lines 1639 - 1656, Update
module_descr_init to validate that positional contains the required self
argument before indexing it. Handle an empty positional slice by returning the
appropriate TypeError, while preserving the existing name validation and
initialization flow when positional[0] is present; follow the sibling
staticmethod_descr_init, classmethod_descr_init, and property_descr_init guards.

@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: 53dc79bd5c

ℹ️ 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".

pyre_object::dictmultiobject::w_dict_setitem_str_no_proxy(
ns,
"__annotate__",
make_getset_property(annotate_getter, annotate_setter, annotate_deleter),

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 Guard function annotate getset receivers

When function.__dict__['__annotate__'] is invoked directly with a non-function receiver, this reqcls-less make_getset_property forwards arbitrary args[1] into function_get_annotate/function_set_annotate, which cast it to Function. For example, type(f).__dict__['__annotate__'].__get__(42, int) can read invalid fields instead of raising the descriptor TypeError; add the same Function receiver guard/reqcls stamping used for descriptors that access typed payloads.

Useful? React with 👍 / 👎.

@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

https://github.com/youknowone/pyre/blob/fa67ef5d6af75b8ef46f827bbecf75df92aa0825/pyre/pyre-interpreter/src/function.rs#L1128-L1131
P2 Badge Validate annotate results before caching

When a function's PEP 649 __annotate__ callable returns anything other than a dict, Python 3.14 raises TypeError and leaves __annotations__ unset. This path accepts any non-null result and stores it in w_ann, so f.__annotate__ = lambda fmt: 1; f.__annotations__ returns and permanently caches an int, breaking callers that rely on annotations being a mapping.

ℹ️ 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 +1763 to +1766
fn module_descr_dir(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::PyError> {
let module = args[0];
let w_dict = unsafe { pyre_object::w_module_get_w_dict(module) };
if w_dict.is_null()

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 Guard module methods before reading module layout

For direct descriptor calls with a foreign receiver, such as types.ModuleType.__dir__(42), this reaches w_module_get_w_dict without first proving the receiver is a module. That helper reads the Module payload layout, so arbitrary objects can be mis-cast or crash instead of raising the descriptor TypeError; add the same module receiver guard used for the new module getsets before loading the dict.

Useful? React with 👍 / 👎.

Comment on lines +9390 to +9393
|args| {
Ok(pyre_object::w_str_new(&unsafe {
crate::function::function_get_qualname(args[0])
}))

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 Reduce bound builtins through their receiver

For built-in functions that have a non-None __self__ (for example type-bound carriers like int.__new__/dict.fromkeys that this code stamps separately), returning only the qualname is not enough to reconstruct the object. Python 3.14 reduces these as getattr(__self__, __name__); with the current string-only result, pickling/copying those bound builtins will resolve the wrong global name or fail instead of preserving the bound receiver.

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: 5

Caution

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

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

328-340: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clamp oversized __defaults__ to the trailing positional window
At pyre/pyre-interpreter/src/call.rs:328-340, 371-383, 1364-1380, 1432-1445, 1916-1932, 1952-1968, __defaults__ needs to be treated as the last N positional defaults. Oversized tuples currently bind the wrong values in the fill paths and can underflow the too-many-args message.

🤖 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/call.rs` around lines 328 - 340, The
positional-default handling in the call argument binding and arity-message paths
must clamp oversized __defaults__ to the trailing positional parameter window.
Update the ndefaults calculations and corresponding fill paths in the affected
call logic so only the last N defaults are used, preventing incorrect bindings
and underflow in the too-many-arguments message; preserve behavior for tuples
within the valid window.
♻️ Duplicate comments (3)
pyre/pyre-interpreter/src/baseobjspace.rs (2)

10919-10925: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Validate the __delete__ target before indexing.

property.__delete__(prop) can still reach args[1] and panic. Require arity two at registration or retrieve the target safely and return TypeError.

Based on the prior review finding, which remains present in the current code.

🤖 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/baseobjspace.rs` around lines 10919 - 10925, Update
property_descr_delete_impl to validate that args contains both the property and
target object before indexing args[1]. Enforce the two-argument arity at
registration or safely retrieve the target and return the established TypeError
for missing arguments, while preserving the existing deleter validation flow.

4990-5022: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Route property-subclass lookup through normal descriptor binding.

Line 5016 wraps every function as a bound method before calling get(). A builtin function stored on a property subclass must remain unbound, but this path binds it. The get() calls also still propagate AttributeError instead of invoking the subclass’s __getattr__, as previously reported.

Mirror the instance path: call get() first, route AttributeError through instance_getattr_hook_or_err, and only use the user-function fallback when needed.

Based on the supplied descriptor-dispatch context and the prior unresolved review finding.

🤖 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/baseobjspace.rs` around lines 4990 - 5022, Update
the property-subclass branch identified by is_property to mirror normal instance
descriptor lookup: call get() before any function-specific fallback, preserve
builtin functions as unbound, and handle AttributeError from descriptor access
through instance_getattr_hook_or_err. Retain the user-function binding fallback
only when get() does not resolve the descriptor, and ensure unresolved
attributes invoke the subclass’s __getattr__ path.
pyre/pyre-interpreter/src/function.rs (1)

2097-2233: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Validate method receivers before unsafe layout reads.

These public type methods accept arbitrary explicit receivers. descr_method_call can dispatch a null receiver, while equality, repr, attribute, hash, and reduce operations cast this directly to the method layout. Calls such as types.MethodType.__repr__(1) can therefore crash instead of raising TypeError.

Use a shared require_method check before every w_method_get_* access. This is the same receiver-validation issue previously marked addressed, but it remains in the current code.

🤖 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/function.rs` around lines 2097 - 2233, Introduce
and reuse a shared require_method validation helper in the method descriptor
functions. Validate the explicit receiver before dispatch in descr_method_call
and before every w_method_get_* access in descr_method_eq, descr_method_ne,
descr_method_repr, descr_method_getattribute, descr_method_hash, and
descr_method__reduce__, returning TypeError for non-method or null receivers
while preserving normal behavior for valid method objects.
🤖 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 `@pyre/extra_tests/parity_tests/module_type_python314.py`:
- Around line 124-126: Rename the annotation-format parameter in annotate and
the corresponding parameter at the other reported use from format to
annotation_format or _annotation_format as appropriate, updating references
within each function to match and avoid shadowing the built-in while satisfying
Ruff A002, A006, and ARG005.

In `@pyre/pyre-interpreter/src/executioncontext.rs`:
- Around line 1048-1053: Replace the w_dict_str_entries iteration in the
builtins-module binding block with the GC-safe pattern used by importing.rs:
snapshot only string keys before allocating module, then allocate module and
iterate those keys while fetching each current value from the dictionary in a
non-allocating loop. Ensure module is rooted or pinned across the allocation and
pass freshly fetched values to builtin_function_set_module_obj, avoiding
retained raw value pointers from the initial snapshot.

In `@pyre/pyre-interpreter/src/function.rs`:
- Around line 134-138: Update walk_raw_function_roots to visit func.w_moduleobj
alongside the other function root fields, including for immortal builtin
functions created through the builtin-allocation path. Preserve the existing
handling of GC-managed Function allocations and ensure the module object is
traced for relocation and collection.

In `@pyre/pyre-interpreter/src/importing.rs`:
- Around line 864-872: Merge the new module-binding loop into the preceding loop
that already iterates over the same keys, eliminating the second
w_dict_getitem_str lookup. Allocate module immediately before the merged loop,
then apply both builtin_function_set_module and builtin_function_set_module_obj
operations to each retrieved value while preserving their existing behavior.

In `@pyre/pyre-interpreter/src/pycode.rs`:
- Around line 619-636: Update code_replace to rebuild the derived
localspluskinds layout after applying replacement co_varnames, co_cellvars, and
co_freevars, rather than retaining the original CodeObject values. Match the
construction used in code_new, including cell aliases, pure cells, and trailing
free-variable slots, so hidden_local() and frame indexing remain aligned.

---

Outside diff comments:
In `@pyre/pyre-interpreter/src/call.rs`:
- Around line 328-340: The positional-default handling in the call argument
binding and arity-message paths must clamp oversized __defaults__ to the
trailing positional parameter window. Update the ndefaults calculations and
corresponding fill paths in the affected call logic so only the last N defaults
are used, preventing incorrect bindings and underflow in the too-many-arguments
message; preserve behavior for tuples within the valid window.

---

Duplicate comments:
In `@pyre/pyre-interpreter/src/baseobjspace.rs`:
- Around line 10919-10925: Update property_descr_delete_impl to validate that
args contains both the property and target object before indexing args[1].
Enforce the two-argument arity at registration or safely retrieve the target and
return the established TypeError for missing arguments, while preserving the
existing deleter validation flow.
- Around line 4990-5022: Update the property-subclass branch identified by
is_property to mirror normal instance descriptor lookup: call get() before any
function-specific fallback, preserve builtin functions as unbound, and handle
AttributeError from descriptor access through instance_getattr_hook_or_err.
Retain the user-function binding fallback only when get() does not resolve the
descriptor, and ensure unresolved attributes invoke the subclass’s __getattr__
path.

In `@pyre/pyre-interpreter/src/function.rs`:
- Around line 2097-2233: Introduce and reuse a shared require_method validation
helper in the method descriptor functions. Validate the explicit receiver before
dispatch in descr_method_call and before every w_method_get_* access in
descr_method_eq, descr_method_ne, descr_method_repr, descr_method_getattribute,
descr_method_hash, and descr_method__reduce__, returning TypeError for
non-method or null receivers while preserving normal behavior for valid method
objects.
🪄 Autofix (Beta)

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

Run ID: 292b59c7-b9f3-4c0c-b09a-a0189adbb69d

📥 Commits

Reviewing files that changed from the base of the PR and between 40d5328 and fa67ef5.

📒 Files selected for processing (41)
  • majit/majit-backend-wasm/src/codegen.rs
  • majit/majit-backend-wasm/src/lib.rs
  • majit/majit-backend-wasm/tests/codegen_test.rs
  • pyre/bench/synth/classmethod_protocol_hot.py
  • pyre/bench/synth/property_protocol_hot.py
  • pyre/extra_tests/parity_tests/builtin_function_python314.py
  • pyre/extra_tests/parity_tests/classmethod_protocol.py
  • pyre/extra_tests/parity_tests/closure_cell_protocol.py
  • pyre/extra_tests/parity_tests/code_python314.py
  • pyre/extra_tests/parity_tests/function_member_descriptors_python314.py
  • pyre/extra_tests/parity_tests/function_python314.py
  • pyre/extra_tests/parity_tests/getset_descriptor_python314.py
  • pyre/extra_tests/parity_tests/mappingproxy_python314.py
  • pyre/extra_tests/parity_tests/method_python314.py
  • pyre/extra_tests/parity_tests/module_type_python314.py
  • pyre/extra_tests/parity_tests/property_protocol.py
  • pyre/extra_tests/parity_tests/range_python314.py
  • pyre/extra_tests/parity_tests/staticmethod_protocol.py
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/builtins.rs
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/display.rs
  • pyre/pyre-interpreter/src/eval.rs
  • pyre/pyre-interpreter/src/executioncontext.rs
  • pyre/pyre-interpreter/src/function.rs
  • pyre/pyre-interpreter/src/importing.rs
  • pyre/pyre-interpreter/src/module/_codecs/mod.rs
  • pyre/pyre-interpreter/src/module/_weakref/interp__weakref.rs
  • pyre/pyre-interpreter/src/objspace/descroperation.rs
  • pyre/pyre-interpreter/src/objspace/std/mapdict.rs
  • pyre/pyre-interpreter/src/opcode_ops.rs
  • pyre/pyre-interpreter/src/pycode.rs
  • pyre/pyre-interpreter/src/pyframe.rs
  • pyre/pyre-interpreter/src/pyopcode.rs
  • pyre/pyre-interpreter/src/shared_opcode.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-jit-trace/src/trace_opcode.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-object/src/descriptor.rs
  • pyre/pyre-object/src/function.rs
  • pyre/pyre-object/src/typedef.rs
💤 Files with no reviewable changes (2)
  • pyre/pyre-interpreter/src/opcode_ops.rs
  • pyre/pyre-jit/src/call_jit.rs

Comment on lines +124 to +126
def annotate(format):
calls.append(format)
return {"answer": format}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename the annotation-format parameters.

Both uses of format shadow the built-in and trigger configured Ruff errors; use annotation_format and _annotation_format.

Proposed fix
-def annotate(format):
-    calls.append(format)
-    return {"answer": format}
+def annotate(annotation_format):
+    calls.append(annotation_format)
+    return {"answer": annotation_format}
...
-module.__annotate__ = lambda format: 1
+module.__annotate__ = lambda _annotation_format: 1

Based on static analysis: Ruff A002, A006, and ARG005.

Also applies to: 148-148

🧰 Tools
🪛 Ruff (0.15.21)

[error] 124-124: Function argument format is shadowing a Python builtin

(A002)

🤖 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/extra_tests/parity_tests/module_type_python314.py` around lines 124 -
126, Rename the annotation-format parameter in annotate and the corresponding
parameter at the other reported use from format to annotation_format or
_annotation_format as appropriate, updating references within each function to
match and avoid shadowing the built-in while satisfying Ruff A002, A006, and
ARG005.

Source: Linters/SAST tools

Comment on lines +1048 to +1053
// function.py:797-815 BuiltinFunction.w_moduleobj. The defining
// module object does not exist while `new_builtin_module_dict` fills
// the namespace, so bind each builtin's `__self__` now.
for (_, value) in unsafe { pyre_object::w_dict_str_entries(self.builtins_module) } {
unsafe { crate::function::builtin_function_set_module_obj(value, module) };
}

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 | 🔴 Critical | ⚡ Quick win

Fix critical GC vulnerabilities caused by unrooted module and stale value pointers.

w_dict_str_entries calls w_dict_items, which allocates w_str_new for string keys. Because Pyre's GC can trigger during these allocations:

  1. The unpinned module allocated just before the loop can be collected, becoming a dangling pointer.
  2. The raw PyObjectRef values collected into the Rust Vec earlier in the iteration can become stale dangling pointers if a subsequent w_str_new triggers a GC.

To fix this, snapshot only the string keys (which are GC-safe) before allocating module, then fetch the fresh value pointers inside a non-allocating loop. This mirrors the safe pattern used in importing.rs.

🔒️ Proposed fix to ensure GC safety
-        let module = pyre_object::w_module_new_aliasing_dict("builtins", self.builtins_module);
         // function.py:797-815 BuiltinFunction.w_moduleobj. The defining
         // module object does not exist while `new_builtin_module_dict` fills
         // the namespace, so bind each builtin's `__self__` now.
-        for (_, value) in unsafe { pyre_object::w_dict_str_entries(self.builtins_module) } {
-            unsafe { crate::function::builtin_function_set_module_obj(value, module) };
-        }
+        let keys: Vec<String> = unsafe { pyre_object::w_dict_str_entries(self.builtins_module) }
+            .into_iter()
+            .map(|(k, _)| k)
+            .collect();
+        let module = pyre_object::w_module_new_aliasing_dict("builtins", self.builtins_module);
+        for key in &keys {
+            if let Some(value) = unsafe { pyre_object::w_dict_getitem_str(self.builtins_module, key) } {
+                unsafe { crate::function::builtin_function_set_module_obj(value, module) };
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// function.py:797-815 BuiltinFunction.w_moduleobj. The defining
// module object does not exist while `new_builtin_module_dict` fills
// the namespace, so bind each builtin's `__self__` now.
for (_, value) in unsafe { pyre_object::w_dict_str_entries(self.builtins_module) } {
unsafe { crate::function::builtin_function_set_module_obj(value, module) };
}
// function.py:797-815 BuiltinFunction.w_moduleobj. The defining
// module object does not exist while `new_builtin_module_dict` fills
// the namespace, so bind each builtin's `__self__` now.
let keys: Vec<String> = unsafe { pyre_object::w_dict_str_entries(self.builtins_module) }
.into_iter()
.map(|(k, _)| k)
.collect();
let module = pyre_object::w_module_new_aliasing_dict("builtins", self.builtins_module);
for key in &keys {
if let Some(value) = unsafe { pyre_object::w_dict_getitem_str(self.builtins_module, key) } {
unsafe { crate::function::builtin_function_set_module_obj(value, module) };
}
}
🤖 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/executioncontext.rs` around lines 1048 - 1053,
Replace the w_dict_str_entries iteration in the builtins-module binding block
with the GC-safe pattern used by importing.rs: snapshot only string keys before
allocating module, then allocate module and iterate those keys while fetching
each current value from the dictionary in a non-allocating loop. Ensure module
is rooted or pinned across the allocation and pass freshly fetched values to
builtin_function_set_module_obj, avoiding retained raw value pointers from the
initial snapshot.

Comment on lines +134 to +138
/// function.py:797-815 `BuiltinFunction.w_moduleobj` — the module object
/// bound as `__self__` for an interp-level module function. PyPy stores
/// this on each BuiltinFunction; CPython 3.14 likewise exposes the
/// defining module object from `PyCFunction_GET_SELF`.
pub w_moduleobj: PyObjectRef,

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 | 🔴 Critical | ⚡ Quick win

Trace w_moduleobj from immortal builtin functions.

FUNCTION_GC_PTR_OFFSETS only covers GC-managed Function allocations. Builtins use the immortal allocation path, then store a movable module in w_moduleobj; the supplied walk_raw_function_roots visits the other new fields but omits this one, allowing collection or relocation to leave __self__ dangling.

Add func.w_moduleobj to walk_raw_function_roots.

Required cross-file change
 visitor(&mut *(&mut func.w_text_signature as *mut PyObjectRef as *mut majit_ir::GcRef));
+visitor(&mut *(&mut func.w_moduleobj as *mut PyObjectRef as *mut majit_ir::GcRef));

Based on the supplied walk_raw_function_roots and builtin-allocation context.

Also applies to: 300-301, 616-629

🤖 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/function.rs` around lines 134 - 138, Update
walk_raw_function_roots to visit func.w_moduleobj alongside the other function
root fields, including for immortal builtin functions created through the
builtin-allocation path. Preserve the existing handling of GC-managed Function
allocations and ensure the module object is traced for relocation and
collection.

Comment on lines +864 to +872
// function.py:797-815 BuiltinFunction.w_moduleobj — MixedModule binds
// every interp-level function to the live defining module object.
for key in &keys {
if let Some(value) =
unsafe { pyre_object::dictmultiobject::w_dict_getitem_str(w_dict, key) }
{
unsafe { crate::function::builtin_function_set_module_obj(value, module) };
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consolidate loops to eliminate redundant dictionary lookups.

The newly added iteration over keys can be safely merged into the preceding loop. By allocating module immediately before the loop, you halve the number of w_dict_getitem_str lookups, which benefits the startup time optimizations targeted by this PR.

Because neither builtin_function_set_module nor builtin_function_set_module_obj allocate or trigger GC, the unpinned module pointer remains perfectly safe inside the merged loop.

♻️ Proposed fix to merge the loops (applies to lines 851-872)
-    for key in &keys {
-        if let Some(value) =
-            unsafe { pyre_object::dictmultiobject::w_dict_getitem_str(w_dict, key) }
-        {
-            unsafe {
-                crate::function::builtin_function_set_module(
-                    value,
-                    pyre_object::gc_roots::shadow_stack_get(save_point + 1),
-                );
-            }
-        }
-    }
     let module = pyre_object::w_module_new_aliasing_dict(name, w_dict);
     // function.py:797-815 BuiltinFunction.w_moduleobj — MixedModule binds
     // every interp-level function to the live defining module object.
     for key in &keys {
         if let Some(value) =
             unsafe { pyre_object::dictmultiobject::w_dict_getitem_str(w_dict, key) }
         {
             unsafe {
+                crate::function::builtin_function_set_module(
+                    value,
+                    pyre_object::gc_roots::shadow_stack_get(save_point + 1),
+                );
                 crate::function::builtin_function_set_module_obj(value, module);
             }
         }
     }
🤖 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/importing.rs` around lines 864 - 872, Merge the new
module-binding loop into the preceding loop that already iterates over the same
keys, eliminating the second w_dict_getitem_str lookup. Allocate module
immediately before the merged loop, then apply both builtin_function_set_module
and builtin_function_set_module_obj operations to each retrieved value while
preserving their existing behavior.

Comment on lines +619 to +636
if argcount + kwonly > nlocals || posonly > argcount {
return Err(crate::PyError::value_error("code: invalid argument count"));
}

// CPython's localsplus table stores cell aliases on the local slot and
// appends only pure cells, followed by free variables.
let mut localspluskinds = vec![crate::bytecode::CO_FAST_LOCAL; varnames.len()];
for cell in cellvars.iter() {
if let Some(index) = varnames.iter().position(|name| name == cell) {
localspluskinds[index] |= crate::bytecode::CO_FAST_CELL;
} else {
localspluskinds.push(crate::bytecode::CO_FAST_CELL);
}
}
localspluskinds.extend(std::iter::repeat_n(
crate::bytecode::CO_FAST_FREE,
freevars.len(),
));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and locate the constructor/replacement paths.
git ls-files pyre/pyre-interpreter/src/pycode.rs
ast-grep outline pyre/pyre-interpreter/src/pycode.rs --view expanded || true

# Show the relevant sections with line numbers.
sed -n '520,760p' pyre/pyre-interpreter/src/pycode.rs
sed -n '920,1060p' pyre/pyre-interpreter/src/pycode.rs

Repository: youknowone/pyre

Length of output: 21842


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect where localspluskinds and CO_FAST_HIDDEN are defined/used.
rg -n "localspluskinds|CO_FAST_HIDDEN|CO_FAST_LOCAL|CO_FAST_CELL|CO_FAST_FREE|npure_cellvars|hidden_applevel" pyre/pyre-interpreter/src/pycode.rs pyre/pyre-interpreter/src -g '!**/target/**'

# Show the CodeObject definition and any helpers around boxing/validation.
sed -n '1,260p' pyre/pyre-interpreter/src/pycode.rs
sed -n '260,460p' pyre/pyre-interpreter/src/pycode.rs

Repository: youknowone/pyre

Length of output: 29956


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the frame-side layout consumers around localspluskinds.
sed -n '1360,1465p' pyre/pyre-interpreter/src/pyframe.rs
sed -n '1560,1605p' pyre/pyre-interpreter/src/pyframe.rs
sed -n '2930,3045p' pyre/pyre-interpreter/src/pyframe.rs
sed -n '3260,3295p' pyre/pyre-interpreter/src/pyframe.rs
sed -n '1565,1595p' pyre/pyre-interpreter/src/eval.rs

Repository: youknowone/pyre

Length of output: 13817


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any other localspluskinds construction or recomputation.
rg -n "localspluskinds|CO_FAST_HIDDEN|npure_cellvars\(|hidden_local\(" pyre/pyre-interpreter/src pyre -g '!**/target/**'

# Inspect any codeobject-related helpers/tests that might cover replacement semantics.
sed -n '1,220p' pyre/pyre-interpreter/src/pyframe.rs
sed -n '1720,1775p' pyre/pyre-interpreter/src/pycode.rs

Repository: youknowone/pyre

Length of output: 18122


Rebuild localspluskinds in code_replace.
code_new already validates the constructor counts, but code_replace can change co_varnames/co_cellvars/co_freevars and then re-box a CodeObject with the old localspluskinds. Recompute the derived locals-plus layout there so hidden_local() and frame slot indexing stay aligned.

🤖 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/pycode.rs` around lines 619 - 636, Update
code_replace to rebuild the derived localspluskinds layout after applying
replacement co_varnames, co_cellvars, and co_freevars, rather than retaining the
original CodeObject values. Match the construction used in code_new, including
cell aliases, pure cells, and trailing free-variable slots, so hidden_local()
and frame indexing remain aligned.

Source: Coding guidelines

@youknowone
youknowone merged commit 01ab079 into main Jul 17, 2026
30 of 31 checks passed
@youknowone
youknowone deleted the rename branch July 17, 2026 06:34
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