◐ Shell
clean mode source ↗

Add `has_*` methods to opcode enums by ShaharNaveh · Pull Request #7818 · RustPython/RustPython

@ShaharNaveh

Extracted from #7797

Summary by CodeRabbit

  • Refactor
    • Improved bytecode opcode/pseudo-opcode capability checks for more consistent and reliable behavior.
    • Streamlined Python-facing opcode helpers to a uniform approach, reducing duplication and improving maintainability.

Review Change Stack

@coderabbitai

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: c00767f3-6b24-4865-a081-49a71f9fe8e2

📥 Commits

Reviewing files that changed from the base of the PR and between d946a58 and 17efe8e.

📒 Files selected for processing (1)
  • crates/stdlib/src/_opcode.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/stdlib/src/_opcode.rs

📝 Walkthrough

Walkthrough

This PR adds opcode capability query helpers to the bytecode instruction layer. New predicates (has_const, has_free, has_jump, has_local, has_name) and block classification (is_block_push) are introduced on both Opcode and PseudoOpcode. These are exposed through Instruction/PseudoInstruction delegators and unified via a new either_real_pseudo! macro for AnyInstruction/AnyOpcode dispatch. Python stdlib _opcode helpers are refactored to delegate to these new abstractions.

Changes

Bytecode Instruction Opcode Helpers

Layer / File(s) Summary
Opcode Capability Predicates
crates/compiler-core/src/bytecode/instruction.rs
Opcode and PseudoOpcode types gain const methods has_const, has_free, has_jump, has_local, has_name, and is_block_push. Real opcodes return false for is_block_push; pseudo opcodes implement it for Setup* variants.
Instruction Delegators
crates/compiler-core/src/bytecode/instruction.rs
Instruction and PseudoInstruction gain is_block_push(&self) methods that delegate to their underlying Opcode and PseudoOpcode.
AnyInstruction/AnyOpcode Macro Dispatch
crates/compiler-core/src/bytecode/instruction.rs
New either_real_pseudo! macro unifies InstructionMetadata dispatch for AnyInstruction. AnyOpcode gains const query methods and is_block_push, dispatching to real or pseudo variants. AnyInstruction::is_block_push receiver changed from self to &self.
Python Stdlib Refactoring
crates/stdlib/src/_opcode.rs
Module imports change to use AnyOpcode. Functions has_const, has_name, has_jump, has_free, has_local, has_exc refactored from hardcoded matches! lists to uniform delegation via `try_from_i32(opcode).map_or(false,

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • RustPython/RustPython#7757: Modifies AnyInstruction opcode-related helpers and refactors instruction/opcode dispatch, directly overlapping.
  • RustPython/RustPython#7711: Changes _opcode pyfunctions' opcode-match logic; closely related to stdlib helper refactors here.
  • RustPython/RustPython#7585: Modifies bytecode instruction metadata and pseudo-instruction behavior across the same file(s).

Suggested reviewers

  • coolreader18
  • youknowone

Poem

🐇 I nibble bytes and hum a tune,

has_const, has_free beneath the moon;
either_real_pseudo! ties the knot,
Opcodes sing, pseudo-ops trot;
A little hop for cleaner thought.

🚥 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 'Add has_* methods to opcode enums' accurately summarizes the main change: introducing new opcode-flag query helpers (has_const, has_free, has_jump, has_local, has_name) to both Opcode and PseudoOpcode.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

coderabbitai[bot]

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@crates/compiler-core/src/bytecode/instruction.rs`:
- Around line 1051-1074: The has_jump() method is missing two specialized opcode
variants; update the matches! list inside pub const fn has_jump(self) -> bool
(in instruction.rs) to also include ForIterGen and SendGen so they map
consistently to the base opcodes ForIter and Send (similar to the existing
ForIterList/ForIterRange/ForIterTuple and JumpBackwardJit/JumpBackwardNoJit
entries); simply add ForIterGen and SendGen to the list of matched variants in
has_jump().
- Around line 1042-1049: The has_free() method currently matches DeleteDeref,
LoadFromDictOrDeref, MakeCell, and StoreDeref; update it to match CPython's
HAS_FREE_FLAG by including LoadDeref and removing LoadFromDictOrDeref and
MakeCell. Edit the matches expression inside the pub const fn has_free(&self) to
only include Self::DeleteDeref | Self::LoadDeref | Self::StoreDeref, ensuring
the function name has_free and the opcode variants DeleteDeref, LoadDeref,
StoreDeref are the referenced symbols to change.
🪄 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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2a215c26-cd0a-4398-8445-1384ce64a21e

📥 Commits

Reviewing files that changed from the base of the PR and between 108461f and d946a58.

📒 Files selected for processing (2)
  • crates/compiler-core/src/bytecode/instruction.rs
  • crates/stdlib/src/_opcode.rs

Comment on lines +1042 to +1049

/// Does this opcode have 'HAS_FREE_FLAG' set.
#[must_use]
pub const fn has_free(self) -> bool {
matches!(
self,
Self::DeleteDeref | Self::LoadFromDictOrDeref | Self::MakeCell | Self::StoreDeref
)
}

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

CPython 3.14 HAS_FREE_FLAG opcode classification which opcodes should be included

💡 Result:

For CPython 3.14, the opcodes marked with the internal opcode-metadata flag HAS_FREE_FLAG are the following: 1) DELETE_DEREF 2) LOAD_CLOSURE 3) LOAD_DEREF 4) STORE_DEREF Evidence: CPython defines HAS_FREE_FLAG in its internal opcode metadata and associates it with the above opcode entries (e.g., DELETE_DEREF is shown with HAS_ARG_FLAG | HAS_FREE_FLAG... in the opcode metadata table) [1]. Related context: the disassembler’s public dis.hasfree set corresponds to “bytecodes that access a free (closure) variable” [2][3], i.e., the closure/free-variable access opcodes above.

Citations:


🏁 Script executed:

cd crates/compiler-core/src/bytecode && grep -n "enum Opcode\|pub const fn has_free\|LoadDeref\|LoadFromDictOrDeref\|MakeCell\|CopyFreeVars" instruction.rs | head -50

Repository: RustPython/RustPython

Length of output: 688


🏁 Script executed:

# Get the specific lines mentioned in the review
sed -n '1042,1049p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 320


🏁 Script executed:

# Check for LoadDeref definition
sed -n '280,300p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 743


🏁 Script executed:

# Check for CopyFreeVars definition
sed -n '220,230p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 359


🏁 Script executed:

# Search for all occurrences of these opcodes in the file
rg -n "LoadDeref|LoadFromDictOrDeref|MakeCell|CopyFreeVars|DeleteDeref|StoreDeref" crates/compiler-core/src/bytecode/instruction.rs | head -30

Repository: RustPython/RustPython

Length of output: 677


🏁 Script executed:

sed -n '1080,1090p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 475


🏁 Script executed:

# Check for docstrings/comments on LoadFromDictOrDeref and MakeCell
sed -n '308,315p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 367


🏁 Script executed:

sed -n '329,340p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 405


🏁 Script executed:

# Search for any usage or discussion of has_free
rg -B2 -A2 "has_free" crates/compiler-core/src/bytecode/instruction.rs | head -40

Repository: RustPython/RustPython

Length of output: 435


🏁 Script executed:

# Get full context around line 1044 to understand the structure
sed -n '1040,1055p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 473


🏁 Script executed:

# Let's also check if there are any comments or documentation about why these opcodes are in has_free
rg -B5 "LoadFromDictOrDeref|MakeCell" crates/compiler-core/src/bytecode/instruction.rs | head -50

Repository: RustPython/RustPython

Length of output: 1358


Add LoadDeref to has_free implementation and remove non-standard opcodes.

The has_free method is missing LoadDeref (line 289), which should be included per CPython's HAS_FREE_FLAG classification. CPython marks DELETE_DEREF, LOAD_DEREF, and STORE_DEREF as having this flag; rustpython correctly includes the first and third but omits the second.

Additionally, LoadFromDictOrDeref and MakeCell should not be in this implementation, as they do not have HAS_FREE_FLAG in CPython's opcode metadata.

Update the matches expression to:

Self::DeleteDeref | Self::LoadDeref | Self::StoreDeref
🤖 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 `@crates/compiler-core/src/bytecode/instruction.rs` around lines 1042 - 1049,
The has_free() method currently matches DeleteDeref, LoadFromDictOrDeref,
MakeCell, and StoreDeref; update it to match CPython's HAS_FREE_FLAG by
including LoadDeref and removing LoadFromDictOrDeref and MakeCell. Edit the
matches expression inside the pub const fn has_free(&self) to only include
Self::DeleteDeref | Self::LoadDeref | Self::StoreDeref, ensuring the function
name has_free and the opcode variants DeleteDeref, LoadDeref, StoreDeref are the
referenced symbols to change.

Comment on lines +1051 to +1074

/// Does this opcode have 'HAS_JUMP_FLAG' set.
#[must_use]
pub const fn has_jump(self) -> bool {
matches!(
self,
Self::EndAsyncFor
| Self::ForIter
| Self::JumpBackward
| Self::JumpBackwardNoInterrupt
| Self::JumpForward
| Self::PopJumpIfFalse
| Self::PopJumpIfNone
| Self::PopJumpIfNotNone
| Self::PopJumpIfTrue
| Self::Send
| Self::ForIterList
| Self::ForIterRange
| Self::ForIterTuple
| Self::JumpBackwardJit
| Self::JumpBackwardNoJit
| Self::InstrumentedForIter
| Self::InstrumentedEndAsyncFor
)
}

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all ForIter* and Send* opcode definitions to verify completeness
echo "=== ForIter variants ==="
rg -n "^\s+(ForIter\w*)\s*=" crates/compiler-core/src/bytecode/instruction.rs

echo -e "\n=== Send variants ==="
rg -n "^\s+(Send\w*)\s*=" crates/compiler-core/src/bytecode/instruction.rs

echo -e "\n=== Opcodes in has_jump ==="
rg -A50 "pub const fn has_jump\(self\) -> bool" crates/compiler-core/src/bytecode/instruction.rs | rg "Self::" | head -20

Repository: RustPython/RustPython

Length of output: 1150


🏁 Script executed:

# Check CPython's opcode definitions to see if ForIterGen and SendGen have HAS_JUMP_FLAG
curl -s https://raw.githubusercontent.com/python/cpython/main/Include/opcode.h | grep -A 2 -B 2 "FOR_ITER_GEN\|SEND_GEN" | head -30

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Also check if there's any documentation in RustPython about this
rg -B5 -A5 "ForIterGen|SendGen" crates/compiler-core/src/bytecode/instruction.rs | head -40

Repository: RustPython/RustPython

Length of output: 2090


🏁 Script executed:

# Check if there's a pattern or comment explaining why Gen variants are excluded
rg -B10 "pub const fn has_jump" crates/compiler-core/src/bytecode/instruction.rs | head -20

Repository: RustPython/RustPython

Length of output: 519


🏁 Script executed:

# Let's check CPython's Python/opcode_ids.h directly for the actual opcode definitions
python3 <<'EOF'
import urllib.request
import json

# Fetch CPython's opcode definitions
url = "https://raw.githubusercontent.com/python/cpython/main/Python/opcode_ids.h"
try:
    with urllib.request.urlopen(url, timeout=5) as response:
        content = response.read().decode('utf-8')
        # Search for FOR_ITER_GEN and SEND_GEN definitions
        lines = content.split('\n')
        for i, line in enumerate(lines):
            if 'FOR_ITER_GEN' in line or 'SEND_GEN' in line:
                # Print context around the definition
                start = max(0, i - 2)
                end = min(len(lines), i + 3)
                for j in range(start, end):
                    print(f"{j}: {lines[j]}")
                print("---")
except Exception as e:
    print(f"Error fetching CPython opcode definitions: {e}")

# Also check core/ceval.c for HAS_JUMP_FLAG definitions
url2 = "https://raw.githubusercontent.com/python/cpython/main/Python/ceval.c"
try:
    with urllib.request.urlopen(url2, timeout=5) as response:
        content = response.read().decode('utf-8')
        lines = content.split('\n')
        for i, line in enumerate(lines):
            if 'FOR_ITER_GEN' in line or 'SEND_GEN' in line:
                # Print context
                start = max(0, i - 2)
                end = min(len(lines), i + 3)
                for j in range(start, end):
                    print(f"{j}: {lines[j]}")
                print("---")
except Exception as e:
    print(f"Error fetching CPython ceval.c: {e}")
EOF

Repository: RustPython/RustPython

Length of output: 396


🏁 Script executed:

# Let's check if there are any comments in the RustPython code about jump flags or specialized variants
rg -B10 -A10 "has_jump" crates/compiler-core/src/bytecode/instruction.rs | grep -A30 "pub const fn has_jump"

Repository: RustPython/RustPython

Length of output: 1418


🏁 Script executed:

# Check if there's any test or documentation about which opcodes should have jump flag
fd instruction tests test | head -5

Repository: RustPython/RustPython

Length of output: 209


🏁 Script executed:

# Look for any git history or comments about ForIterGen/SendGen jump behavior
rg "ForIterGen|SendGen" crates/compiler-core/src -B3 -A3

Repository: RustPython/RustPython

Length of output: 4344


Include ForIterGen and SendGen in the has_jump method.

The implementation includes specialized variants like ForIterList, ForIterRange, ForIterTuple, JumpBackwardJit, and JumpBackwardNoJit, but omits:

  • ForIterGen (line 448) — maps to base opcode ForIter
  • SendGen (line 197) — maps to base opcode Send

Since their base opcodes (ForIter and Send) are included, and other specialized variants are also present, these should be added for consistency.

🤖 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 `@crates/compiler-core/src/bytecode/instruction.rs` around lines 1051 - 1074,
The has_jump() method is missing two specialized opcode variants; update the
matches! list inside pub const fn has_jump(self) -> bool (in instruction.rs) to
also include ForIterGen and SendGen so they map consistently to the base opcodes
ForIter and Send (similar to the existing ForIterList/ForIterRange/ForIterTuple
and JumpBackwardJit/JumpBackwardNoJit entries); simply add ForIterGen and
SendGen to the list of matched variants in has_jump().

@ShaharNaveh