Skip to main content

rustpython_vm/builtins/
code.rs

1//! Infamous code object. The python class `code`
2
3use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType};
4use crate::common::lock::PyMutex;
5use crate::{
6    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
7    builtins::PyStrInterned,
8    bytecode::{self, AsBag, BorrowedConstant, CodeFlags, Constant, ConstantBag, Instruction},
9    class::{PyClassImpl, StaticType},
10    convert::{ToPyException, ToPyObject},
11    frozen,
12    function::OptionalArg,
13    types::{Comparable, Constructor, Hashable, Representable},
14};
15use alloc::fmt;
16use core::{
17    borrow::Borrow,
18    ops::Deref,
19    sync::atomic::{AtomicPtr, AtomicU64, Ordering},
20};
21use malachite_bigint::BigInt;
22use num_traits::Zero;
23use rustpython_compiler_core::{OneIndexed, bytecode::CodeUnits, bytecode::PyCodeLocationInfoKind};
24
25/// State for iterating through code address ranges
26struct PyCodeAddressRange<'a> {
27    ar_start: i32,
28    ar_end: i32,
29    ar_line: i32,
30    computed_line: i32,
31    reader: LineTableReader<'a>,
32}
33
34impl<'a> PyCodeAddressRange<'a> {
35    fn new(linetable: &'a [u8], first_line: i32) -> Self {
36        PyCodeAddressRange {
37            ar_start: 0,
38            ar_end: 0,
39            ar_line: -1,
40            computed_line: first_line,
41            reader: LineTableReader::new(linetable),
42        }
43    }
44
45    /// Check if this is a NO_LINE marker (code 15)
46    fn is_no_line_marker(byte: u8) -> bool {
47        (byte >> 3) == 0x1f
48    }
49
50    /// Advance to next address range
51    fn advance(&mut self) -> bool {
52        if self.reader.at_end() {
53            return false;
54        }
55
56        let first_byte = match self.reader.read_byte() {
57            Some(b) => b,
58            None => return false,
59        };
60
61        if (first_byte & 0x80) == 0 {
62            return false; // Invalid linetable
63        }
64
65        let code = (first_byte >> 3) & 0x0f;
66        let length = ((first_byte & 0x07) + 1) as i32;
67
68        // Get line delta for this entry
69        let line_delta = self.get_line_delta(code);
70
71        // Update computed line
72        self.computed_line += line_delta;
73
74        // Check for NO_LINE marker
75        if Self::is_no_line_marker(first_byte) {
76            self.ar_line = -1;
77        } else {
78            self.ar_line = self.computed_line;
79        }
80
81        // Update address range
82        self.ar_start = self.ar_end;
83        self.ar_end += length * 2; // sizeof(_Py_CODEUNIT) = 2
84
85        // Skip remaining bytes for this entry
86        while !self.reader.at_end() {
87            if let Some(b) = self.reader.peek_byte() {
88                if (b & 0x80) != 0 {
89                    break;
90                }
91                self.reader.read_byte();
92            } else {
93                break;
94            }
95        }
96
97        true
98    }
99
100    fn get_line_delta(&mut self, code: u8) -> i32 {
101        let kind = match PyCodeLocationInfoKind::from_code(code) {
102            Some(k) => k,
103            None => return 0,
104        };
105
106        match kind {
107            PyCodeLocationInfoKind::None => 0, // NO_LINE marker
108            PyCodeLocationInfoKind::Long => {
109                let delta = self.reader.read_signed_varint();
110                // Skip end_line, col, end_col
111                self.reader.read_varint();
112                self.reader.read_varint();
113                self.reader.read_varint();
114                delta
115            }
116            PyCodeLocationInfoKind::NoColumns => self.reader.read_signed_varint(),
117            PyCodeLocationInfoKind::OneLine0 => {
118                self.reader.read_byte(); // Skip column
119                self.reader.read_byte(); // Skip end column
120                0
121            }
122            PyCodeLocationInfoKind::OneLine1 => {
123                self.reader.read_byte(); // Skip column
124                self.reader.read_byte(); // Skip end column
125                1
126            }
127            PyCodeLocationInfoKind::OneLine2 => {
128                self.reader.read_byte(); // Skip column
129                self.reader.read_byte(); // Skip end column
130                2
131            }
132            _ if kind.is_short() => {
133                self.reader.read_byte(); // Skip column byte
134                0
135            }
136            _ => 0,
137        }
138    }
139}
140
141#[derive(FromArgs)]
142pub struct ReplaceArgs {
143    #[pyarg(named, optional)]
144    co_posonlyargcount: OptionalArg<u32>,
145    #[pyarg(named, optional)]
146    co_argcount: OptionalArg<u32>,
147    #[pyarg(named, optional)]
148    co_kwonlyargcount: OptionalArg<u32>,
149    #[pyarg(named, optional)]
150    co_filename: OptionalArg<PyStrRef>,
151    #[pyarg(named, optional)]
152    co_firstlineno: OptionalArg<u32>,
153    #[pyarg(named, optional)]
154    co_consts: OptionalArg<Vec<PyObjectRef>>,
155    #[pyarg(named, optional)]
156    co_name: OptionalArg<PyStrRef>,
157    #[pyarg(named, optional)]
158    co_names: OptionalArg<Vec<PyObjectRef>>,
159    #[pyarg(named, optional)]
160    co_flags: OptionalArg<u32>,
161    #[pyarg(named, optional)]
162    co_varnames: OptionalArg<Vec<PyObjectRef>>,
163    #[pyarg(named, optional)]
164    co_nlocals: OptionalArg<u32>,
165    #[pyarg(named, optional)]
166    co_stacksize: OptionalArg<u32>,
167    #[pyarg(named, optional)]
168    co_code: OptionalArg<crate::builtins::PyBytesRef>,
169    #[pyarg(named, optional)]
170    co_linetable: OptionalArg<crate::builtins::PyBytesRef>,
171    #[pyarg(named, optional)]
172    co_exceptiontable: OptionalArg<crate::builtins::PyBytesRef>,
173    #[pyarg(named, optional)]
174    co_freevars: OptionalArg<Vec<PyObjectRef>>,
175    #[pyarg(named, optional)]
176    co_cellvars: OptionalArg<Vec<PyObjectRef>>,
177    #[pyarg(named, optional)]
178    co_qualname: OptionalArg<PyStrRef>,
179}
180
181#[derive(Clone)]
182#[repr(transparent)]
183pub struct Literal(PyObjectRef);
184
185impl Borrow<PyObject> for Literal {
186    fn borrow(&self) -> &PyObject {
187        &self.0
188    }
189}
190
191impl From<Literal> for PyObjectRef {
192    fn from(obj: Literal) -> Self {
193        obj.0
194    }
195}
196
197impl From<PyObjectRef> for Literal {
198    fn from(obj: PyObjectRef) -> Self {
199        Literal(obj)
200    }
201}
202
203fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<'_, Literal> {
204    match_class!(match obj {
205        ref i @ super::int::PyInt => {
206            let value = i.as_bigint();
207            if obj.class().is(super::bool_::PyBool::static_type()) {
208                BorrowedConstant::Boolean {
209                    value: !value.is_zero(),
210                }
211            } else {
212                BorrowedConstant::Integer { value }
213            }
214        }
215        ref f @ super::float::PyFloat => BorrowedConstant::Float { value: f.to_f64() },
216        ref c @ super::complex::PyComplex => BorrowedConstant::Complex {
217            value: c.to_complex()
218        },
219        ref s @ super::pystr::PyStr => BorrowedConstant::Str { value: s.as_wtf8() },
220        ref b @ super::bytes::PyBytes => BorrowedConstant::Bytes {
221            value: b.as_bytes()
222        },
223        ref c @ PyCode => {
224            BorrowedConstant::Code { code: &c.code }
225        }
226        ref t @ super::tuple::PyTuple => {
227            let elements = t.as_slice();
228            // SAFETY: Literal is repr(transparent) over PyObjectRef, and a Literal tuple only ever
229            //         has other literals as elements
230            let elements = unsafe { &*(elements as *const [PyObjectRef] as *const [Literal]) };
231            BorrowedConstant::Tuple { elements }
232        }
233        super::singletons::PyNone => BorrowedConstant::None,
234        super::slice::PyEllipsis => BorrowedConstant::Ellipsis,
235        ref s @ super::slice::PySlice => {
236            // Constant pool slices always store Some() for start/step (even for None).
237            // Box::leak the array so it outlives the borrow. Leak is acceptable since
238            // constant pool objects live for the program's lifetime.
239            let start = s.start.clone().unwrap();
240            let stop = s.stop.clone();
241            let step = s.step.clone().unwrap();
242            let arr = Box::leak(Box::new([Literal(start), Literal(stop), Literal(step)]));
243            BorrowedConstant::Slice { elements: arr }
244        }
245        ref fs @ super::set::PyFrozenSet => {
246            // Box::leak the elements so they outlive the borrow. Leak is acceptable since
247            // constant pool objects live for the program's lifetime.
248            let elems: Vec<Literal> = fs.elements().into_iter().map(Literal).collect();
249            let elements = Box::leak(elems.into_boxed_slice());
250            BorrowedConstant::Frozenset { elements }
251        }
252        _ => panic!("unexpected payload for constant python value"),
253    })
254}
255
256impl Constant for Literal {
257    type Name = &'static PyStrInterned;
258    fn borrow_constant(&self) -> BorrowedConstant<'_, Self> {
259        borrow_obj_constant(&self.0)
260    }
261}
262
263impl<'a> AsBag for &'a Context {
264    type Bag = PyObjBag<'a>;
265    fn as_bag(self) -> PyObjBag<'a> {
266        PyObjBag(self)
267    }
268}
269
270impl<'a> AsBag for &'a VirtualMachine {
271    type Bag = PyObjBag<'a>;
272    fn as_bag(self) -> PyObjBag<'a> {
273        PyObjBag(&self.ctx)
274    }
275}
276
277#[derive(Clone, Copy)]
278pub struct PyObjBag<'a>(pub &'a Context);
279
280impl ConstantBag for PyObjBag<'_> {
281    type Constant = Literal;
282
283    fn make_constant<C: Constant>(&self, constant: BorrowedConstant<'_, C>) -> Self::Constant {
284        let ctx = self.0;
285        let obj = match constant {
286            BorrowedConstant::Integer { value } => ctx.new_bigint(value).into(),
287            BorrowedConstant::Float { value } => ctx.new_float(value).into(),
288            BorrowedConstant::Complex { value } => ctx.new_complex(value).into(),
289            BorrowedConstant::Str { value } if value.len() <= 20 => {
290                ctx.intern_str(value).to_object()
291            }
292            BorrowedConstant::Str { value } => ctx.new_str(value).into(),
293            BorrowedConstant::Bytes { value } => ctx.new_bytes(value.to_vec()).into(),
294            BorrowedConstant::Boolean { value } => ctx.new_bool(value).into(),
295            BorrowedConstant::Code { code } => ctx.new_code(code.map_clone_bag(self)).into(),
296            BorrowedConstant::Tuple { elements } => {
297                let elements = elements
298                    .iter()
299                    .map(|constant| self.make_constant(constant.borrow_constant()).0)
300                    .collect();
301                ctx.new_tuple(elements).into()
302            }
303            BorrowedConstant::Slice { elements } => {
304                let [start, stop, step] = elements;
305                let start_obj = self.make_constant(start.borrow_constant()).0;
306                let stop_obj = self.make_constant(stop.borrow_constant()).0;
307                let step_obj = self.make_constant(step.borrow_constant()).0;
308                // Store as PySlice with Some() for all fields (even None values)
309                // so borrow_obj_constant can reference them.
310                use crate::builtins::PySlice;
311                PySlice {
312                    start: Some(start_obj),
313                    stop: stop_obj,
314                    step: Some(step_obj),
315                }
316                .into_ref(ctx)
317                .into()
318            }
319            BorrowedConstant::Frozenset { elements: _ } => {
320                // Creating a frozenset requires VirtualMachine for element hashing.
321                // PyObjBag only has Context, so we cannot construct PyFrozenSet here.
322                // Frozenset constants from .pyc are handled by PyMarshalBag which has VM access.
323                unimplemented!(
324                    "frozenset constant in PyObjBag::make_constant requires VirtualMachine"
325                )
326            }
327            BorrowedConstant::None => ctx.none(),
328            BorrowedConstant::Ellipsis => ctx.ellipsis.clone().into(),
329        };
330
331        Literal(obj)
332    }
333
334    fn make_name(&self, name: &str) -> &'static PyStrInterned {
335        self.0.intern_str(name)
336    }
337
338    fn make_int(&self, value: BigInt) -> Self::Constant {
339        Literal(self.0.new_int(value).into())
340    }
341
342    fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant {
343        Literal(self.0.new_tuple(elements.map(|lit| lit.0).collect()).into())
344    }
345
346    fn make_code(&self, code: CodeObject) -> Self::Constant {
347        Literal(self.0.new_code(code).into())
348    }
349}
350
351pub type CodeObject = bytecode::CodeObject<Literal>;
352
353pub trait IntoCodeObject {
354    fn into_code_object(self, ctx: &Context) -> CodeObject;
355}
356
357impl IntoCodeObject for CodeObject {
358    fn into_code_object(self, _ctx: &Context) -> Self {
359        self
360    }
361}
362
363impl IntoCodeObject for bytecode::CodeObject {
364    fn into_code_object(self, ctx: &Context) -> CodeObject {
365        self.map_bag(PyObjBag(ctx))
366    }
367}
368
369impl<B: AsRef<[u8]>> IntoCodeObject for frozen::FrozenCodeObject<B> {
370    fn into_code_object(self, ctx: &Context) -> CodeObject {
371        self.decode(ctx)
372    }
373}
374
375/// Per-code-object monitoring data (_PyCoMonitoringData).
376/// Stores original opcodes displaced by INSTRUMENTED_LINE / INSTRUMENTED_INSTRUCTION.
377pub struct CoMonitoringData {
378    /// Original opcodes at positions with INSTRUMENTED_LINE.
379    /// Indexed by instruction index. 0 = not instrumented for LINE.
380    pub line_opcodes: Vec<u8>,
381
382    /// Original opcodes at positions with INSTRUMENTED_INSTRUCTION.
383    /// Indexed by instruction index. 0 = not instrumented for INSTRUCTION.
384    pub per_instruction_opcodes: Vec<u8>,
385}
386
387#[pyclass(module = false, name = "code")]
388pub struct PyCode {
389    pub code: CodeObject,
390    source_path: AtomicPtr<PyStrInterned>,
391    /// Version counter for lazy re-instrumentation.
392    /// Compared against `PyGlobalState::instrumentation_version` at RESUME.
393    pub instrumentation_version: AtomicU64,
394    /// Side-table for INSTRUMENTED_LINE / INSTRUMENTED_INSTRUCTION.
395    pub monitoring_data: PyMutex<Option<CoMonitoringData>>,
396    /// Whether adaptive counters have been initialized (lazy quickening).
397    pub quickened: core::sync::atomic::AtomicBool,
398}
399
400impl Deref for PyCode {
401    type Target = CodeObject;
402    fn deref(&self) -> &Self::Target {
403        &self.code
404    }
405}
406
407impl PyCode {
408    pub fn new(code: CodeObject) -> Self {
409        let sp = code.source_path as *const PyStrInterned as *mut PyStrInterned;
410        Self {
411            code,
412            source_path: AtomicPtr::new(sp),
413            instrumentation_version: AtomicU64::new(0),
414            monitoring_data: PyMutex::new(None),
415            quickened: core::sync::atomic::AtomicBool::new(false),
416        }
417    }
418
419    pub fn source_path(&self) -> &'static PyStrInterned {
420        // SAFETY: always points to a valid &'static PyStrInterned (interned strings are never deallocated)
421        unsafe { &*self.source_path.load(Ordering::Relaxed) }
422    }
423
424    pub fn set_source_path(&self, new: &'static PyStrInterned) {
425        self.source_path.store(
426            new as *const PyStrInterned as *mut PyStrInterned,
427            Ordering::Relaxed,
428        );
429    }
430    pub fn from_pyc_path(path: &std::path::Path, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
431        let name = match path.file_stem() {
432            Some(stem) => stem.display().to_string(),
433            None => "".to_owned(),
434        };
435        let content = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?;
436        Self::from_pyc(
437            &content,
438            Some(&name),
439            Some(&path.display().to_string()),
440            Some("<source>"),
441            vm,
442        )
443    }
444    pub fn from_pyc(
445        pyc_bytes: &[u8],
446        name: Option<&str>,
447        bytecode_path: Option<&str>,
448        source_path: Option<&str>,
449        vm: &VirtualMachine,
450    ) -> PyResult<PyRef<Self>> {
451        if !crate::import::check_pyc_magic_number_bytes(pyc_bytes) {
452            return Err(vm.new_value_error("pyc bytes has wrong MAGIC"));
453        }
454        let bootstrap_external = vm.import("_frozen_importlib_external", 0)?;
455        let compile_bytecode = bootstrap_external.get_attr("_compile_bytecode", vm)?;
456        // 16 is the pyc header length
457        let Some((_, code_bytes)) = pyc_bytes.split_at_checked(16) else {
458            return Err(vm.new_value_error(format!(
459                "pyc_bytes header is broken. 16 bytes expected but {} bytes given.",
460                pyc_bytes.len()
461            )));
462        };
463        let code_bytes_obj = vm.ctx.new_bytes(code_bytes.to_vec());
464        let compiled =
465            compile_bytecode.call((code_bytes_obj, name, bytecode_path, source_path), vm)?;
466        compiled.try_downcast(vm)
467    }
468}
469
470impl fmt::Debug for PyCode {
471    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472        write!(f, "code: {:?}", self.code)
473    }
474}
475
476impl PyPayload for PyCode {
477    #[inline]
478    fn class(ctx: &Context) -> &'static Py<PyType> {
479        ctx.types.code_type
480    }
481}
482
483impl Representable for PyCode {
484    #[inline]
485    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
486        let code = &zelf.code;
487        Ok(format!(
488            "<code object {} at {:#x} file {:?}, line {}>",
489            code.obj_name,
490            zelf.get_id(),
491            zelf.source_path().as_str(),
492            code.first_line_number.map_or(-1, |n| n.get() as i32)
493        ))
494    }
495}
496
497impl Comparable for PyCode {
498    fn cmp(
499        zelf: &Py<Self>,
500        other: &PyObject,
501        op: crate::types::PyComparisonOp,
502        vm: &VirtualMachine,
503    ) -> PyResult<crate::function::PyComparisonValue> {
504        op.eq_only(|| {
505            let other = class_or_notimplemented!(Self, other);
506            let a = &zelf.code;
507            let b = &other.code;
508            let eq = a.obj_name == b.obj_name
509                && a.arg_count == b.arg_count
510                && a.posonlyarg_count == b.posonlyarg_count
511                && a.kwonlyarg_count == b.kwonlyarg_count
512                && a.flags == b.flags
513                && a.first_line_number == b.first_line_number
514                && a.instructions.original_bytes() == b.instructions.original_bytes()
515                && a.linetable == b.linetable
516                && a.exceptiontable == b.exceptiontable
517                && a.names == b.names
518                && a.varnames == b.varnames
519                && a.freevars == b.freevars
520                && a.cellvars == b.cellvars
521                && {
522                    let a_consts: Vec<_> = a.constants.iter().map(|c| c.0.clone()).collect();
523                    let b_consts: Vec<_> = b.constants.iter().map(|c| c.0.clone()).collect();
524                    if a_consts.len() != b_consts.len() {
525                        false
526                    } else {
527                        let mut eq = true;
528                        for (ac, bc) in a_consts.iter().zip(b_consts.iter()) {
529                            if !vm.bool_eq(ac, bc)? {
530                                eq = false;
531                                break;
532                            }
533                        }
534                        eq
535                    }
536                };
537            Ok(eq.into())
538        })
539    }
540}
541
542impl Hashable for PyCode {
543    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<crate::common::hash::PyHash> {
544        let code = &zelf.code;
545        // Hash a tuple of key attributes, matching CPython's code_hash
546        let tuple = vm.ctx.new_tuple(vec![
547            vm.ctx.new_str(code.obj_name.as_str()).into(),
548            vm.ctx.new_int(code.arg_count).into(),
549            vm.ctx.new_int(code.posonlyarg_count).into(),
550            vm.ctx.new_int(code.kwonlyarg_count).into(),
551            vm.ctx.new_int(code.varnames.len()).into(),
552            vm.ctx.new_int(code.flags.bits()).into(),
553            vm.ctx
554                .new_int(code.first_line_number.map_or(0, |n| n.get()) as i64)
555                .into(),
556            vm.ctx.new_bytes(code.instructions.original_bytes()).into(),
557            {
558                let consts: Vec<_> = code.constants.iter().map(|c| c.0.clone()).collect();
559                vm.ctx.new_tuple(consts).into()
560            },
561        ]);
562        tuple.as_object().hash(vm)
563    }
564}
565
566// Arguments for code object constructor
567#[derive(FromArgs)]
568pub struct PyCodeNewArgs {
569    argcount: u32,
570    posonlyargcount: u32,
571    kwonlyargcount: u32,
572    nlocals: u32,
573    stacksize: u32,
574    flags: u32,
575    co_code: PyBytesRef,
576    consts: PyTupleRef,
577    names: PyTupleRef,
578    varnames: PyTupleRef,
579    filename: PyStrRef,
580    name: PyStrRef,
581    qualname: PyStrRef,
582    firstlineno: i32,
583    linetable: PyBytesRef,
584    exceptiontable: PyBytesRef,
585    freevars: PyTupleRef,
586    cellvars: PyTupleRef,
587}
588
589impl Constructor for PyCode {
590    type Args = PyCodeNewArgs;
591
592    fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
593        // Convert names tuple to vector of interned strings
594        let names: Box<[&'static PyStrInterned]> = args
595            .names
596            .iter()
597            .map(|obj| {
598                let s = obj
599                    .downcast_ref::<super::pystr::PyStr>()
600                    .ok_or_else(|| vm.new_type_error("names must be tuple of strings"))?;
601                Ok(vm.ctx.intern_str(s.as_wtf8()))
602            })
603            .collect::<PyResult<Vec<_>>>()?
604            .into_boxed_slice();
605
606        let varnames: Box<[&'static PyStrInterned]> = args
607            .varnames
608            .iter()
609            .map(|obj| {
610                let s = obj
611                    .downcast_ref::<super::pystr::PyStr>()
612                    .ok_or_else(|| vm.new_type_error("varnames must be tuple of strings"))?;
613                Ok(vm.ctx.intern_str(s.as_wtf8()))
614            })
615            .collect::<PyResult<Vec<_>>>()?
616            .into_boxed_slice();
617
618        let cellvars: Box<[&'static PyStrInterned]> = args
619            .cellvars
620            .iter()
621            .map(|obj| {
622                let s = obj
623                    .downcast_ref::<super::pystr::PyStr>()
624                    .ok_or_else(|| vm.new_type_error("cellvars must be tuple of strings"))?;
625                Ok(vm.ctx.intern_str(s.as_wtf8()))
626            })
627            .collect::<PyResult<Vec<_>>>()?
628            .into_boxed_slice();
629
630        let freevars: Box<[&'static PyStrInterned]> = args
631            .freevars
632            .iter()
633            .map(|obj| {
634                let s = obj
635                    .downcast_ref::<super::pystr::PyStr>()
636                    .ok_or_else(|| vm.new_type_error("freevars must be tuple of strings"))?;
637                Ok(vm.ctx.intern_str(s.as_wtf8()))
638            })
639            .collect::<PyResult<Vec<_>>>()?
640            .into_boxed_slice();
641
642        // Check nlocals matches varnames length
643        if args.nlocals as usize != varnames.len() {
644            return Err(vm.new_value_error(format!(
645                "nlocals ({}) != len(varnames) ({})",
646                args.nlocals,
647                varnames.len()
648            )));
649        }
650
651        // Parse and validate bytecode from bytes
652        let bytecode_bytes = args.co_code.as_bytes();
653        let instructions = CodeUnits::try_from(bytecode_bytes)
654            .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))?;
655
656        // Convert constants
657        let constants = args
658            .consts
659            .iter()
660            .map(|obj| {
661                // Convert PyObject to Literal constant. For now, just wrap it
662                Literal(obj.clone())
663            })
664            .collect();
665
666        // Create locations (start and end pairs)
667        let row = if args.firstlineno > 0 {
668            OneIndexed::new(args.firstlineno as usize).unwrap_or(OneIndexed::MIN)
669        } else {
670            OneIndexed::MIN
671        };
672        let loc = rustpython_compiler_core::SourceLocation {
673            line: row,
674            character_offset: OneIndexed::from_zero_indexed(0),
675        };
676        let locations: Box<
677            [(
678                rustpython_compiler_core::SourceLocation,
679                rustpython_compiler_core::SourceLocation,
680            )],
681        > = vec![(loc, loc); instructions.len()].into_boxed_slice();
682
683        // Build localspluskinds with cell-local merging
684        let localspluskinds = {
685            use rustpython_compiler_core::bytecode::*;
686            let nlocals = varnames.len();
687            let ncells = cellvars.len();
688            let nfrees = freevars.len();
689            let numdropped = cellvars
690                .iter()
691                .filter(|cv| varnames.iter().any(|v| *v == **cv))
692                .count();
693            let nlocalsplus = nlocals + ncells - numdropped + nfrees;
694            let mut kinds = vec![0u8; nlocalsplus];
695            for kind in kinds.iter_mut().take(nlocals) {
696                *kind = CO_FAST_LOCAL;
697            }
698            let mut cell_numdropped = 0usize;
699            for (i, cv) in cellvars.iter().enumerate() {
700                let merged_idx = varnames.iter().position(|v| **v == **cv);
701                if let Some(local_idx) = merged_idx {
702                    kinds[local_idx] |= CO_FAST_CELL;
703                    cell_numdropped += 1;
704                } else {
705                    kinds[nlocals + i - cell_numdropped] = CO_FAST_CELL;
706                }
707            }
708            let free_start = nlocals + ncells - numdropped;
709            for i in 0..nfrees {
710                kinds[free_start + i] = CO_FAST_FREE;
711            }
712            kinds.into_boxed_slice()
713        };
714
715        // Build the CodeObject
716        let code = CodeObject {
717            instructions,
718            locations,
719            flags: CodeFlags::from_bits_truncate(args.flags),
720            posonlyarg_count: args.posonlyargcount,
721            arg_count: args.argcount,
722            kwonlyarg_count: args.kwonlyargcount,
723            source_path: vm.ctx.intern_str(args.filename.as_wtf8()),
724            first_line_number: if args.firstlineno > 0 {
725                OneIndexed::new(args.firstlineno as usize)
726            } else {
727                None
728            },
729            max_stackdepth: args.stacksize,
730            obj_name: vm.ctx.intern_str(args.name.as_wtf8()),
731            qualname: vm.ctx.intern_str(args.qualname.as_wtf8()),
732            constants,
733            names,
734            varnames,
735            cellvars,
736            freevars,
737            localspluskinds,
738            linetable: args.linetable.as_bytes().to_vec().into_boxed_slice(),
739            exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(),
740        };
741
742        Ok(PyCode::new(code))
743    }
744}
745
746#[pyclass(
747    with(Representable, Constructor, Comparable, Hashable),
748    flags(HAS_WEAKREF)
749)]
750impl PyCode {
751    #[pygetset]
752    const fn co_posonlyargcount(&self) -> usize {
753        self.code.posonlyarg_count as usize
754    }
755
756    #[pygetset]
757    const fn co_argcount(&self) -> usize {
758        self.code.arg_count as usize
759    }
760
761    #[pygetset]
762    const fn co_stacksize(&self) -> u32 {
763        self.code.max_stackdepth
764    }
765
766    #[pygetset]
767    pub fn co_filename(&self) -> PyStrRef {
768        self.source_path().to_owned()
769    }
770
771    #[pygetset]
772    pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef {
773        let cellvars = self
774            .cellvars
775            .iter()
776            .map(|name| name.to_pyobject(vm))
777            .collect();
778        vm.ctx.new_tuple(cellvars)
779    }
780
781    #[pygetset]
782    fn co_nlocals(&self) -> usize {
783        self.code.varnames.len()
784    }
785
786    #[pygetset]
787    fn co_firstlineno(&self) -> u32 {
788        self.code.first_line_number.map_or(0, |n| n.get() as _)
789    }
790
791    #[pygetset]
792    const fn co_kwonlyargcount(&self) -> usize {
793        self.code.kwonlyarg_count as usize
794    }
795
796    #[pygetset]
797    fn co_consts(&self, vm: &VirtualMachine) -> PyTupleRef {
798        let consts = self.code.constants.iter().map(|x| x.0.clone()).collect();
799        vm.ctx.new_tuple(consts)
800    }
801
802    #[pygetset]
803    fn co_name(&self) -> PyStrRef {
804        self.code.obj_name.to_owned()
805    }
806    #[pygetset]
807    fn co_qualname(&self) -> PyStrRef {
808        self.code.qualname.to_owned()
809    }
810
811    #[pygetset]
812    fn co_names(&self, vm: &VirtualMachine) -> PyTupleRef {
813        let names = self
814            .code
815            .names
816            .deref()
817            .iter()
818            .map(|name| name.to_pyobject(vm))
819            .collect();
820        vm.ctx.new_tuple(names)
821    }
822
823    #[pygetset]
824    const fn co_flags(&self) -> u32 {
825        self.code.flags.bits()
826    }
827
828    #[pygetset]
829    pub fn co_varnames(&self, vm: &VirtualMachine) -> PyTupleRef {
830        let varnames = self.code.varnames.iter().map(|s| s.to_object()).collect();
831        vm.ctx.new_tuple(varnames)
832    }
833
834    #[pygetset]
835    pub fn co_code(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
836        vm.ctx.new_bytes(self.code.instructions.original_bytes())
837    }
838
839    #[pygetset]
840    pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
841        // Return current (possibly quickened/specialized) bytecode
842        let bytes = unsafe {
843            core::slice::from_raw_parts(
844                self.code.instructions.as_ptr() as *const u8,
845                self.code.instructions.len() * 2,
846            )
847        };
848        vm.ctx.new_bytes(bytes.to_vec())
849    }
850
851    #[pygetset]
852    pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef {
853        let names = self
854            .code
855            .freevars
856            .deref()
857            .iter()
858            .map(|name| name.to_pyobject(vm))
859            .collect();
860        vm.ctx.new_tuple(names)
861    }
862
863    #[pygetset]
864    pub fn co_linetable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
865        // Return the actual linetable from the code object
866        vm.ctx.new_bytes(self.code.linetable.to_vec())
867    }
868
869    #[pygetset]
870    pub fn co_exceptiontable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
871        // Return the actual exception table from the code object
872        vm.ctx.new_bytes(self.code.exceptiontable.to_vec())
873    }
874
875    // spell-checker: ignore lnotab
876    // co_lnotab is intentionally not implemented.
877    // It was deprecated since 3.12 and scheduled for removal in 3.14.
878    // Use co_lines() or co_linetable instead.
879
880    #[pymethod]
881    pub fn co_lines(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
882        // TODO: Implement lazy iterator (lineiterator) like CPython for better performance
883        // Currently returns eager list for simplicity
884
885        // Return an iterator over (start_offset, end_offset, lineno) tuples
886        let linetable = self.code.linetable.as_ref();
887        let mut lines = Vec::new();
888
889        if !linetable.is_empty() {
890            let first_line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
891            let mut range = PyCodeAddressRange::new(linetable, first_line);
892
893            // Process all address ranges and merge consecutive entries with same line
894            let mut pending_entry: Option<(i32, i32, i32)> = None;
895
896            while range.advance() {
897                let start = range.ar_start;
898                let end = range.ar_end;
899                let line = range.ar_line;
900
901                if let Some((prev_start, _, prev_line)) = pending_entry {
902                    if prev_line == line {
903                        // Same line, extend the range
904                        pending_entry = Some((prev_start, end, prev_line));
905                    } else {
906                        // Different line, emit the previous entry
907                        let tuple = if prev_line == -1 {
908                            vm.ctx.new_tuple(vec![
909                                vm.ctx.new_int(prev_start).into(),
910                                vm.ctx.new_int(start).into(),
911                                vm.ctx.none(),
912                            ])
913                        } else {
914                            vm.ctx.new_tuple(vec![
915                                vm.ctx.new_int(prev_start).into(),
916                                vm.ctx.new_int(start).into(),
917                                vm.ctx.new_int(prev_line).into(),
918                            ])
919                        };
920                        lines.push(tuple.into());
921                        pending_entry = Some((start, end, line));
922                    }
923                } else {
924                    // First entry
925                    pending_entry = Some((start, end, line));
926                }
927            }
928
929            // Emit the last pending entry
930            if let Some((start, end, line)) = pending_entry {
931                let tuple = if line == -1 {
932                    vm.ctx.new_tuple(vec![
933                        vm.ctx.new_int(start).into(),
934                        vm.ctx.new_int(end).into(),
935                        vm.ctx.none(),
936                    ])
937                } else {
938                    vm.ctx.new_tuple(vec![
939                        vm.ctx.new_int(start).into(),
940                        vm.ctx.new_int(end).into(),
941                        vm.ctx.new_int(line).into(),
942                    ])
943                };
944                lines.push(tuple.into());
945            }
946        }
947
948        let list = vm.ctx.new_list(lines);
949        vm.call_method(list.as_object(), "__iter__", ())
950    }
951
952    #[pymethod]
953    pub fn co_positions(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
954        // Return an iterator over (line, end_line, column, end_column) tuples for each instruction
955        let linetable = self.code.linetable.as_ref();
956        let mut positions = Vec::new();
957
958        if !linetable.is_empty() {
959            let mut reader = LineTableReader::new(linetable);
960            let mut line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
961
962            while !reader.at_end() {
963                let first_byte = match reader.read_byte() {
964                    Some(b) => b,
965                    None => break,
966                };
967
968                if (first_byte & 0x80) == 0 {
969                    break; // Invalid linetable
970                }
971
972                let code = (first_byte >> 3) & 0x0f;
973                let length = ((first_byte & 0x07) + 1) as i32;
974
975                let kind = match PyCodeLocationInfoKind::from_code(code) {
976                    Some(k) => k,
977                    None => break, // Invalid code
978                };
979
980                let (line_delta, end_line_delta, column, end_column): (
981                    i32,
982                    i32,
983                    Option<i32>,
984                    Option<i32>,
985                ) = match kind {
986                    PyCodeLocationInfoKind::None => {
987                        // No location - all values are None
988                        (0, 0, None, None)
989                    }
990                    PyCodeLocationInfoKind::Long => {
991                        // Long form
992                        let delta = reader.read_signed_varint();
993                        let end_line_delta = reader.read_varint() as i32;
994
995                        let col = reader.read_varint();
996                        let column = if col == 0 {
997                            None
998                        } else {
999                            Some((col - 1) as i32)
1000                        };
1001
1002                        let end_col = reader.read_varint();
1003                        let end_column = if end_col == 0 {
1004                            None
1005                        } else {
1006                            Some((end_col - 1) as i32)
1007                        };
1008
1009                        // endline = line + end_line_delta (will be computed after line update)
1010                        (delta, end_line_delta, column, end_column)
1011                    }
1012                    PyCodeLocationInfoKind::NoColumns => {
1013                        // No column form
1014                        let delta = reader.read_signed_varint();
1015                        (delta, 0, None, None) // endline will be same as line (delta = 0)
1016                    }
1017                    PyCodeLocationInfoKind::OneLine0
1018                    | PyCodeLocationInfoKind::OneLine1
1019                    | PyCodeLocationInfoKind::OneLine2 => {
1020                        // One-line form - endline = line
1021                        let col = reader.read_byte().unwrap_or(0) as i32;
1022                        let end_col = reader.read_byte().unwrap_or(0) as i32;
1023                        let delta = kind.one_line_delta().unwrap_or(0);
1024                        (delta, 0, Some(col), Some(end_col)) // endline = line (delta = 0)
1025                    }
1026                    _ if kind.is_short() => {
1027                        // Short form - endline = line
1028                        let col_data = reader.read_byte().unwrap_or(0);
1029                        let col_group = kind.short_column_group().unwrap_or(0);
1030                        let col = ((col_group as i32) << 3) | ((col_data >> 4) as i32);
1031                        let end_col = col + (col_data & 0x0f) as i32;
1032                        (0, 0, Some(col), Some(end_col)) // endline = line (delta = 0)
1033                    }
1034                    _ => (0, 0, None, None),
1035                };
1036
1037                // Update line number
1038                line += line_delta;
1039
1040                // Generate position tuples for each instruction covered by this entry
1041                for _ in 0..length {
1042                    // Handle special case for no location (code 15)
1043                    let final_line = if kind == PyCodeLocationInfoKind::None {
1044                        None
1045                    } else {
1046                        Some(line)
1047                    };
1048
1049                    let final_endline = if kind == PyCodeLocationInfoKind::None {
1050                        None
1051                    } else {
1052                        Some(line + end_line_delta)
1053                    };
1054
1055                    let line_obj = final_line.to_pyobject(vm);
1056                    let end_line_obj = final_endline.to_pyobject(vm);
1057                    let column_obj = column.to_pyobject(vm);
1058                    let end_column_obj = end_column.to_pyobject(vm);
1059
1060                    let tuple =
1061                        vm.ctx
1062                            .new_tuple(vec![line_obj, end_line_obj, column_obj, end_column_obj]);
1063                    positions.push(tuple.into());
1064                }
1065            }
1066        }
1067
1068        let list = vm.ctx.new_list(positions);
1069        vm.call_method(list.as_object(), "__iter__", ())
1070    }
1071
1072    #[pymethod]
1073    pub fn co_branches(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1074        let instructions = &self.code.instructions;
1075        let mut branches = Vec::new();
1076        let mut extended_arg: u32 = 0;
1077
1078        for (i, unit) in instructions.iter().enumerate() {
1079            // De-instrument: use base opcode for instrumented variants
1080            let op = unit.op.to_base().unwrap_or(unit.op);
1081            let raw_arg = u32::from(u8::from(unit.arg));
1082
1083            if matches!(op, Instruction::ExtendedArg) {
1084                extended_arg = (extended_arg | raw_arg) << 8;
1085                continue;
1086            }
1087
1088            let oparg = extended_arg | raw_arg;
1089            extended_arg = 0;
1090
1091            let caches = op.cache_entries();
1092            let (src, left, right) = match op {
1093                Instruction::ForIter { .. } => {
1094                    // left = fall-through past CACHE entries (continue iteration)
1095                    // right = past END_FOR (iterator exhausted, skip cleanup)
1096                    // arg is relative forward from after instruction+caches
1097                    let after_cache = i + 1 + caches;
1098                    let target = after_cache + oparg as usize;
1099                    let right = if matches!(
1100                        instructions.get(target).map(|u| u.op),
1101                        Some(Instruction::EndFor) | Some(Instruction::InstrumentedEndFor)
1102                    ) {
1103                        (target + 1) * 2
1104                    } else {
1105                        target * 2
1106                    };
1107                    (i * 2, after_cache * 2, right)
1108                }
1109                Instruction::PopJumpIfFalse { .. }
1110                | Instruction::PopJumpIfTrue { .. }
1111                | Instruction::PopJumpIfNone { .. }
1112                | Instruction::PopJumpIfNotNone { .. } => {
1113                    // left = fall-through past CACHE entries (skip NOT_TAKEN if present)
1114                    // right = jump target (relative forward from after instruction+caches)
1115                    let after_cache = i + 1 + caches;
1116                    let next_op = instructions
1117                        .get(after_cache)
1118                        .map(|u| u.op.to_base().unwrap_or(u.op));
1119                    let fallthrough = if matches!(next_op, Some(Instruction::NotTaken)) {
1120                        (after_cache + 1) * 2
1121                    } else {
1122                        after_cache * 2
1123                    };
1124                    let right_target = after_cache + oparg as usize;
1125                    (i * 2, fallthrough, right_target * 2)
1126                }
1127                Instruction::EndAsyncFor => {
1128                    // src = END_SEND position (next_i - oparg)
1129                    let next_i = i + 1;
1130                    let Some(src_i) = next_i.checked_sub(oparg as usize) else {
1131                        continue;
1132                    };
1133                    // left = fall-through past NOT_TAKEN
1134                    (src_i * 2, (src_i + 2) * 2, next_i * 2)
1135                }
1136                _ => continue,
1137            };
1138
1139            let tuple = vm.ctx.new_tuple(vec![
1140                vm.ctx.new_int(src).into(),
1141                vm.ctx.new_int(left).into(),
1142                vm.ctx.new_int(right).into(),
1143            ]);
1144            branches.push(tuple.into());
1145        }
1146
1147        let list = vm.ctx.new_list(branches);
1148        vm.call_method(list.as_object(), "__iter__", ())
1149    }
1150
1151    #[pymethod]
1152    pub fn __replace__(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> {
1153        self.replace(args, vm)
1154    }
1155
1156    #[pymethod]
1157    pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> {
1158        let ReplaceArgs {
1159            co_posonlyargcount,
1160            co_argcount,
1161            co_kwonlyargcount,
1162            co_filename,
1163            co_firstlineno,
1164            co_consts,
1165            co_name,
1166            co_names,
1167            co_flags,
1168            co_varnames,
1169            co_nlocals,
1170            co_stacksize,
1171            co_code,
1172            co_linetable,
1173            co_exceptiontable,
1174            co_freevars,
1175            co_cellvars,
1176            co_qualname,
1177        } = args;
1178        let posonlyarg_count = match co_posonlyargcount {
1179            OptionalArg::Present(posonlyarg_count) => posonlyarg_count,
1180            OptionalArg::Missing => self.code.posonlyarg_count,
1181        };
1182
1183        let arg_count = match co_argcount {
1184            OptionalArg::Present(arg_count) => arg_count,
1185            OptionalArg::Missing => self.code.arg_count,
1186        };
1187
1188        let source_path = match co_filename {
1189            OptionalArg::Present(source_path) => source_path,
1190            OptionalArg::Missing => self.source_path().to_owned(),
1191        };
1192
1193        let first_line_number = match co_firstlineno {
1194            OptionalArg::Present(first_line_number) => OneIndexed::new(first_line_number as _),
1195            OptionalArg::Missing => self.code.first_line_number,
1196        };
1197
1198        let kwonlyarg_count = match co_kwonlyargcount {
1199            OptionalArg::Present(kwonlyarg_count) => kwonlyarg_count,
1200            OptionalArg::Missing => self.code.kwonlyarg_count,
1201        };
1202
1203        let constants = match co_consts {
1204            OptionalArg::Present(constants) => constants,
1205            OptionalArg::Missing => self.code.constants.iter().map(|x| x.0.clone()).collect(),
1206        };
1207
1208        let obj_name = match co_name {
1209            OptionalArg::Present(obj_name) => obj_name,
1210            OptionalArg::Missing => self.code.obj_name.to_owned(),
1211        };
1212
1213        let names = match co_names {
1214            OptionalArg::Present(names) => names,
1215            OptionalArg::Missing => self
1216                .code
1217                .names
1218                .deref()
1219                .iter()
1220                .map(|name| name.to_pyobject(vm))
1221                .collect(),
1222        };
1223
1224        let flags = match co_flags {
1225            OptionalArg::Present(flags) => flags,
1226            OptionalArg::Missing => self.code.flags.bits(),
1227        };
1228
1229        let varnames = match co_varnames {
1230            OptionalArg::Present(varnames) => varnames,
1231            OptionalArg::Missing => self.code.varnames.iter().map(|s| s.to_object()).collect(),
1232        };
1233
1234        let qualname = match co_qualname {
1235            OptionalArg::Present(qualname) => qualname,
1236            OptionalArg::Missing => self.code.qualname.to_owned(),
1237        };
1238
1239        let max_stackdepth = match co_stacksize {
1240            OptionalArg::Present(stacksize) => stacksize,
1241            OptionalArg::Missing => self.code.max_stackdepth,
1242        };
1243
1244        let instructions = match co_code {
1245            OptionalArg::Present(code_bytes) => {
1246                // Parse and validate bytecode from bytes
1247                CodeUnits::try_from(code_bytes.as_bytes())
1248                    .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))?
1249            }
1250            OptionalArg::Missing => self.code.instructions.clone(),
1251        };
1252
1253        let cellvars = match co_cellvars {
1254            OptionalArg::Present(cellvars) => cellvars
1255                .into_iter()
1256                .map(|o| o.as_interned_str(vm).unwrap())
1257                .collect(),
1258            OptionalArg::Missing => self.code.cellvars.clone(),
1259        };
1260
1261        let freevars = match co_freevars {
1262            OptionalArg::Present(freevars) => freevars
1263                .into_iter()
1264                .map(|o| o.as_interned_str(vm).unwrap())
1265                .collect(),
1266            OptionalArg::Missing => self.code.freevars.clone(),
1267        };
1268
1269        // Validate co_nlocals if provided
1270        if let OptionalArg::Present(nlocals) = co_nlocals
1271            && nlocals as usize != varnames.len()
1272        {
1273            return Err(vm.new_value_error(format!(
1274                "co_nlocals ({}) != len(co_varnames) ({})",
1275                nlocals,
1276                varnames.len()
1277            )));
1278        }
1279
1280        // Handle linetable and exceptiontable
1281        let linetable = match co_linetable {
1282            OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(),
1283            OptionalArg::Missing => self.code.linetable.clone(),
1284        };
1285
1286        let exceptiontable = match co_exceptiontable {
1287            OptionalArg::Present(exceptiontable) => {
1288                exceptiontable.as_bytes().to_vec().into_boxed_slice()
1289            }
1290            OptionalArg::Missing => self.code.exceptiontable.clone(),
1291        };
1292
1293        let new_code = CodeObject {
1294            flags: CodeFlags::from_bits_truncate(flags),
1295            posonlyarg_count,
1296            arg_count,
1297            kwonlyarg_count,
1298            source_path: source_path.as_object().as_interned_str(vm).unwrap(),
1299            first_line_number,
1300            obj_name: obj_name.as_object().as_interned_str(vm).unwrap(),
1301            qualname: qualname.as_object().as_interned_str(vm).unwrap(),
1302
1303            max_stackdepth,
1304            instructions,
1305            // FIXME: invalid locations. Actually locations is a duplication of linetable.
1306            // It can be removed once we move every other code to use linetable only.
1307            locations: self.code.locations.clone(),
1308            constants: constants.into_iter().map(Literal).collect(),
1309            names: names
1310                .into_iter()
1311                .map(|o| o.as_interned_str(vm).unwrap())
1312                .collect(),
1313            varnames: varnames
1314                .into_iter()
1315                .map(|o| o.as_interned_str(vm).unwrap())
1316                .collect(),
1317            cellvars,
1318            freevars,
1319            localspluskinds: self.code.localspluskinds.clone(),
1320            linetable,
1321            exceptiontable,
1322        };
1323
1324        Ok(PyCode::new(new_code))
1325    }
1326
1327    #[pymethod]
1328    fn _varname_from_oparg(&self, opcode: i32, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1329        let idx_err = |vm: &VirtualMachine| vm.new_index_error("tuple index out of range");
1330
1331        let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?;
1332
1333        let varnames_len = self.code.varnames.len();
1334        // Non-parameter cells: cellvars that are NOT also in varnames
1335        let nonparam_cellvars: Vec<_> = self
1336            .code
1337            .cellvars
1338            .iter()
1339            .filter(|s| {
1340                let s_str: &str = s.as_ref();
1341                !self.code.varnames.iter().any(|v| {
1342                    let v_str: &str = v.as_ref();
1343                    v_str == s_str
1344                })
1345            })
1346            .collect();
1347        let nonparam_len = nonparam_cellvars.len();
1348
1349        let name = if idx < varnames_len {
1350            // Index in varnames (includes parameter cells)
1351            self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?
1352        } else if idx < varnames_len + nonparam_len {
1353            // Index in non-parameter cellvars
1354            *nonparam_cellvars
1355                .get(idx - varnames_len)
1356                .ok_or_else(|| idx_err(vm))?
1357        } else {
1358            // Index in freevars
1359            self.code
1360                .freevars
1361                .get(idx - varnames_len - nonparam_len)
1362                .ok_or_else(|| idx_err(vm))?
1363        };
1364        Ok(name.to_object())
1365    }
1366}
1367
1368impl fmt::Display for PyCode {
1369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1370        (**self).fmt(f)
1371    }
1372}
1373
1374impl ToPyObject for CodeObject {
1375    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
1376        vm.ctx.new_code(self).into()
1377    }
1378}
1379
1380impl ToPyObject for bytecode::CodeObject {
1381    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
1382        vm.ctx.new_code(self).into()
1383    }
1384}
1385
1386// Helper struct for reading linetable
1387struct LineTableReader<'a> {
1388    data: &'a [u8],
1389    pos: usize,
1390}
1391
1392impl<'a> LineTableReader<'a> {
1393    fn new(data: &'a [u8]) -> Self {
1394        Self { data, pos: 0 }
1395    }
1396
1397    fn read_byte(&mut self) -> Option<u8> {
1398        if self.pos < self.data.len() {
1399            let byte = self.data[self.pos];
1400            self.pos += 1;
1401            Some(byte)
1402        } else {
1403            None
1404        }
1405    }
1406
1407    fn peek_byte(&self) -> Option<u8> {
1408        if self.pos < self.data.len() {
1409            Some(self.data[self.pos])
1410        } else {
1411            None
1412        }
1413    }
1414
1415    fn read_varint(&mut self) -> u32 {
1416        if let Some(first) = self.read_byte() {
1417            let mut val = (first & 0x3f) as u32;
1418            let mut shift = 0;
1419            let mut byte = first;
1420            while (byte & 0x40) != 0 {
1421                if let Some(next) = self.read_byte() {
1422                    shift += 6;
1423                    val |= ((next & 0x3f) as u32) << shift;
1424                    byte = next;
1425                } else {
1426                    break;
1427                }
1428            }
1429            val
1430        } else {
1431            0
1432        }
1433    }
1434
1435    fn read_signed_varint(&mut self) -> i32 {
1436        let uval = self.read_varint();
1437        if uval & 1 != 0 {
1438            -((uval >> 1) as i32)
1439        } else {
1440            (uval >> 1) as i32
1441        }
1442    }
1443
1444    fn at_end(&self) -> bool {
1445        self.pos >= self.data.len()
1446    }
1447}
1448
1449pub fn init(ctx: &'static Context) {
1450    PyCode::extend_class(ctx, ctx.types.code_type);
1451}