From 33ef1b905727cd32e89b9473ad9ae7e9a89c141c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 16 Mar 2026 11:37:47 +0900 Subject: [PATCH] Implement code object __eq__/__hash__ - Add Comparable and Hashable traits for PyCode - Compare by name, args, flags, bytecode, consts, names, vars, linetable - Hash by tuple of key attributes matching CPython's code_hash - Remove unused custom_ops slice in Instruction::try_from - Add co_lnotab intentional non-implementation comment --- .../compiler-core/src/bytecode/instruction.rs | 4 - crates/vm/src/builtins/code.rs | 77 ++++++++++++++++++- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index ea5fe1818..ed5cfb724 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -446,13 +446,9 @@ impl TryFrom for Instruction { let instrumented_start = u8::from(Self::InstrumentedEndFor); let instrumented_end = u8::from(Self::InstrumentedLine); - // No RustPython-only opcodes anymore - all opcodes match CPython 3.14 - let custom_ops: &[u8] = &[]; - if (cpython_start..=cpython_end).contains(&value) || value == resume_id || value == enter_executor_id - || custom_ops.contains(&value) || (specialized_start..=specialized_end).contains(&value) || (instrumented_start..=instrumented_end).contains(&value) { diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index a7ef4c08a..7a2c3cf58 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -10,7 +10,7 @@ use crate::{ convert::{ToPyException, ToPyObject}, frozen, function::OptionalArg, - types::{Constructor, Representable}, + types::{Comparable, Constructor, Hashable, Representable}, }; use alloc::fmt; use core::{ @@ -447,6 +447,75 @@ impl Representable for PyCode { } } +impl Comparable for PyCode { + fn cmp( + zelf: &Py, + other: &PyObject, + op: crate::types::PyComparisonOp, + vm: &VirtualMachine, + ) -> PyResult { + op.eq_only(|| { + let other = class_or_notimplemented!(Self, other); + let a = &zelf.code; + let b = &other.code; + let eq = a.obj_name == b.obj_name + && a.arg_count == b.arg_count + && a.posonlyarg_count == b.posonlyarg_count + && a.kwonlyarg_count == b.kwonlyarg_count + && a.flags == b.flags + && a.first_line_number == b.first_line_number + && a.instructions.original_bytes() == b.instructions.original_bytes() + && a.linetable == b.linetable + && a.exceptiontable == b.exceptiontable + && a.names == b.names + && a.varnames == b.varnames + && a.freevars == b.freevars + && a.cellvars == b.cellvars + && { + let a_consts: Vec<_> = a.constants.iter().map(|c| c.0.clone()).collect(); + let b_consts: Vec<_> = b.constants.iter().map(|c| c.0.clone()).collect(); + if a_consts.len() != b_consts.len() { + false + } else { + let mut eq = true; + for (ac, bc) in a_consts.iter().zip(b_consts.iter()) { + if !vm.bool_eq(ac, bc)? { + eq = false; + break; + } + } + eq + } + }; + Ok(eq.into()) + }) + } +} + +impl Hashable for PyCode { + fn hash(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let code = &zelf.code; + // Hash a tuple of key attributes, matching CPython's code_hash + let tuple = vm.ctx.new_tuple(vec![ + vm.ctx.new_str(code.obj_name.as_str()).into(), + vm.ctx.new_int(code.arg_count).into(), + vm.ctx.new_int(code.posonlyarg_count).into(), + vm.ctx.new_int(code.kwonlyarg_count).into(), + vm.ctx.new_int(code.varnames.len()).into(), + vm.ctx.new_int(code.flags.bits()).into(), + vm.ctx + .new_int(code.first_line_number.map_or(0, |n| n.get()) as i64) + .into(), + vm.ctx.new_bytes(code.instructions.original_bytes()).into(), + { + let consts: Vec<_> = code.constants.iter().map(|c| c.0.clone()).collect(); + vm.ctx.new_tuple(consts).into() + }, + ]); + tuple.as_object().hash(vm) + } +} + // Arguments for code object constructor #[derive(FromArgs)] pub struct PyCodeNewArgs { @@ -595,7 +664,7 @@ impl Constructor for PyCode { } } -#[pyclass(with(Representable, Constructor), flags(HAS_WEAKREF))] +#[pyclass(with(Representable, Constructor, Comparable, Hashable), flags(HAS_WEAKREF))] impl PyCode { #[pygetset] const fn co_posonlyargcount(&self) -> usize { @@ -721,6 +790,10 @@ impl PyCode { vm.ctx.new_bytes(self.code.exceptiontable.to_vec()) } + // co_lnotab is intentionally not implemented. + // It was deprecated since 3.12 and scheduled for removal in 3.14. + // Use co_lines() or co_linetable instead. + #[pymethod] pub fn co_lines(&self, vm: &VirtualMachine) -> PyResult { // TODO: Implement lazy iterator (lineiterator) like CPython for better performance