X Tutup
Skip to content

Compiler parity: docstring dedent, StopIteration wrapper, constant folding#7530

Open
youknowone wants to merge 2 commits intoRustPython:mainfrom
youknowone:bytecode-parity
Open

Compiler parity: docstring dedent, StopIteration wrapper, constant folding#7530
youknowone wants to merge 2 commits intoRustPython:mainfrom
youknowone:bytecode-parity

Conversation

@youknowone
Copy link
Copy Markdown
Member

@youknowone youknowone commented Mar 28, 2026

  • Fix clean_doc to skip first line in margin calculation (match CPython)
  • Add PEP 479 StopIteration handler to generator/coroutine bodies (SETUP_CLEANUP + CALL_INTRINSIC_1(StopIterationError) + RERAISE)
  • Remove yield-from/await -1 depth compensation in RESUME DEPTH1
  • Add constant condition elimination for if/while (if False, while True)
  • Add POP_JUMP_IF_NONE/NOT_NONE for x is None patterns
  • Add binary op constant folding (*, **, +, -, <<, >>, etc.)
  • Add expr_constant() for compile-time boolean evaluation

Summary by CodeRabbit

  • Refactor
    • Binary operations with compile-time constant operands are now evaluated and pre-computed
    • If and while statements with constant boolean conditions now eliminate unreachable code paths, reducing bytecode size
    • Generator and coroutine functions improved with proper StopIteration exception handling
    • Specialized bytecode instructions automatically generated for None-comparison operations

…lding

- Fix clean_doc to skip first line in margin calculation (match CPython)
- Add PEP 479 StopIteration handler to generator/coroutine bodies
  (SETUP_CLEANUP + CALL_INTRINSIC_1(StopIterationError) + RERAISE)
- Remove yield-from/await -1 depth compensation in RESUME DEPTH1
- Add constant condition elimination for if/while (if False, while True)
- Add POP_JUMP_IF_NONE/NOT_NONE for `x is None` patterns
- Add binary op constant folding (*, **, +, -, <<, >>, etc.)
- Add expr_constant() for compile-time boolean evaluation
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

This pull request introduces compile-time constant detection and folding optimizations across the compiler pipeline. Changes include dead-code elimination for conditionals with constant-boolean tests, a new binary operation constant-folding pass in IR, improved exception handling for generators/coroutines, and specialized jump instructions for None comparisons.

Changes

Cohort / File(s) Summary
Constant Folding and Dead Code Elimination
crates/codegen/src/compile.rs
Added do_not_emit_bytecode gating mechanism for walk-only AST traversal; refactored if statement compilation to detect compile-time boolean constants and skip bytecode generation for dead branches; enhanced while loop constant-elimination handling; added expr_constant() and emit_nop() helpers; modified low-level emission to early-return when bytecode gating is active.
IR Optimization and Exception Handling
crates/codegen/src/ir.rs
Implemented new fold_binop_constants() pass that evaluates binary operations at compile time for supported constant types (integers, floats, strings) with guards for division/mod-by-zero and float finiteness; updated finalize_code() to run the folding pass and added NOP-removal; modified label_exception_targets() to unconditionally set last_yield_except_depth on YIELD_VALUE; added helper methods get_const_value_from(), eval_binop(), and const_too_big().
Generator/Coroutine Exception Handling
crates/codegen/src/compile.rs
Wrapped generator/coroutine function body compilation with StopIteration stop handler (PEP 479) emitting SetupCleanup, StopIterationError, Reraise and corresponding cleanup instructions.
Jump Instruction Enhancement
crates/codegen/src/compile.rs
Enhanced compile_jump_if() to emit specialized PopJumpIfNone/PopJumpIfNotNone instructions for x is None/x is not None comparisons instead of generic fallback compilation.
Documentation String Processing
crates/codegen/src/compile.rs
Modified clean_doc() logic to recalculate margin based on non-first lines with non-space characters; added early-return optimization for first lines with no leading spaces; altered line slicing/trimming based on computed margin.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested reviewers

  • ShaharNaveh

Poem

🐰 Constants fold and dead code falls,
Branches trim where false truth calls,
Bytecode gates keep walkers true,
Optimized flows—the compiler's debut! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 three main changes: docstring dedent fix (clean_doc), StopIteration wrapper (PEP 479), and constant folding (binop optimization). It covers the primary objectives without being overly broad.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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.

Walk dead branches to consume sub_tables without emitting bytecode,
matching CPython's BEGIN_DO_NOT_EMIT_BYTECODE pattern. Fixes freeze
compilation errors for files containing scope-creating definitions
inside dead branches (e.g. if False: def foo(): ...).
@youknowone youknowone marked this pull request as ready for review March 28, 2026 17:13
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@crates/codegen/src/compile.rs`:
- Around line 8737-8740: The flag do_not_emit_bytecode only prevents emitting
instructions in _emit(), but helpers like emit_load_const call arg_constant()
(which interns constants) before _emit(), leaking metadata; modify emit helpers
(e.g., emit_load_const) and/or arg_constant() to check do_not_emit_bytecode and
skip interning when do_not_emit_bytecode > 0 so no constants/names are added
while bytecode emission is disabled, ensuring _emit, emit_load_const,
arg_constant (and any other helpers that mutate constant/name tables) return
early or no-op metadata mutations when the flag is set.
- Around line 3871-3885: The PEP 479 StopIteration wrapper currently wraps only
compile_function_body() and omits generator/coroutine code objects created in
compile_comprehension(), causing StopIteration to escape; modify
compile_comprehension() to mirror the same logic used where is_gen is computed
(create handler_block via new_block(), emit PseudoInstruction::SetupCleanup {
delta: handler_block }, and call push_fblock(FBlockType::StopIteration,
handler_block, handler_block) when the comprehension builds a
generator/coroutine) or refactor the wrapper into a small helper used by both
compile_function_body() and compile_comprehension(); also apply the same fix to
the duplicate site referenced around the other block (the section analogous to
3917-3929).
- Around line 8943-8963: The current expr_constant implementation incorrectly
returns Some(!elts.is_empty()) for ast::Expr::Tuple, which folds away element
evaluation (and side effects) for non-empty tuples; change this to only return
Some(false) for an empty tuple and for non-empty tuples return Some(true) only
if every element is a compile-time constant (call expr_constant recursively on
each element and if any returns None, return None), otherwise return None so
elements with possible side effects are preserved; update the ast::Expr::Tuple
branch in expr_constant accordingly (check elts, recurse into each elt, and only
yield Some(true) when all elts are constant).
- Around line 5217-5224: The current early-return for constant == Some(true)
skips compiling the elif/else tests, which bypasses async-await validation and
leaves nested scope sub_tables unconsumed; instead, keep the no-emission mode
but still compile each clause's test expressions (and any nested
lambdas/comprehensions) so errors and scope consumption occur. Concretely,
inside the constant == Some(true) branch where you call self.emit_nop() and
self.compile_statements(body) and adjust self.do_not_emit_bytecode, also iterate
over elif_else_clauses and call the same compiler path that validates/compiles
their test expressions (e.g. use the existing expression-compile routine used
for condition checks) with do_not_emit_bytecode incremented so no bytecode is
emitted but await-checks and sub_table consumption run; do not compile clause
bodies, only their tests/scopes. Ensure you reference self.compile_statements,
elif_else_clauses, and self.do_not_emit_bytecode when making the change.
- Around line 8992-8994: The early return when self.do_not_emit_bytecode > 0
skips loop/except* validation and lets dead branches (e.g., `if False: break`)
pass; instead, preserve the validation logic and only skip emission. Change the
code around the `if self.do_not_emit_bytecode > 0 { return Ok(()); }` guard so
that you still execute the loop/except* checks that follow (the
dead-branch/`break`/`continue` and `except*` validation code) but bypass only
the bytecode emission paths—e.g., wrap emission-specific calls with a check `if
self.do_not_emit_bytecode == 0 { ... }` or move the `return` to after the
validation block; keep references to `self.do_not_emit_bytecode`, the
loop/except* validation code paths, and emission sites to locate and update the
necessary conditionals.

In `@crates/codegen/src/ir.rs`:
- Around line 193-196: The binop-constant folding pass should run again after
UNARY_NEGATIVE has been folded into LOAD_CONST so expressions like `-1 + 2` and
`2 * -3` are caught; update the sequence around fold_binop_constants(),
fold_unary_negative(), and remove_nops() to call fold_binop_constants() once
more immediately after fold_unary_negative() (keeping the surrounding
remove_nops() calls as needed) so binary constant folding re-runs on the newly
produced constants.
- Around line 787-798: The BinOp::FloorDivide and BinOp::Remainder folding
currently uses Rust’s / and % which produce non-Python semantics for BigInt and
floats; change the BigInt cases to call malachite_bigint::BigInt methods
.div_floor() and .mod_floor() (used by the runtime) instead of l / r and l % r,
and for the float remainder branch replicate the Python floor-mod behavior by
applying the sign-correction logic from float_ops::mod_() (or the divmod helper)
so result sign follows the divisor (i.e., matches Python’s // and % semantics
for negative values).
🪄 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: e2c39f50-5b6c-4884-a8a3-1bc68e3aad8f

📥 Commits

Reviewing files that changed from the base of the PR and between 902985d and 15e2b2e.

⛔ Files ignored due to path filters (1)
  • crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • crates/codegen/src/compile.rs
  • crates/codegen/src/ir.rs

Comment on lines +3871 to +3885
// PEP 479: Wrap generator/coroutine body with StopIteration handler
let is_gen = is_async || self.current_symbol_table().is_generator;
let stop_iteration_block = if is_gen {
let handler_block = self.new_block();
emit!(
self,
PseudoInstruction::SetupCleanup {
delta: handler_block
}
);
self.push_fblock(FBlockType::StopIteration, handler_block, handler_block)?;
Some(handler_block)
} else {
None
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

PEP 479 still bypasses comprehension-generated code objects.

This wrapper only covers compile_function_body(). Generator expressions and non-inlined async comprehensions build their own generator/coroutine code objects in compile_comprehension() around Line 8231, so StopIteration escaping from those bodies will still miss StopIterationError.

Also applies to: 3917-3929

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` around lines 3871 - 3885, The PEP 479
StopIteration wrapper currently wraps only compile_function_body() and omits
generator/coroutine code objects created in compile_comprehension(), causing
StopIteration to escape; modify compile_comprehension() to mirror the same logic
used where is_gen is computed (create handler_block via new_block(), emit
PseudoInstruction::SetupCleanup { delta: handler_block }, and call
push_fblock(FBlockType::StopIteration, handler_block, handler_block) when the
comprehension builds a generator/coroutine) or refactor the wrapper into a small
helper used by both compile_function_body() and compile_comprehension(); also
apply the same fix to the duplicate site referenced around the other block (the
section analogous to 3917-3929).

Comment on lines +5217 to +5224
if constant == Some(true) {
self.emit_nop();
self.compile_statements(body)?;
self.do_not_emit_bytecode += 1;
for clause in elif_else_clauses {
self.compile_statements(&clause.body)?;
}
self.do_not_emit_bytecode -= 1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Dead elif tests still need validation and scope consumption.

The constant-true path skips those tests entirely. if True: pass elif await x: ... stops triggering the await-outside-async error, and lambdas/comprehensions inside the skipped tests leave their sub_tables unconsumed for the next live nested scope.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` around lines 5217 - 5224, The current
early-return for constant == Some(true) skips compiling the elif/else tests,
which bypasses async-await validation and leaves nested scope sub_tables
unconsumed; instead, keep the no-emission mode but still compile each clause's
test expressions (and any nested lambdas/comprehensions) so errors and scope
consumption occur. Concretely, inside the constant == Some(true) branch where
you call self.emit_nop() and self.compile_statements(body) and adjust
self.do_not_emit_bytecode, also iterate over elif_else_clauses and call the same
compiler path that validates/compiles their test expressions (e.g. use the
existing expression-compile routine used for condition checks) with
do_not_emit_bytecode incremented so no bytecode is emitted but await-checks and
sub_table consumption run; do not compile clause bodies, only their
tests/scopes. Ensure you reference self.compile_statements, elif_else_clauses,
and self.do_not_emit_bytecode when making the change.

Comment on lines 8737 to +8740
fn _emit<I: Into<AnyInstruction>>(&mut self, instr: I, arg: OpArg, target: BlockIdx) {
if self.do_not_emit_bytecode > 0 {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

do_not_emit_bytecode still leaks dead metadata into the live code object.

Only _emit() is gated here. Helpers like emit_load_const() still intern via arg_constant() at Line 8847 before _emit() runs, so dead branches can still populate the constant/name tables even when no bytecode is emitted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` around lines 8737 - 8740, The flag
do_not_emit_bytecode only prevents emitting instructions in _emit(), but helpers
like emit_load_const call arg_constant() (which interns constants) before
_emit(), leaking metadata; modify emit helpers (e.g., emit_load_const) and/or
arg_constant() to check do_not_emit_bytecode and skip interning when
do_not_emit_bytecode > 0 so no constants/names are added while bytecode emission
is disabled, ensuring _emit, emit_load_const, arg_constant (and any other
helpers that mutate constant/name tables) return early or no-op metadata
mutations when the flag is set.

Comment on lines +8943 to +8963
fn expr_constant(expr: &ast::Expr) -> Option<bool> {
match expr {
ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => Some(*value),
ast::Expr::NoneLiteral(_) => Some(false),
ast::Expr::EllipsisLiteral(_) => Some(true),
ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value {
ast::Number::Int(i) => {
let n: i64 = i.as_i64().unwrap_or(1);
Some(n != 0)
}
ast::Number::Float(f) => Some(*f != 0.0),
ast::Number::Complex { real, imag, .. } => Some(*real != 0.0 || *imag != 0.0),
},
ast::Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
Some(!value.to_str().is_empty())
}
ast::Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
Some(value.bytes().next().is_some())
}
ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => Some(!elts.is_empty()),
_ => None,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Non-empty tuples are not unconditional compile-time true.

Some(!elts.is_empty()) folds if (side_effect(),): to an unconditional branch and drops the tuple element evaluation. This needs a recursive side-effect-free constant check, or None for non-empty tuples whose elements are not all constants.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` around lines 8943 - 8963, The current
expr_constant implementation incorrectly returns Some(!elts.is_empty()) for
ast::Expr::Tuple, which folds away element evaluation (and side effects) for
non-empty tuples; change this to only return Some(false) for an empty tuple and
for non-empty tuples return Some(true) only if every element is a compile-time
constant (call expr_constant recursively on each element and if any returns
None, return None), otherwise return None so elements with possible side effects
are preserved; update the ast::Expr::Tuple branch in expr_constant accordingly
(check elts, recurse into each elt, and only yield Some(true) when all elts are
constant).

Comment on lines +8992 to +8994
if self.do_not_emit_bytecode > 0 {
return Ok(());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Keep break/continue validation even when bytecode is suppressed.

This returns before the loop and except* checks below, so dead branches like if False: break no longer raise SyntaxError. Only the cleanup/jump emission should be skipped here.

🔧 Minimal fix
-        if self.do_not_emit_bytecode > 0 {
-            return Ok(());
-        }
-
         // unwind_fblock_stack
         // We need to unwind fblocks and compile cleanup code. For FinallyTry blocks,
         // we need to compile the finally body inline, but we must temporarily pop
@@
         let Some(loop_idx) = loop_idx else {
             if is_break {
                 return Err(self.error_ranged(CodegenErrorType::InvalidBreak, range));
             } else {
                 return Err(self.error_ranged(CodegenErrorType::InvalidContinue, range));
             }
         };
+
+        if self.do_not_emit_bytecode > 0 {
+            return Ok(());
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` around lines 8992 - 8994, The early return
when self.do_not_emit_bytecode > 0 skips loop/except* validation and lets dead
branches (e.g., `if False: break`) pass; instead, preserve the validation logic
and only skip emission. Change the code around the `if self.do_not_emit_bytecode
> 0 { return Ok(()); }` guard so that you still execute the loop/except* checks
that follow (the dead-branch/`break`/`continue` and `except*` validation code)
but bypass only the bytecode emission paths—e.g., wrap emission-specific calls
with a check `if self.do_not_emit_bytecode == 0 { ... }` or move the `return` to
after the validation block; keep references to `self.do_not_emit_bytecode`, the
loop/except* validation code paths, and emission sites to locate and update the
necessary conditionals.

Comment on lines +193 to 196
self.fold_binop_constants();
self.remove_nops();
self.fold_unary_negative();
self.remove_nops(); // remove NOPs from unary folding so tuple/list/set see contiguous LOADs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Run binary folding once more after unary-negative folding.

With the current sequence, -1 + 2 and 2 * -3 never reach fold_binop_constants(): the binop pass runs before fold_unary_negative(), and there is no second binop pass once UNARY_NEGATIVE has collapsed into LOAD_CONST. Re-running the binop fold after Line 195 closes that parity hole.

Suggested change
         self.fold_binop_constants();
         self.remove_nops();
         self.fold_unary_negative();
         self.remove_nops(); // remove NOPs from unary folding so tuple/list/set see contiguous LOADs
+        self.fold_binop_constants();
+        self.remove_nops();
         self.fold_tuple_constants();
📝 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
self.fold_binop_constants();
self.remove_nops();
self.fold_unary_negative();
self.remove_nops(); // remove NOPs from unary folding so tuple/list/set see contiguous LOADs
self.fold_binop_constants();
self.remove_nops();
self.fold_unary_negative();
self.remove_nops(); // remove NOPs from unary folding so tuple/list/set see contiguous LOADs
self.fold_binop_constants();
self.remove_nops();
self.fold_tuple_constants();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/ir.rs` around lines 193 - 196, The binop-constant folding
pass should run again after UNARY_NEGATIVE has been folded into LOAD_CONST so
expressions like `-1 + 2` and `2 * -3` are caught; update the sequence around
fold_binop_constants(), fold_unary_negative(), and remove_nops() to call
fold_binop_constants() once more immediately after fold_unary_negative()
(keeping the surrounding remove_nops() calls as needed) so binary constant
folding re-runs on the newly produced constants.

Comment on lines +787 to +798
BinOp::FloorDivide => {
if r.is_zero() {
return None;
}
l / r
}
BinOp::Remainder => {
if r.is_zero() {
return None;
}
l % r
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

For negative operands, what are the semantics of malachite_bigint::BigInt /versus num_integer::Integer::div_floor/mod_floor, what are Rustf64 % f64semantics, and what do Python//and% require?

💡 Result:

For negative operands: malachite_bigint::BigInt / : No explicit documentation found on division semantics for negative numbers in official docs.rs pages. Malachite provides multiple division traits like FloorDiv, CeilingDiv, DivRound (with rounding modes), but the / operator semantics are not specified in searched sources. As a drop-in replacement for num-bigint, it may follow similar conventions, but unconfirmed. Likely towards zero or floored based on GMP inspiration, but evidence insufficient (low confidence). num_integer::Integer::div_floor/mod_floor: Uses floored division (round quotient towards -∞). Examples: 8/3=2 r=2; 8/-3=-3 r=-1; -8/3=-3 r=1; -8/-3=2 r=-2. Satisfies q*d + r = n with sign(r) matching d. Rust f64 % f64: The % operator is truncating remainder (towards zero division), sign follows dividend. For Euclidean (always non-negative remainder), use rem_euclid (pairs with div_euclid, 0 <= r < |d| approximately). Python // and % : // is floor division (towards -∞); % is remainder with sign of divisor, |r| < |d|, satisfying n = (n//d)*d + (n%d). Examples: (-8)//3=-3, (-8)%3=1; 8//-3=-3, 8%-3=-1.

Citations:


🏁 Script executed:

# First, let's see the file size and check the lines mentioned
wc -l crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 94


🏁 Script executed:

# Now read the specific lines mentioned (787-798 and 841-846) with context
sed -n '780,850p' crates/codegen/src/ir.rs | cat -n

Repository: RustPython/RustPython

Length of output: 3340


🏁 Script executed:

# Check imports and early context to understand what types are being used
head -100 crates/codegen/src/ir.rs | cat -n

Repository: RustPython/RustPython

Length of output: 3677


🏁 Script executed:

# Search for malachite_bigint usage and available division methods
rg "div_floor|mod_floor|FloorDiv|floor_div" crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 152


🏁 Script executed:

# Check if there are any utility functions for Python-compatible division
rg "def.*floor.*div|fn.*floor.*div" crates/

Repository: RustPython/RustPython

Length of output: 723


🏁 Script executed:

# Look at the full context around the problem lines
sed -n '770,860p' crates/codegen/src/ir.rs | cat -n

Repository: RustPython/RustPython

Length of output: 4130


🏁 Script executed:

# Check what malachite_bigint methods are available
web_search
Malachite BigInt division operators - does / operator use floor division or truncating division semantics?

Repository: RustPython/RustPython

Length of output: 163


🏁 Script executed:

# Check if there's a common float_ops or int_ops module with Python-compatible helpers
cat crates/common/src/float_ops.rs | head -50

Repository: RustPython/RustPython

Length of output: 1688


🏁 Script executed:

# Search for how BigInt division is actually implemented in runtime
cat crates/vm/src/builtins/int.rs | grep -A 20 "inner_floordiv"

Repository: RustPython/RustPython

Length of output: 1497


🏁 Script executed:

# Check if malachite_bigint::BigInt has a / operator and what it does
rg "impl.*Div.*for.*BigInt" --type rust

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Look at what remainder methods exist for BigInt
rg "mod_floor|rem|Rem" crates/vm/src/builtins/int.rs -A 3 -B 1

Repository: RustPython/RustPython

Length of output: 995


🏁 Script executed:

# Check if there are Python-compatible modulo implementations for f64
cat crates/common/src/float_ops.rs | grep -A 10 "floordiv\|modulo\|remainder"

Repository: RustPython/RustPython

Length of output: 305


🏁 Script executed:

# Search for actual usage of % operator with BigInt to understand current behavior
rg "BigInt.*%|% r" crates/vm/src/builtins/ -A 2 -B 2

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Check the full divmod function for f64 to understand Python-compatible modulo
cat crates/common/src/float_ops.rs | grep -A 20 "pub fn divmod"

Repository: RustPython/RustPython

Length of output: 607


🏁 Script executed:

# Check what modulo operations are available in float_ops
cat crates/common/src/float_ops.rs

Repository: RustPython/RustPython

Length of output: 7815


🏁 Script executed:

# Verify if there's a modulo helper for f64 that computes Python-style remainder
rg "def.*mod|fn.*mod" crates/common/src/float_ops.rs

Repository: RustPython/RustPython

Length of output: 167


🏁 Script executed:

# See how runtime float remainder is implemented
rg "inner_mod\|BinOp::Remainder" crates/vm/src/builtins/float.rs -A 10 -B 2

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Check if malachite_bigint::BigInt actually has div_floor and mod_floor methods
rg "div_floor\|mod_floor" crates/ -t rust | head -20

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Verify the exact signature and availability of these methods
rg "\.div_floor\(|\.mod_floor\(" crates/vm/src/builtins/int.rs -B 2 -A 2

Repository: RustPython/RustPython

Length of output: 341


🏁 Script executed:

# Check if there's a way to import float_ops helpers in the codegen file
head -30 crates/codegen/src/ir.rs | grep "use\|import"

Repository: RustPython/RustPython

Length of output: 267


Use Python-compatible // and % operators instead of raw Rust operators.

These arms currently fold with Rust-native division/remainder behavior. For BigInt operations (lines 787–798), use .div_floor() and .mod_floor() instead of / and %—these methods are available on malachite_bigint::BigInt and are already used by the runtime (crates/vm/src/builtins/int.rs). For float modulo (line 841–846), the raw % operator applies truncating remainder with the dividend's sign, but Python requires floor modulo with the divisor's sign. Apply the sign-correction logic from float_ops::mod_() (in crates/common/src/float_ops.rs) or replicate the divmod helper's approach.

Negative cases like -3 // 2, -3 % 2, or -1.0 % 3.0 will fold to wrong constants without these fixes.

Also applies to: 841–846

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/ir.rs` around lines 787 - 798, The BinOp::FloorDivide and
BinOp::Remainder folding currently uses Rust’s / and % which produce non-Python
semantics for BigInt and floats; change the BigInt cases to call
malachite_bigint::BigInt methods .div_floor() and .mod_floor() (used by the
runtime) instead of l / r and l % r, and for the float remainder branch
replicate the Python floor-mod behavior by applying the sign-correction logic
from float_ops::mod_() (or the divmod helper) so result sign follows the divisor
(i.e., matches Python’s // and % semantics for negative values).

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

X Tutup