Skip to main content

rustpython_vm/builtins/
function.rs

1#[cfg(feature = "jit")]
2mod jit;
3
4use super::{
5    PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyModule, PyStr, PyStrRef, PyTuple,
6    PyTupleRef, PyType,
7};
8use crate::common::hash::PyHash;
9use crate::common::lock::PyMutex;
10use crate::function::ArgMapping;
11use crate::object::{PyAtomicRef, Traverse, TraverseFn};
12use crate::{
13    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
14    bytecode,
15    class::PyClassImpl,
16    common::wtf8::{Wtf8Buf, wtf8_concat},
17    frame::{Frame, FrameRef},
18    function::{FuncArgs, OptionalArg, PyComparisonValue, PySetterValue},
19    scope::Scope,
20    types::{
21        Callable, Comparable, Constructor, GetAttr, GetDescriptor, Hashable, PyComparisonOp,
22        Representable,
23    },
24};
25use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
26use itertools::Itertools;
27#[cfg(feature = "jit")]
28use rustpython_jit::CompiledCode;
29
30fn format_missing_args(
31    qualname: impl core::fmt::Display,
32    kind: &str,
33    missing: &mut Vec<impl core::fmt::Display>,
34) -> String {
35    let count = missing.len();
36    let last = if missing.len() > 1 {
37        missing.pop()
38    } else {
39        None
40    };
41    let (and, right): (&str, String) = if let Some(last) = last {
42        (
43            if missing.len() == 1 {
44                "' and '"
45            } else {
46                "', and '"
47            },
48            format!("{last}"),
49        )
50    } else {
51        ("", String::new())
52    };
53    format!(
54        "{qualname}() missing {count} required {kind} argument{}: '{}{}{right}'",
55        if count == 1 { "" } else { "s" },
56        missing.iter().join("', '"),
57        and,
58    )
59}
60
61#[pyclass(module = false, name = "function", traverse = "manual")]
62#[derive(Debug)]
63pub struct PyFunction {
64    code: PyAtomicRef<PyCode>,
65    globals: PyDictRef,
66    builtins: PyObjectRef,
67    pub(crate) closure: Option<PyRef<PyTuple<PyCellRef>>>,
68    defaults_and_kwdefaults: PyMutex<(Option<PyTupleRef>, Option<PyDictRef>)>,
69    name: PyMutex<PyStrRef>,
70    qualname: PyMutex<PyStrRef>,
71    type_params: PyMutex<PyTupleRef>,
72    annotations: PyMutex<Option<PyDictRef>>,
73    annotate: PyMutex<Option<PyObjectRef>>,
74    module: PyMutex<PyObjectRef>,
75    doc: PyMutex<PyObjectRef>,
76    func_version: AtomicU32,
77    #[cfg(feature = "jit")]
78    jitted_code: PyMutex<Option<CompiledCode>>,
79}
80
81static FUNC_VERSION_COUNTER: AtomicU32 = AtomicU32::new(1);
82
83/// Atomically allocate the next function version, returning 0 if exhausted.
84/// Once the counter wraps to 0, it stays at 0 permanently.
85fn next_func_version() -> u32 {
86    FUNC_VERSION_COUNTER
87        .fetch_update(Relaxed, Relaxed, |v| (v != 0).then(|| v.wrapping_add(1)))
88        .unwrap_or(0)
89}
90
91unsafe impl Traverse for PyFunction {
92    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
93        self.globals.traverse(tracer_fn);
94        if let Some(closure) = self.closure.as_ref() {
95            closure.as_untyped().traverse(tracer_fn);
96        }
97        self.defaults_and_kwdefaults.traverse(tracer_fn);
98        // Traverse additional fields that may contain references
99        self.type_params.lock().traverse(tracer_fn);
100        self.annotations.lock().traverse(tracer_fn);
101        self.module.lock().traverse(tracer_fn);
102        self.doc.lock().traverse(tracer_fn);
103    }
104
105    fn clear(&mut self, out: &mut Vec<crate::PyObjectRef>) {
106        // Pop closure if present (equivalent to Py_CLEAR(func_closure))
107        if let Some(closure) = self.closure.take() {
108            out.push(closure.into());
109        }
110
111        // Pop defaults and kwdefaults
112        if let Some(mut guard) = self.defaults_and_kwdefaults.try_lock() {
113            if let Some(defaults) = guard.0.take() {
114                out.push(defaults.into());
115            }
116            if let Some(kwdefaults) = guard.1.take() {
117                out.push(kwdefaults.into());
118            }
119        }
120
121        // Clear annotations and annotate (Py_CLEAR)
122        if let Some(mut guard) = self.annotations.try_lock()
123            && let Some(annotations) = guard.take()
124        {
125            out.push(annotations.into());
126        }
127        if let Some(mut guard) = self.annotate.try_lock()
128            && let Some(annotate) = guard.take()
129        {
130            out.push(annotate);
131        }
132
133        // Clear module, doc, and type_params (Py_CLEAR)
134        if let Some(mut guard) = self.module.try_lock() {
135            let old_module =
136                core::mem::replace(&mut *guard, Context::genesis().none.to_owned().into());
137            out.push(old_module);
138        }
139        if let Some(mut guard) = self.doc.try_lock() {
140            let old_doc =
141                core::mem::replace(&mut *guard, Context::genesis().none.to_owned().into());
142            out.push(old_doc);
143        }
144        if let Some(mut guard) = self.type_params.try_lock() {
145            let old_type_params =
146                core::mem::replace(&mut *guard, Context::genesis().empty_tuple.to_owned());
147            out.push(old_type_params.into());
148        }
149
150        // Replace name and qualname with empty string to break potential str subclass cycles
151        // name and qualname could be str subclasses, so they could have reference cycles
152        if let Some(mut guard) = self.name.try_lock() {
153            let old_name = core::mem::replace(&mut *guard, Context::genesis().empty_str.to_owned());
154            out.push(old_name.into());
155        }
156        if let Some(mut guard) = self.qualname.try_lock() {
157            let old_qualname =
158                core::mem::replace(&mut *guard, Context::genesis().empty_str.to_owned());
159            out.push(old_qualname.into());
160        }
161
162        // Note: globals, builtins, code are NOT cleared (required to be non-NULL)
163    }
164}
165
166impl PyFunction {
167    #[inline]
168    pub(crate) fn new(
169        code: PyRef<PyCode>,
170        globals: PyDictRef,
171        vm: &VirtualMachine,
172    ) -> PyResult<Self> {
173        let name = PyMutex::new(code.obj_name.to_owned());
174        let module = vm.unwrap_or_none(globals.get_item_opt(identifier!(vm, __name__), vm)?);
175        let builtins = globals.get_item("__builtins__", vm).unwrap_or_else(|_| {
176            // If not in globals, inherit from current execution context
177            if let Some(frame) = vm.current_frame() {
178                frame.builtins.clone()
179            } else {
180                vm.builtins.dict().into()
181            }
182        });
183        // If builtins is a module, use its __dict__ instead
184        let builtins = if let Some(module) = builtins.downcast_ref::<PyModule>() {
185            module.dict().into()
186        } else {
187            builtins
188        };
189
190        // Get docstring from co_consts[0] if HAS_DOCSTRING flag is set
191        let doc = if code.code.flags.contains(bytecode::CodeFlags::HAS_DOCSTRING) {
192            code.code
193                .constants
194                .first()
195                .map(|c| c.as_object().to_owned())
196                .unwrap_or_else(|| vm.ctx.none())
197        } else {
198            vm.ctx.none()
199        };
200
201        let qualname = vm.ctx.new_str(code.qualname.as_str());
202        let func = Self {
203            code: PyAtomicRef::from(code.clone()),
204            globals,
205            builtins,
206            closure: None,
207            defaults_and_kwdefaults: PyMutex::new((None, None)),
208            name,
209            qualname: PyMutex::new(qualname),
210            type_params: PyMutex::new(vm.ctx.empty_tuple.clone()),
211            annotations: PyMutex::new(None),
212            annotate: PyMutex::new(None),
213            module: PyMutex::new(module),
214            doc: PyMutex::new(doc),
215            func_version: AtomicU32::new(next_func_version()),
216            #[cfg(feature = "jit")]
217            jitted_code: PyMutex::new(None),
218        };
219        Ok(func)
220    }
221
222    fn fill_locals_from_args(
223        &self,
224        frame: &Frame,
225        func_args: FuncArgs,
226        vm: &VirtualMachine,
227    ) -> PyResult<()> {
228        let code: &Py<PyCode> = &self.code;
229        let nargs = func_args.args.len();
230        let n_expected_args = code.arg_count as usize;
231        let total_args = code.arg_count as usize + code.kwonlyarg_count as usize;
232        // let arg_names = self.code.arg_names();
233
234        // This parses the arguments from args and kwargs into
235        // the proper variables keeping into account default values
236        // and star-args and kwargs.
237        // See also: PyEval_EvalCodeWithName in cpython:
238        // https://github.com/python/cpython/blob/main/Python/ceval.c#L3681
239
240        // SAFETY: Frame was just created and not yet executing.
241        let fastlocals = unsafe { frame.fastlocals_mut() };
242
243        let mut args_iter = func_args.args.into_iter();
244
245        // Copy positional arguments into local variables
246        // zip short-circuits if either iterator returns None, which is the behavior we want --
247        // only fill as much as there is to fill with as much as we have
248        for (local, arg) in Iterator::zip(
249            fastlocals.iter_mut().take(n_expected_args),
250            args_iter.by_ref().take(nargs),
251        ) {
252            *local = Some(arg);
253        }
254
255        let mut vararg_offset = total_args;
256        // Pack other positional arguments in to *args:
257        if code.flags.contains(bytecode::CodeFlags::VARARGS) {
258            let vararg_value = vm.ctx.new_tuple(args_iter.collect());
259            fastlocals[vararg_offset] = Some(vararg_value.into());
260            vararg_offset += 1;
261        } else {
262            // Check the number of positional arguments
263            if nargs > n_expected_args {
264                let n_defaults = self
265                    .defaults_and_kwdefaults
266                    .lock()
267                    .0
268                    .as_ref()
269                    .map_or(0, |d| d.len());
270                let n_required = n_expected_args - n_defaults;
271                let takes_msg = if n_defaults > 0 {
272                    format!("from {} to {}", n_required, n_expected_args)
273                } else {
274                    n_expected_args.to_string()
275                };
276
277                // Count keyword-only arguments that were actually provided
278                let kw_only_given = if code.kwonlyarg_count > 0 {
279                    let start = code.arg_count as usize;
280                    let end = start + code.kwonlyarg_count as usize;
281                    code.varnames[start..end]
282                        .iter()
283                        .filter(|name| func_args.kwargs.contains_key(name.as_str()))
284                        .count()
285                } else {
286                    0
287                };
288
289                let given_msg = if kw_only_given > 0 {
290                    format!(
291                        "{} positional argument{} (and {} keyword-only argument{}) were",
292                        nargs,
293                        if nargs == 1 { "" } else { "s" },
294                        kw_only_given,
295                        if kw_only_given == 1 { "" } else { "s" },
296                    )
297                } else {
298                    format!("{} {}", nargs, if nargs == 1 { "was" } else { "were" })
299                };
300
301                return Err(vm.new_type_error(format!(
302                    "{}() takes {} positional argument{} but {} given",
303                    self.__qualname__(),
304                    takes_msg,
305                    if n_expected_args == 1 { "" } else { "s" },
306                    given_msg,
307                )));
308            }
309        }
310
311        // Do we support `**kwargs` ?
312        let kwargs = if code.flags.contains(bytecode::CodeFlags::VARKEYWORDS) {
313            let d = vm.ctx.new_dict();
314            fastlocals[vararg_offset] = Some(d.clone().into());
315            Some(d)
316        } else {
317            None
318        };
319
320        let arg_pos = |range: core::ops::Range<_>, name: &str| {
321            code.varnames
322                .iter()
323                .enumerate()
324                .skip(range.start)
325                .take(range.end - range.start)
326                .find(|(_, s)| s.as_str() == name)
327                .map(|(p, _)| p)
328        };
329
330        let mut posonly_passed_as_kwarg = Vec::new();
331        // Handle keyword arguments
332        for (name, value) in func_args.kwargs {
333            // Check if we have a parameter with this name:
334            if let Some(pos) = arg_pos(code.posonlyarg_count as usize..total_args, &name) {
335                let slot = &mut fastlocals[pos];
336                if slot.is_some() {
337                    return Err(vm.new_type_error(format!(
338                        "{}() got multiple values for argument '{}'",
339                        self.__qualname__(),
340                        name
341                    )));
342                }
343                *slot = Some(value);
344            } else if let Some(kwargs) = kwargs.as_ref() {
345                kwargs.set_item(&name, value, vm)?;
346            } else if arg_pos(0..code.posonlyarg_count as usize, &name).is_some() {
347                posonly_passed_as_kwarg.push(name);
348            } else {
349                return Err(vm.new_type_error(format!(
350                    "{}() got an unexpected keyword argument '{}'",
351                    self.__qualname__(),
352                    name
353                )));
354            }
355        }
356        if !posonly_passed_as_kwarg.is_empty() {
357            return Err(vm.new_type_error(format!(
358                "{}() got some positional-only arguments passed as keyword arguments: '{}'",
359                self.__qualname__(),
360                posonly_passed_as_kwarg.into_iter().format(", "),
361            )));
362        }
363
364        let mut defaults_and_kwdefaults = None;
365        // can't be a closure cause it returns a reference to a captured variable :/
366        macro_rules! get_defaults {
367            () => {{
368                defaults_and_kwdefaults
369                    .get_or_insert_with(|| self.defaults_and_kwdefaults.lock().clone())
370            }};
371        }
372
373        // Add missing positional arguments, if we have fewer positional arguments than the
374        // function definition calls for
375        if nargs < n_expected_args {
376            let defaults = get_defaults!().0.as_ref().map(|tup| tup.as_slice());
377            let n_defs = defaults.map_or(0, |d| d.len());
378
379            let n_required = code.arg_count as usize - n_defs;
380
381            // Given the number of defaults available, check all the arguments for which we
382            // _don't_ have defaults; if any are missing, raise an exception
383            let mut missing: Vec<_> = (nargs..n_required)
384                .filter_map(|i| {
385                    if fastlocals[i].is_none() {
386                        Some(&code.varnames[i])
387                    } else {
388                        None
389                    }
390                })
391                .collect();
392
393            if !missing.is_empty() {
394                return Err(vm.new_type_error(format_missing_args(
395                    self.__qualname__(),
396                    "positional",
397                    &mut missing,
398                )));
399            }
400
401            if let Some(defaults) = defaults {
402                let n = core::cmp::min(nargs, n_expected_args);
403                let i = n.saturating_sub(n_required);
404
405                // We have sufficient defaults, so iterate over the corresponding names and use
406                // the default if we don't already have a value
407                for i in i..defaults.len() {
408                    let slot = &mut fastlocals[n_required + i];
409                    if slot.is_none() {
410                        *slot = Some(defaults[i].clone());
411                    }
412                }
413            }
414        };
415
416        if code.kwonlyarg_count > 0 {
417            let mut missing = Vec::new();
418            // Check if kw only arguments are all present:
419            for (slot, kwarg) in fastlocals
420                .iter_mut()
421                .zip(&*code.varnames)
422                .skip(code.arg_count as usize)
423                .take(code.kwonlyarg_count as usize)
424                .filter(|(slot, _)| slot.is_none())
425            {
426                if let Some(defaults) = &get_defaults!().1
427                    && let Some(default) = defaults.get_item_opt(&**kwarg, vm)?
428                {
429                    *slot = Some(default);
430                    continue;
431                }
432
433                // No default value and not specified.
434                missing.push(kwarg);
435            }
436
437            if !missing.is_empty() {
438                return Err(vm.new_type_error(format_missing_args(
439                    self.__qualname__(),
440                    "keyword-only",
441                    &mut missing,
442                )));
443            }
444        }
445
446        Ok(())
447    }
448
449    /// Set function attribute based on MakeFunctionFlags
450    pub(crate) fn set_function_attribute(
451        &mut self,
452        attr: bytecode::MakeFunctionFlag,
453        attr_value: PyObjectRef,
454        vm: &VirtualMachine,
455    ) -> PyResult<()> {
456        use crate::builtins::PyDict;
457        match attr {
458            bytecode::MakeFunctionFlag::Defaults => {
459                let defaults = match attr_value.downcast::<PyTuple>() {
460                    Ok(tuple) => tuple,
461                    Err(obj) => {
462                        return Err(vm.new_type_error(format!(
463                            "__defaults__ must be a tuple, not {}",
464                            obj.class().name()
465                        )));
466                    }
467                };
468                self.defaults_and_kwdefaults.lock().0 = Some(defaults);
469            }
470            bytecode::MakeFunctionFlag::KwOnlyDefaults => {
471                let kwdefaults = match attr_value.downcast::<PyDict>() {
472                    Ok(dict) => dict,
473                    Err(obj) => {
474                        return Err(vm.new_type_error(format!(
475                            "__kwdefaults__ must be a dict, not {}",
476                            obj.class().name()
477                        )));
478                    }
479                };
480                self.defaults_and_kwdefaults.lock().1 = Some(kwdefaults);
481            }
482            bytecode::MakeFunctionFlag::Annotations => {
483                let annotations = match attr_value.downcast::<PyDict>() {
484                    Ok(dict) => dict,
485                    Err(obj) => {
486                        return Err(vm.new_type_error(format!(
487                            "__annotations__ must be a dict, not {}",
488                            obj.class().name()
489                        )));
490                    }
491                };
492                *self.annotations.lock() = Some(annotations);
493            }
494            bytecode::MakeFunctionFlag::Closure => {
495                let closure_tuple = attr_value
496                    .clone()
497                    .downcast_exact::<PyTuple>(vm)
498                    .map_err(|obj| {
499                        vm.new_type_error(format!(
500                            "closure must be a tuple, not {}",
501                            obj.class().name()
502                        ))
503                    })?
504                    .into_pyref();
505
506                self.closure = Some(closure_tuple.try_into_typed::<PyCell>(vm)?);
507            }
508            bytecode::MakeFunctionFlag::TypeParams => {
509                let type_params = attr_value.clone().downcast::<PyTuple>().map_err(|_| {
510                    vm.new_type_error(format!(
511                        "__type_params__ must be a tuple, not {}",
512                        attr_value.class().name()
513                    ))
514                })?;
515                *self.type_params.lock() = type_params;
516            }
517            bytecode::MakeFunctionFlag::Annotate => {
518                if !attr_value.is_callable() {
519                    return Err(vm.new_type_error("__annotate__ must be callable"));
520                }
521                *self.annotate.lock() = Some(attr_value);
522            }
523        }
524        Ok(())
525    }
526}
527
528impl Py<PyFunction> {
529    pub(crate) fn is_optimized_for_call_specialization(&self) -> bool {
530        self.code.flags.contains(bytecode::CodeFlags::OPTIMIZED)
531    }
532
533    pub fn invoke_with_locals(
534        &self,
535        func_args: FuncArgs,
536        locals: Option<ArgMapping>,
537        vm: &VirtualMachine,
538    ) -> PyResult {
539        #[cfg(feature = "jit")]
540        if let Some(jitted_code) = self.jitted_code.lock().as_ref() {
541            use crate::convert::ToPyObject;
542            match jit::get_jit_args(self, &func_args, jitted_code, vm) {
543                Ok(args) => {
544                    return Ok(args.invoke().to_pyobject(vm));
545                }
546                Err(err) => info!(
547                    "jit: function `{}` is falling back to being interpreted because of the \
548                    error: {}",
549                    self.code.obj_name, err
550                ),
551            }
552        }
553
554        let code: PyRef<PyCode> = (*self.code).to_owned();
555
556        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
557            None
558        } else if let Some(locals) = locals {
559            Some(locals)
560        } else {
561            Some(ArgMapping::from_dict_exact(self.globals.clone()))
562        };
563
564        let is_gen = code.flags.contains(bytecode::CodeFlags::GENERATOR);
565        let is_coro = code.flags.contains(bytecode::CodeFlags::COROUTINE);
566        let use_datastack = !(is_gen || is_coro);
567
568        // Construct frame:
569        let frame = Frame::new(
570            code,
571            Scope::new(locals, self.globals.clone()),
572            self.builtins.clone(),
573            self.closure.as_ref().map_or(&[], |c| c.as_slice()),
574            Some(self.to_owned().into()),
575            use_datastack,
576            vm,
577        )
578        .into_ref(&vm.ctx);
579
580        self.fill_locals_from_args(&frame, func_args, vm)?;
581        match (is_gen, is_coro) {
582            (true, false) => {
583                let obj = PyGenerator::new(frame.clone(), self.__name__(), self.__qualname__())
584                    .into_pyobject(vm);
585                frame.set_generator(&obj);
586                Ok(obj)
587            }
588            (false, true) => {
589                let obj = PyCoroutine::new(frame.clone(), self.__name__(), self.__qualname__())
590                    .into_pyobject(vm);
591                frame.set_generator(&obj);
592                Ok(obj)
593            }
594            (true, true) => {
595                let obj = PyAsyncGen::new(frame.clone(), self.__name__(), self.__qualname__())
596                    .into_pyobject(vm);
597                frame.set_generator(&obj);
598                Ok(obj)
599            }
600            (false, false) => {
601                let result = vm.run_frame(frame.clone());
602                // Release data stack memory after frame execution completes.
603                unsafe {
604                    if let Some(base) = frame.materialize_localsplus() {
605                        vm.datastack_pop(base);
606                    }
607                }
608                result
609            }
610        }
611    }
612
613    #[inline(always)]
614    pub fn invoke(&self, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
615        self.invoke_with_locals(func_args, None, vm)
616    }
617
618    /// Returns the function version, or 0 if invalidated.
619    #[inline]
620    pub fn func_version(&self) -> u32 {
621        self.func_version.load(Relaxed)
622    }
623
624    /// Returns the current version, assigning a fresh one if previously invalidated.
625    /// Returns 0 if the version counter has overflowed.
626    /// `_PyFunction_GetVersionForCurrentState`
627    pub fn get_version_for_current_state(&self) -> u32 {
628        let v = self.func_version.load(Relaxed);
629        if v != 0 {
630            return v;
631        }
632        let new_v = next_func_version();
633        if new_v == 0 {
634            return 0;
635        }
636        self.func_version.store(new_v, Relaxed);
637        new_v
638    }
639
640    /// function_kind(SIMPLE_FUNCTION) equivalent for CALL specialization.
641    /// Returns true if: CO_OPTIMIZED, no VARARGS, no VARKEYWORDS, no kwonly args.
642    pub(crate) fn is_simple_for_call_specialization(&self) -> bool {
643        let code: &Py<PyCode> = &self.code;
644        let flags = code.flags;
645        flags.contains(bytecode::CodeFlags::OPTIMIZED)
646            && !flags.intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
647            && code.kwonlyarg_count == 0
648    }
649
650    /// Check if this function is eligible for exact-args call specialization.
651    /// Returns true if: CO_OPTIMIZED, no VARARGS, no VARKEYWORDS, no kwonly args,
652    /// and effective_nargs matches co_argcount.
653    pub(crate) fn can_specialize_call(&self, effective_nargs: u32) -> bool {
654        let code: &Py<PyCode> = &self.code;
655        let flags = code.flags;
656        flags.contains(bytecode::CodeFlags::OPTIMIZED)
657            && !flags.intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
658            && code.kwonlyarg_count == 0
659            && code.arg_count == effective_nargs
660    }
661
662    /// Runtime guard for CALL_*_EXACT_ARGS specialization: check only argcount.
663    /// Other invariants are guaranteed by function versioning and specialization-time checks.
664    #[inline]
665    pub(crate) fn has_exact_argcount(&self, effective_nargs: u32) -> bool {
666        self.code.arg_count == effective_nargs
667    }
668
669    /// Bytes required for this function's frame on RustPython's thread datastack.
670    /// Returns `None` for generator/coroutine code paths that do not push a
671    /// regular datastack-backed frame in the fast call path.
672    pub(crate) fn datastack_frame_size_bytes(&self) -> Option<usize> {
673        datastack_frame_size_bytes_for_code(&self.code)
674    }
675
676    pub(crate) fn prepare_exact_args_frame(
677        &self,
678        mut args: Vec<PyObjectRef>,
679        vm: &VirtualMachine,
680    ) -> FrameRef {
681        let code: PyRef<PyCode> = (*self.code).to_owned();
682
683        debug_assert_eq!(args.len(), code.arg_count as usize);
684        debug_assert!(code.flags.contains(bytecode::CodeFlags::OPTIMIZED));
685        debug_assert!(
686            !code
687                .flags
688                .intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
689        );
690        debug_assert_eq!(code.kwonlyarg_count, 0);
691        debug_assert!(
692            !code
693                .flags
694                .intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE)
695        );
696
697        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
698            None
699        } else {
700            Some(ArgMapping::from_dict_exact(self.globals.clone()))
701        };
702
703        let frame = Frame::new(
704            code.clone(),
705            Scope::new(locals, self.globals.clone()),
706            self.builtins.clone(),
707            self.closure.as_ref().map_or(&[], |c| c.as_slice()),
708            Some(self.to_owned().into()),
709            true, // Exact-args fast path is only used for non-gen/coro functions.
710            vm,
711        )
712        .into_ref(&vm.ctx);
713
714        {
715            let fastlocals = unsafe { frame.fastlocals_mut() };
716            for (slot, arg) in fastlocals.iter_mut().zip(args.drain(..)) {
717                *slot = Some(arg);
718            }
719        }
720
721        frame
722    }
723
724    /// Fast path for calling a simple function with exact positional args.
725    /// Skips FuncArgs allocation, prepend_arg, and fill_locals_from_args.
726    /// Only valid when: CO_OPTIMIZED, no VARARGS, no VARKEYWORDS, no kwonlyargs,
727    /// and nargs == co_argcount.
728    pub fn invoke_exact_args(&self, args: Vec<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
729        let code: PyRef<PyCode> = (*self.code).to_owned();
730
731        debug_assert_eq!(args.len(), code.arg_count as usize);
732        debug_assert!(code.flags.contains(bytecode::CodeFlags::OPTIMIZED));
733        debug_assert!(
734            !code
735                .flags
736                .intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
737        );
738        debug_assert_eq!(code.kwonlyarg_count, 0);
739
740        // Generator/coroutine code objects are SIMPLE_FUNCTION in call
741        // specialization classification, but their call path must still
742        // go through invoke() to produce generator/coroutine objects.
743        if code
744            .flags
745            .intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE)
746        {
747            return self.invoke(FuncArgs::from(args), vm);
748        }
749        let frame = self.prepare_exact_args_frame(args, vm);
750
751        let result = vm.run_frame(frame.clone());
752        unsafe {
753            if let Some(base) = frame.materialize_localsplus() {
754                vm.datastack_pop(base);
755            }
756        }
757        result
758    }
759}
760
761pub(crate) fn datastack_frame_size_bytes_for_code(code: &Py<PyCode>) -> Option<usize> {
762    if code
763        .flags
764        .intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE)
765    {
766        return None;
767    }
768    let nlocalsplus = code.localspluskinds.len();
769    let capacity = nlocalsplus.checked_add(code.max_stackdepth as usize)?;
770    capacity.checked_mul(core::mem::size_of::<usize>())
771}
772
773impl PyPayload for PyFunction {
774    #[inline]
775    fn class(ctx: &Context) -> &'static Py<PyType> {
776        ctx.types.function_type
777    }
778}
779
780#[pyclass(
781    with(GetDescriptor, Callable, Representable, Constructor),
782    flags(HAS_DICT, HAS_WEAKREF, METHOD_DESCRIPTOR)
783)]
784impl PyFunction {
785    #[pygetset]
786    fn __code__(&self) -> PyRef<PyCode> {
787        (*self.code).to_owned()
788    }
789
790    #[pygetset(setter)]
791    fn set___code__(&self, code: PyRef<PyCode>, vm: &VirtualMachine) {
792        #[cfg(feature = "jit")]
793        let mut jit_guard = self.jitted_code.lock();
794        self.code.swap_to_temporary_refs(code, vm);
795        #[cfg(feature = "jit")]
796        {
797            *jit_guard = None;
798        }
799        self.func_version.store(0, Relaxed);
800    }
801
802    #[pygetset]
803    fn __defaults__(&self) -> Option<PyTupleRef> {
804        self.defaults_and_kwdefaults.lock().0.clone()
805    }
806    #[pygetset(setter)]
807    fn set___defaults__(&self, defaults: Option<PyTupleRef>) {
808        self.defaults_and_kwdefaults.lock().0 = defaults;
809        self.func_version.store(0, Relaxed);
810    }
811
812    #[pygetset]
813    fn __kwdefaults__(&self) -> Option<PyDictRef> {
814        self.defaults_and_kwdefaults.lock().1.clone()
815    }
816    #[pygetset(setter)]
817    fn set___kwdefaults__(&self, kwdefaults: Option<PyDictRef>) {
818        self.defaults_and_kwdefaults.lock().1 = kwdefaults;
819        self.func_version.store(0, Relaxed);
820    }
821
822    // {"__closure__",   T_OBJECT,     OFF(func_closure), READONLY},
823    // {"__doc__",       T_OBJECT,     OFF(func_doc), 0},
824    // {"__globals__",   T_OBJECT,     OFF(func_globals), READONLY},
825    // {"__module__",    T_OBJECT,     OFF(func_module), 0},
826    // {"__builtins__",  T_OBJECT,     OFF(func_builtins), READONLY},
827    #[pymember]
828    fn __globals__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
829        let zelf = Self::_as_pyref(&zelf, vm)?;
830        Ok(zelf.globals.clone().into())
831    }
832
833    #[pymember]
834    fn __closure__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
835        let zelf = Self::_as_pyref(&zelf, vm)?;
836        Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.into())))
837    }
838
839    #[pymember]
840    fn __builtins__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
841        let zelf = Self::_as_pyref(&zelf, vm)?;
842        Ok(zelf.builtins.clone())
843    }
844
845    #[pygetset]
846    fn __name__(&self) -> PyStrRef {
847        self.name.lock().clone()
848    }
849
850    #[pygetset(setter)]
851    fn set___name__(&self, name: PyStrRef) {
852        *self.name.lock() = name;
853    }
854
855    #[pymember]
856    fn __doc__(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult {
857        // When accessed from instance, obj is the PyFunction instance
858        if let Ok(func) = obj.downcast::<Self>() {
859            let doc = func.doc.lock();
860            Ok(doc.clone())
861        } else {
862            // When accessed from class, return None as there's no instance
863            Ok(vm.ctx.none())
864        }
865    }
866
867    #[pymember(setter)]
868    fn set___doc__(vm: &VirtualMachine, zelf: PyObjectRef, value: PySetterValue) -> PyResult<()> {
869        let zelf: PyRef<Self> = zelf.downcast().unwrap_or_else(|_| unreachable!());
870        let value = value.unwrap_or_none(vm);
871        *zelf.doc.lock() = value;
872        Ok(())
873    }
874
875    #[pygetset]
876    fn __module__(&self) -> PyObjectRef {
877        self.module.lock().clone()
878    }
879
880    #[pygetset(setter)]
881    fn set___module__(&self, module: PySetterValue<PyObjectRef>, vm: &VirtualMachine) {
882        *self.module.lock() = module.unwrap_or_none(vm);
883    }
884
885    #[pygetset]
886    fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyDictRef> {
887        // First check if we have cached annotations
888        {
889            let annotations = self.annotations.lock();
890            if let Some(ref ann) = *annotations {
891                return Ok(ann.clone());
892            }
893        }
894
895        // Check for callable __annotate__ and clone it before calling
896        let annotate_fn = {
897            let annotate = self.annotate.lock();
898            if let Some(ref func) = *annotate
899                && func.is_callable()
900            {
901                Some(func.clone())
902            } else {
903                None
904            }
905        };
906
907        // Release locks before calling __annotate__ to avoid deadlock
908        if let Some(annotate_fn) = annotate_fn {
909            let one = vm.ctx.new_int(1);
910            let ann_dict = annotate_fn.call((one,), vm)?;
911            let ann_dict = ann_dict
912                .downcast::<crate::builtins::PyDict>()
913                .map_err(|obj| {
914                    vm.new_type_error(format!(
915                        "__annotate__ returned non-dict of type '{}'",
916                        obj.class().name()
917                    ))
918                })?;
919
920            // Cache the result
921            *self.annotations.lock() = Some(ann_dict.clone());
922            return Ok(ann_dict);
923        }
924
925        // No __annotate__ or not callable, create empty dict
926        let new_dict = vm.ctx.new_dict();
927        *self.annotations.lock() = Some(new_dict.clone());
928        Ok(new_dict)
929    }
930
931    #[pygetset(setter)]
932    fn set___annotations__(
933        &self,
934        value: PySetterValue<Option<PyObjectRef>>,
935        vm: &VirtualMachine,
936    ) -> PyResult<()> {
937        match value {
938            PySetterValue::Assign(Some(value)) => {
939                let annotations = value.downcast::<crate::builtins::PyDict>().map_err(|_| {
940                    vm.new_type_error("__annotations__ must be set to a dict object")
941                })?;
942                *self.annotations.lock() = Some(annotations);
943                *self.annotate.lock() = None;
944            }
945            PySetterValue::Assign(None) => {
946                *self.annotations.lock() = None;
947                *self.annotate.lock() = None;
948            }
949            PySetterValue::Delete => {
950                // del only clears cached annotations; __annotate__ is preserved
951                *self.annotations.lock() = None;
952            }
953        }
954        Ok(())
955    }
956
957    #[pygetset]
958    fn __annotate__(&self, vm: &VirtualMachine) -> PyObjectRef {
959        self.annotate
960            .lock()
961            .clone()
962            .unwrap_or_else(|| vm.ctx.none())
963    }
964
965    #[pygetset(setter)]
966    fn set___annotate__(
967        &self,
968        value: PySetterValue<Option<PyObjectRef>>,
969        vm: &VirtualMachine,
970    ) -> PyResult<()> {
971        let annotate = match value {
972            PySetterValue::Assign(Some(value)) => {
973                if !value.is_callable() {
974                    return Err(vm.new_type_error("__annotate__ must be callable or None"));
975                }
976                // Clear cached __annotations__ when __annotate__ is set
977                *self.annotations.lock() = None;
978                Some(value)
979            }
980            PySetterValue::Assign(None) => None,
981            PySetterValue::Delete => {
982                return Err(vm.new_type_error("__annotate__ cannot be deleted"));
983            }
984        };
985        *self.annotate.lock() = annotate;
986        Ok(())
987    }
988
989    #[pygetset]
990    fn __qualname__(&self) -> PyStrRef {
991        self.qualname.lock().clone()
992    }
993
994    #[pygetset(setter)]
995    fn set___qualname__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
996        match value {
997            PySetterValue::Assign(value) => {
998                let Ok(qualname) = value.downcast::<PyStr>() else {
999                    return Err(vm.new_type_error("__qualname__ must be set to a string object"));
1000                };
1001                *self.qualname.lock() = qualname;
1002            }
1003            PySetterValue::Delete => {
1004                return Err(vm.new_type_error("__qualname__ must be set to a string object"));
1005            }
1006        }
1007        Ok(())
1008    }
1009
1010    #[pygetset]
1011    fn __type_params__(&self) -> PyTupleRef {
1012        self.type_params.lock().clone()
1013    }
1014
1015    #[pygetset(setter)]
1016    fn set___type_params__(
1017        &self,
1018        value: PySetterValue<PyTupleRef>,
1019        vm: &VirtualMachine,
1020    ) -> PyResult<()> {
1021        match value {
1022            PySetterValue::Assign(value) => {
1023                *self.type_params.lock() = value;
1024            }
1025            PySetterValue::Delete => {
1026                return Err(vm.new_type_error("__type_params__ must be set to a tuple object"));
1027            }
1028        }
1029        Ok(())
1030    }
1031
1032    #[cfg(feature = "jit")]
1033    #[pymethod]
1034    fn __jit__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> {
1035        let mut jit_guard = zelf.jitted_code.lock();
1036        if jit_guard.is_some() {
1037            return Ok(());
1038        }
1039        let arg_types = jit::get_jit_arg_types(&zelf, vm)?;
1040        let ret_type = jit::jit_ret_type(&zelf, vm)?;
1041        let code: &Py<PyCode> = &zelf.code;
1042        let compiled = rustpython_jit::compile(&code.code, &arg_types, ret_type)
1043            .map_err(|err| jit::new_jit_error(err.to_string(), vm))?;
1044        *jit_guard = Some(compiled);
1045        Ok(())
1046    }
1047}
1048
1049impl GetDescriptor for PyFunction {
1050    fn descr_get(
1051        zelf: PyObjectRef,
1052        obj: Option<PyObjectRef>,
1053        cls: Option<PyObjectRef>,
1054        vm: &VirtualMachine,
1055    ) -> PyResult {
1056        let (_zelf, obj) = Self::_unwrap(&zelf, obj, vm)?;
1057        Ok(if vm.is_none(&obj) && !Self::_cls_is(&cls, obj.class()) {
1058            zelf
1059        } else {
1060            PyBoundMethod::new(obj, zelf).into_ref(&vm.ctx).into()
1061        })
1062    }
1063}
1064
1065impl Callable for PyFunction {
1066    type Args = FuncArgs;
1067    #[inline]
1068    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1069        zelf.invoke(args, vm)
1070    }
1071}
1072
1073impl Representable for PyFunction {
1074    #[inline]
1075    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
1076        Ok(format!(
1077            "<function {} at {:#x}>",
1078            zelf.__qualname__(),
1079            zelf.get_id()
1080        ))
1081    }
1082}
1083
1084#[derive(FromArgs)]
1085pub struct PyFunctionNewArgs {
1086    #[pyarg(positional)]
1087    code: PyRef<PyCode>,
1088    #[pyarg(positional)]
1089    globals: PyDictRef,
1090    #[pyarg(any, optional, error_msg = "arg 3 (name) must be None or string")]
1091    name: OptionalArg<PyStrRef>,
1092    #[pyarg(any, optional, error_msg = "arg 4 (defaults) must be None or tuple")]
1093    argdefs: Option<PyTupleRef>,
1094    #[pyarg(any, optional, error_msg = "arg 5 (closure) must be None or tuple")]
1095    closure: Option<PyTupleRef>,
1096    #[pyarg(any, optional, error_msg = "arg 6 (kwdefaults) must be None or dict")]
1097    kwdefaults: Option<PyDictRef>,
1098}
1099
1100impl Constructor for PyFunction {
1101    type Args = PyFunctionNewArgs;
1102
1103    fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
1104        // Handle closure - must be a tuple of cells
1105        let closure = if let Some(closure_tuple) = args.closure {
1106            // Check that closure length matches code's free variables
1107            if closure_tuple.len() != args.code.freevars.len() {
1108                return Err(vm.new_value_error(format!(
1109                    "{} requires closure of length {}, not {}",
1110                    args.code.obj_name,
1111                    args.code.freevars.len(),
1112                    closure_tuple.len()
1113                )));
1114            }
1115
1116            // Validate that all items are cells and create typed tuple
1117            let typed_closure = closure_tuple.try_into_typed::<PyCell>(vm)?;
1118            Some(typed_closure)
1119        } else if !args.code.freevars.is_empty() {
1120            return Err(vm.new_type_error("arg 5 (closure) must be tuple"));
1121        } else {
1122            None
1123        };
1124
1125        let mut func = Self::new(args.code.clone(), args.globals.clone(), vm)?;
1126        // Set function name if provided
1127        if let Some(name) = args.name.into_option() {
1128            *func.name.lock() = name.clone();
1129            // Also update qualname to match the name
1130            *func.qualname.lock() = name;
1131        }
1132        // Now set additional attributes directly
1133        if let Some(closure_tuple) = closure {
1134            func.closure = Some(closure_tuple);
1135        }
1136        if let Some(argdefs) = args.argdefs {
1137            func.defaults_and_kwdefaults.lock().0 = Some(argdefs);
1138        }
1139        if let Some(kwdefaults) = args.kwdefaults {
1140            func.defaults_and_kwdefaults.lock().1 = Some(kwdefaults);
1141        }
1142
1143        Ok(func)
1144    }
1145}
1146
1147#[pyclass(module = false, name = "method", traverse)]
1148#[derive(Debug)]
1149pub struct PyBoundMethod {
1150    object: PyObjectRef,
1151    function: PyObjectRef,
1152}
1153
1154impl Callable for PyBoundMethod {
1155    type Args = FuncArgs;
1156    #[inline]
1157    fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1158        args.prepend_arg(zelf.object.clone());
1159        zelf.function.call(args, vm)
1160    }
1161}
1162
1163impl Comparable for PyBoundMethod {
1164    fn cmp(
1165        zelf: &Py<Self>,
1166        other: &PyObject,
1167        op: PyComparisonOp,
1168        _vm: &VirtualMachine,
1169    ) -> PyResult<PyComparisonValue> {
1170        op.eq_only(|| {
1171            let other = class_or_notimplemented!(Self, other);
1172            Ok(PyComparisonValue::Implemented(
1173                zelf.function.is(&other.function) && zelf.object.is(&other.object),
1174            ))
1175        })
1176    }
1177}
1178
1179impl Hashable for PyBoundMethod {
1180    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
1181        let self_hash = crate::common::hash::hash_object_id_raw(zelf.object.get_id());
1182        let func_hash = zelf.function.hash(vm)?;
1183        Ok(crate::common::hash::fix_sentinel(self_hash ^ func_hash))
1184    }
1185}
1186
1187impl GetAttr for PyBoundMethod {
1188    fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
1189        let class_attr = vm
1190            .ctx
1191            .interned_str(name)
1192            .and_then(|attr_name| zelf.get_class_attr(attr_name));
1193        if let Some(obj) = class_attr {
1194            return vm.call_if_get_descriptor(&obj, zelf.to_owned().into());
1195        }
1196        zelf.function.get_attr(name, vm)
1197    }
1198}
1199
1200impl GetDescriptor for PyBoundMethod {
1201    fn descr_get(
1202        zelf: PyObjectRef,
1203        _obj: Option<PyObjectRef>,
1204        _cls: Option<PyObjectRef>,
1205        _vm: &VirtualMachine,
1206    ) -> PyResult {
1207        Ok(zelf)
1208    }
1209}
1210
1211#[derive(FromArgs)]
1212pub struct PyBoundMethodNewArgs {
1213    #[pyarg(positional)]
1214    function: PyObjectRef,
1215    #[pyarg(positional)]
1216    object: PyObjectRef,
1217}
1218
1219impl Constructor for PyBoundMethod {
1220    type Args = PyBoundMethodNewArgs;
1221
1222    fn py_new(
1223        _cls: &Py<PyType>,
1224        Self::Args { function, object }: Self::Args,
1225        vm: &VirtualMachine,
1226    ) -> PyResult<Self> {
1227        if !function.is_callable() {
1228            return Err(vm.new_type_error("first argument must be callable".to_owned()));
1229        }
1230        if vm.is_none(&object) {
1231            return Err(vm.new_type_error("instance must not be None".to_owned()));
1232        }
1233        Ok(Self::new(object, function))
1234    }
1235}
1236
1237impl PyBoundMethod {
1238    pub const fn new(object: PyObjectRef, function: PyObjectRef) -> Self {
1239        Self { object, function }
1240    }
1241
1242    #[inline]
1243    pub(crate) fn function_obj(&self) -> &PyObjectRef {
1244        &self.function
1245    }
1246
1247    #[inline]
1248    pub(crate) fn self_obj(&self) -> &PyObjectRef {
1249        &self.object
1250    }
1251
1252    #[deprecated(note = "Use `Self::new(object, function).into_ref(ctx)` instead")]
1253    pub fn new_ref(object: PyObjectRef, function: PyObjectRef, ctx: &Context) -> PyRef<Self> {
1254        Self::new(object, function).into_ref(ctx)
1255    }
1256}
1257
1258#[pyclass(
1259    with(
1260        Callable,
1261        Comparable,
1262        Hashable,
1263        GetAttr,
1264        GetDescriptor,
1265        Constructor,
1266        Representable
1267    ),
1268    flags(IMMUTABLETYPE, HAS_WEAKREF)
1269)]
1270impl PyBoundMethod {
1271    #[pymethod]
1272    fn __reduce__(
1273        &self,
1274        vm: &VirtualMachine,
1275    ) -> PyResult<(PyObjectRef, (PyObjectRef, PyObjectRef))> {
1276        let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
1277        let func_self = self.object.clone();
1278        let func_name = self.function.get_attr("__name__", vm)?;
1279        Ok((builtins_getattr, (func_self, func_name)))
1280    }
1281
1282    #[pygetset]
1283    fn __doc__(&self, vm: &VirtualMachine) -> PyResult {
1284        self.function.get_attr("__doc__", vm)
1285    }
1286
1287    #[pygetset]
1288    fn __func__(&self) -> PyObjectRef {
1289        self.function.clone()
1290    }
1291
1292    #[pygetset(name = "__self__")]
1293    fn get_self(&self) -> PyObjectRef {
1294        self.object.clone()
1295    }
1296
1297    #[pygetset]
1298    fn __module__(&self, vm: &VirtualMachine) -> Option<PyObjectRef> {
1299        self.function.get_attr("__module__", vm).ok()
1300    }
1301}
1302
1303impl PyPayload for PyBoundMethod {
1304    #[inline]
1305    fn class(ctx: &Context) -> &'static Py<PyType> {
1306        ctx.types.bound_method_type
1307    }
1308}
1309
1310impl Representable for PyBoundMethod {
1311    #[inline]
1312    fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
1313        let func_name = if let Some(qname) =
1314            vm.get_attribute_opt(zelf.function.clone(), identifier!(vm, __qualname__))?
1315        {
1316            Some(qname)
1317        } else {
1318            vm.get_attribute_opt(zelf.function.clone(), identifier!(vm, __name__))?
1319        };
1320        let func_name: Option<PyStrRef> = func_name.and_then(|o| o.downcast().ok());
1321        let object_repr = zelf.object.repr(vm)?;
1322        let name = func_name.as_ref().map_or("?".as_ref(), |s| s.as_wtf8());
1323        Ok(wtf8_concat!(
1324            "<bound method ",
1325            name,
1326            " of ",
1327            object_repr.as_wtf8(),
1328            ">"
1329        ))
1330    }
1331}
1332
1333#[pyclass(module = false, name = "cell", traverse)]
1334#[derive(Debug, Default)]
1335pub(crate) struct PyCell {
1336    contents: PyMutex<Option<PyObjectRef>>,
1337}
1338pub(crate) type PyCellRef = PyRef<PyCell>;
1339
1340impl PyPayload for PyCell {
1341    #[inline]
1342    fn class(ctx: &Context) -> &'static Py<PyType> {
1343        ctx.types.cell_type
1344    }
1345}
1346
1347impl Constructor for PyCell {
1348    type Args = OptionalArg;
1349
1350    fn py_new(_cls: &Py<PyType>, value: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
1351        Ok(Self::new(value.into_option()))
1352    }
1353}
1354
1355#[pyclass(with(Constructor))]
1356impl PyCell {
1357    pub const fn new(contents: Option<PyObjectRef>) -> Self {
1358        Self {
1359            contents: PyMutex::new(contents),
1360        }
1361    }
1362
1363    pub fn get(&self) -> Option<PyObjectRef> {
1364        self.contents.lock().clone()
1365    }
1366    pub fn set(&self, x: Option<PyObjectRef>) {
1367        *self.contents.lock() = x;
1368    }
1369
1370    #[pygetset]
1371    fn cell_contents(&self, vm: &VirtualMachine) -> PyResult {
1372        self.get()
1373            .ok_or_else(|| vm.new_value_error("Cell is empty"))
1374    }
1375    #[pygetset(setter)]
1376    fn set_cell_contents(&self, x: PySetterValue) {
1377        match x {
1378            PySetterValue::Assign(value) => self.set(Some(value)),
1379            PySetterValue::Delete => self.set(None),
1380        }
1381    }
1382}
1383
1384/// Vectorcall implementation for PyFunction (PEP 590).
1385/// Takes owned args to avoid cloning when filling fastlocals.
1386pub(crate) fn vectorcall_function(
1387    zelf_obj: &PyObject,
1388    mut args: Vec<PyObjectRef>,
1389    nargs: usize,
1390    kwnames: Option<&[PyObjectRef]>,
1391    vm: &VirtualMachine,
1392) -> PyResult {
1393    let zelf: &Py<PyFunction> = zelf_obj.downcast_ref().unwrap();
1394    let code: &Py<PyCode> = &zelf.code;
1395
1396    let has_kwargs = kwnames.is_some_and(|kw| !kw.is_empty());
1397    let is_simple = !has_kwargs
1398        && code.flags.contains(bytecode::CodeFlags::OPTIMIZED)
1399        && !code.flags.contains(bytecode::CodeFlags::VARARGS)
1400        && !code.flags.contains(bytecode::CodeFlags::VARKEYWORDS)
1401        && code.kwonlyarg_count == 0
1402        && !code
1403            .flags
1404            .intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE);
1405
1406    if is_simple && nargs == code.arg_count as usize {
1407        // FAST PATH: simple positional-only call, exact arg count.
1408        // Move owned args directly into fastlocals — no clone needed.
1409        args.truncate(nargs);
1410        let frame = zelf.prepare_exact_args_frame(args, vm);
1411
1412        let result = vm.run_frame(frame.clone());
1413        unsafe {
1414            if let Some(base) = frame.materialize_localsplus() {
1415                vm.datastack_pop(base);
1416            }
1417        }
1418        return result;
1419    }
1420
1421    // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
1422    let func_args = if has_kwargs {
1423        FuncArgs::from_vectorcall(&args, nargs, kwnames)
1424    } else {
1425        args.truncate(nargs);
1426        FuncArgs::from(args)
1427    };
1428    zelf.invoke(func_args, vm)
1429}
1430
1431/// Vectorcall implementation for PyBoundMethod (PEP 590).
1432fn vectorcall_bound_method(
1433    zelf_obj: &PyObject,
1434    mut args: Vec<PyObjectRef>,
1435    nargs: usize,
1436    kwnames: Option<&[PyObjectRef]>,
1437    vm: &VirtualMachine,
1438) -> PyResult {
1439    let zelf: &Py<PyBoundMethod> = zelf_obj.downcast_ref().unwrap();
1440
1441    // Insert self at front of existing Vec (avoids 2nd allocation).
1442    // O(n) memmove is cheaper than a 2nd heap alloc+dealloc for typical arg counts.
1443    args.insert(0, zelf.object.clone());
1444    let new_nargs = nargs + 1;
1445    zelf.function.vectorcall(args, new_nargs, kwnames, vm)
1446}
1447
1448pub fn init(context: &'static Context) {
1449    PyFunction::extend_class(context, context.types.function_type);
1450    context
1451        .types
1452        .function_type
1453        .slots
1454        .vectorcall
1455        .store(Some(vectorcall_function));
1456
1457    PyBoundMethod::extend_class(context, context.types.bound_method_type);
1458    context
1459        .types
1460        .bound_method_type
1461        .slots
1462        .vectorcall
1463        .store(Some(vectorcall_bound_method));
1464
1465    PyCell::extend_class(context, context.types.cell_type);
1466}