Skip to main content

rustpython_vm/builtins/
function.rs

1#[cfg(feature = "jit")]
2mod jit;
3
4use super::{
5    PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyList, PyModule, PyStr, PyStrRef,
6    PyTuple, PyTupleRef, PyType, object,
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::{FrameObject, FrameObjectRef},
18    function::{Either, 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
37    let last = if missing.len() > 1 {
38        missing.pop()
39    } else {
40        None
41    };
42
43    let (and, right): (&str, String) = if let Some(last) = last {
44        (
45            if missing.len() == 1 {
46                "' and '"
47            } else {
48                "', and '"
49            },
50            last.to_string(),
51        )
52    } else {
53        ("", String::new())
54    };
55
56    format!(
57        "{qualname}() missing {count} required {kind} argument{}: '{}{}{right}'",
58        if count == 1 { "" } else { "s" },
59        missing.iter().join("', '"),
60        and,
61    )
62}
63
64#[pyclass(module = false, name = "function", traverse = "manual")]
65#[derive(Debug)]
66pub struct PyFunction {
67    pub(crate) code: PyAtomicRef<PyCode>,
68    #[pymember(name = "__globals__")]
69    pub(crate) globals: PyDictRef,
70    #[pymember(name = "__builtins__")]
71    pub(crate) builtins: PyObjectRef,
72    #[pymember(name = "__closure__")]
73    pub(crate) closure: Option<PyRef<PyTuple<PyCellRef>>>,
74    defaults_and_kwdefaults: PyMutex<(Option<PyTupleRef>, Option<PyDictRef>)>,
75    name: PyMutex<PyStrRef>,
76    qualname: PyMutex<PyStrRef>,
77    type_params: PyMutex<PyTupleRef>,
78    annotations: PyMutex<Option<PyDictRef>>,
79    annotate: PyMutex<Option<PyObjectRef>>,
80    #[pymember(name = "__module__", writable)]
81    module: PyAtomicRef<Option<PyObject>>,
82    #[pymember(name = "__doc__", writable)]
83    doc: PyAtomicRef<Option<PyObject>>,
84    func_version: AtomicU32,
85    #[cfg(feature = "jit")]
86    jitted_code: PyMutex<Option<CompiledCode>>,
87}
88
89static FUNC_VERSION_COUNTER: AtomicU32 = AtomicU32::new(1);
90
91/// Atomically allocate the next function version, returning 0 if exhausted.
92/// Once the counter wraps to 0, it stays at 0 permanently.
93fn next_func_version() -> u32 {
94    FUNC_VERSION_COUNTER
95        .try_update(Relaxed, Relaxed, |v| (v != 0).then(|| v.wrapping_add(1)))
96        .unwrap_or(0)
97}
98
99unsafe impl Traverse for PyFunction {
100    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
101        self.globals.traverse(tracer_fn);
102        if let Some(closure) = self.closure.as_ref() {
103            // Visit the closure tuple itself as an edge, not its cells: the
104            // tuple is a tracked object that can join a reference cycle, and
105            // `clear` releases the whole tuple. Visiting only the cells would
106            // leave the tuple's reference unaccounted, stranding it as a false
107            // GC root.
108            tracer_fn(closure.as_untyped().as_object());
109        }
110        self.defaults_and_kwdefaults.traverse(tracer_fn);
111        // Traverse additional fields that may contain references
112        self.type_params.lock().traverse(tracer_fn);
113        self.annotations.lock().traverse(tracer_fn);
114        self.annotate.lock().traverse(tracer_fn);
115        self.module.traverse(tracer_fn);
116        self.doc.traverse(tracer_fn);
117        self.name.lock().traverse(tracer_fn);
118        self.qualname.lock().traverse(tracer_fn);
119    }
120
121    fn clear(&mut self, out: &mut Vec<crate::PyObjectRef>) {
122        // Pop closure if present (equivalent to Py_CLEAR(func_closure))
123        if let Some(closure) = self.closure.take() {
124            out.push(closure.into());
125        }
126
127        // Pop defaults and kwdefaults
128        if let Some(mut guard) = self.defaults_and_kwdefaults.try_lock() {
129            if let Some(defaults) = guard.0.take() {
130                out.push(defaults.into());
131            }
132            if let Some(kwdefaults) = guard.1.take() {
133                out.push(kwdefaults.into());
134            }
135        }
136
137        // Clear annotations and annotate (Py_CLEAR)
138        if let Some(mut guard) = self.annotations.try_lock()
139            && let Some(annotations) = guard.take()
140        {
141            out.push(annotations.into());
142        }
143        if let Some(mut guard) = self.annotate.try_lock()
144            && let Some(annotate) = guard.take()
145        {
146            out.push(annotate);
147        }
148
149        // Clear module, doc, and type_params (Py_CLEAR)
150        if let Some(old_module) = self.module.store(Some(Context::genesis().none())) {
151            out.push(old_module);
152        }
153        if let Some(old_doc) = self.doc.store(Some(Context::genesis().none())) {
154            out.push(old_doc);
155        }
156        if let Some(mut guard) = self.type_params.try_lock() {
157            let old_type_params =
158                core::mem::replace(&mut *guard, Context::genesis().empty_tuple.to_owned());
159            out.push(old_type_params.into());
160        }
161
162        // Replace name and qualname with empty string to break potential str subclass cycles
163        // name and qualname could be str subclasses, so they could have reference cycles
164        if let Some(mut guard) = self.name.try_lock() {
165            let old_name = core::mem::replace(&mut *guard, Context::genesis().empty_str.to_owned());
166            out.push(old_name.into());
167        }
168        if let Some(mut guard) = self.qualname.try_lock() {
169            let old_qualname =
170                core::mem::replace(&mut *guard, Context::genesis().empty_str.to_owned());
171            out.push(old_qualname.into());
172        }
173
174        // Note: globals, builtins, code are NOT cleared (required to be non-NULL)
175    }
176}
177
178impl PyFunction {
179    #[inline]
180    pub(crate) fn new(
181        code: PyRef<PyCode>,
182        globals: PyDictRef,
183        vm: &VirtualMachine,
184    ) -> PyResult<Self> {
185        let name = PyMutex::new(code.obj_name.to_owned());
186        let module = vm.unwrap_or_none(globals.get_item_opt(identifier!(vm, __name__), vm)?);
187        let builtins = globals.get_item("__builtins__", vm).unwrap_or_else(|_| {
188            // If not in globals, inherit from current execution context
189            crate::frame::current_builtins().unwrap_or_else(|| vm.builtins.dict().into())
190        });
191        // If builtins is a module, use its __dict__ instead
192        let builtins = if let Some(module) = builtins.downcast_ref::<PyModule>() {
193            module.dict().into()
194        } else {
195            builtins
196        };
197
198        // Get docstring from co_consts[0] if HAS_DOCSTRING flag is set
199        let doc = if code.code.flags.contains(bytecode::CodeFlags::HAS_DOCSTRING) {
200            code.code
201                .constants
202                .first()
203                .map_or_else(|| vm.ctx.none(), |c| c.as_object().to_owned())
204        } else {
205            vm.ctx.none()
206        };
207
208        let qualname = vm.ctx.new_str(code.qualname.as_str());
209        let func = Self {
210            code: PyAtomicRef::from(code),
211            globals,
212            builtins,
213            closure: None,
214            defaults_and_kwdefaults: PyMutex::new((None, None)),
215            name,
216            qualname: PyMutex::new(qualname),
217            type_params: PyMutex::new(vm.ctx.empty_tuple.clone()),
218            annotations: PyMutex::new(None),
219            annotate: PyMutex::new(None),
220            module: PyAtomicRef::from(Some(module)),
221            doc: PyAtomicRef::from(Some(doc)),
222            func_version: AtomicU32::new(next_func_version()),
223            #[cfg(feature = "jit")]
224            jitted_code: PyMutex::new(None),
225        };
226        Ok(func)
227    }
228
229    fn fill_locals_from_args(
230        &self,
231        frame: &FrameObject,
232        func_args: FuncArgs,
233        vm: &VirtualMachine,
234    ) -> PyResult<()> {
235        // SAFETY: FrameObject was just created and not yet executing.
236        let fastlocals = unsafe { frame.fastlocals_mut() };
237        self.fill_locals_from_args_inner(fastlocals, func_args, vm)
238    }
239
240    fn fill_locals_from_args_iframe(
241        &self,
242        iframe: &mut crate::frame::InterpreterFrame,
243        func_args: FuncArgs,
244        vm: &VirtualMachine,
245    ) -> PyResult<()> {
246        let fastlocals = iframe.localsplus.fastlocals_mut();
247        self.fill_locals_from_args_inner(fastlocals, func_args, vm)
248    }
249
250    fn fill_locals_from_args_inner(
251        &self,
252        fastlocals: &mut [Option<PyObjectRef>],
253        func_args: FuncArgs,
254        vm: &VirtualMachine,
255    ) -> PyResult<()> {
256        let code: &Py<PyCode> = &self.code;
257        let nargs = func_args.args.len();
258        let n_expected_args = code.arg_count as usize;
259        let total_args = code.arg_count as usize + code.kwonlyarg_count as usize;
260
261        let mut args_iter = func_args.args.into_iter();
262
263        // Copy positional arguments into local variables
264        // zip short-circuits if either iterator returns None, which is the behavior we want --
265        // only fill as much as there is to fill with as much as we have
266        for (local, arg) in Iterator::zip(
267            fastlocals.iter_mut().take(n_expected_args),
268            args_iter.by_ref().take(nargs),
269        ) {
270            *local = Some(arg);
271        }
272
273        let mut vararg_offset = total_args;
274        // Pack other positional arguments in to *args:
275        let too_many_positional = if code.flags.contains(bytecode::CodeFlags::VARARGS) {
276            let vararg_value = vm.ctx.new_tuple(args_iter.collect());
277            fastlocals[vararg_offset] = Some(vararg_value.into());
278            vararg_offset += 1;
279            false
280        } else {
281            nargs > n_expected_args
282        };
283
284        // The keyword-only parameters the call brought are counted alongside
285        // the positional ones it brought too many of, before the keywords
286        // themselves are taken out of the call.
287        let kw_only_given = if too_many_positional && code.kwonlyarg_count > 0 {
288            let start = code.arg_count as usize;
289            let end = start + code.kwonlyarg_count as usize;
290            code.varnames[start..end]
291                .iter()
292                .filter(|name| func_args.kwargs.contains_key(name.as_str()))
293                .count()
294        } else {
295            0
296        };
297
298        // Do we support `**kwargs` ?
299        let kwargs = if code.flags.contains(bytecode::CodeFlags::VARKEYWORDS) {
300            let d = vm.ctx.new_dict();
301            fastlocals[vararg_offset] = Some(d.clone().into());
302            Some(d)
303        } else {
304            None
305        };
306
307        let arg_pos = |range: core::ops::Range<_>, name: &str| {
308            code.varnames
309                .iter()
310                .enumerate()
311                .skip(range.start)
312                .take(range.end - range.start)
313                .find(|(_, s)| s.as_str() == name)
314                .map(|(p, _)| p)
315        };
316
317        // Handle keyword arguments
318        let mut kwargs_iter = func_args.kwargs.into_iter();
319        while let Some((name, value)) = kwargs_iter.next() {
320            // Parameter names are plain identifiers, so a non-UTF-8 (surrogate) key
321            // can never match one and just falls through to **kwargs / the error path.
322            let name_str = name.as_str().ok();
323            // Check if we have a parameter with this name:
324            if let Some(pos) =
325                name_str.and_then(|s| arg_pos(code.posonlyarg_count as usize..total_args, s))
326            {
327                let slot = &mut fastlocals[pos];
328                if slot.is_some() {
329                    return Err(vm.new_type_error(format!(
330                        "{}() got multiple values for argument '{}'",
331                        self.qualname.lock().clone(),
332                        name
333                    )));
334                }
335                *slot = Some(value);
336            } else if let Some(kwargs) = kwargs.as_ref() {
337                kwargs.set_item(&name, value, vm)?;
338            } else {
339                // A name the call cannot place is faulted over the whole of
340                // what it named that only position can give, whether that
341                // came before this name or after it.
342                let is_posonly = |name: &Wtf8Buf| {
343                    name.as_str()
344                        .is_ok_and(|s| arg_pos(0..code.posonlyarg_count as usize, s).is_some())
345                };
346                let mut posonly: Vec<_> = is_posonly(&name)
347                    .then(|| name.clone())
348                    .into_iter()
349                    .collect();
350                posonly.extend(kwargs_iter.map(|(name, _)| name).filter(is_posonly));
351                if !posonly.is_empty() {
352                    return Err(vm.new_type_error(format!(
353                        "{}() got some positional-only arguments passed as keyword arguments: '{}'",
354                        self.qualname.lock().clone(),
355                        posonly.into_iter().format(", "),
356                    )));
357                }
358                return Err(vm.new_type_error(format!(
359                    "{}() got an unexpected keyword argument '{}'",
360                    self.qualname.lock().clone(),
361                    name
362                )));
363            }
364        }
365        // The count of positional arguments is faulted once the keywords have
366        // all been placed.
367        if too_many_positional {
368            let n_defaults = self
369                .defaults_and_kwdefaults
370                .lock()
371                .0
372                .as_ref()
373                .map_or(0, |d| d.as_slice().len());
374            let n_required = n_expected_args - n_defaults;
375            let (takes_msg, plural) = if n_defaults > 0 {
376                (format!("from {n_required} to {n_expected_args}"), true)
377            } else {
378                (n_expected_args.to_string(), n_expected_args != 1)
379            };
380
381            let given_msg = if kw_only_given > 0 {
382                format!(
383                    "{} positional argument{} (and {} keyword-only argument{}) were",
384                    nargs,
385                    if nargs == 1 { "" } else { "s" },
386                    kw_only_given,
387                    if kw_only_given == 1 { "" } else { "s" },
388                )
389            } else {
390                format!("{} {}", nargs, if nargs == 1 { "was" } else { "were" })
391            };
392
393            return Err(vm.new_type_error(format!(
394                "{}() takes {} positional argument{} but {} given",
395                self.qualname.lock().clone(),
396                takes_msg,
397                if plural { "s" } else { "" },
398                given_msg,
399            )));
400        }
401
402        let mut defaults_and_kwdefaults = None;
403        // can't be a closure cause it returns a reference to a captured variable :/
404        macro_rules! get_defaults {
405            () => {{
406                defaults_and_kwdefaults
407                    .get_or_insert_with(|| self.defaults_and_kwdefaults.lock().clone())
408            }};
409        }
410
411        // Add missing positional arguments, if we have fewer positional arguments than the
412        // function definition calls for
413        if nargs < n_expected_args {
414            let defaults = get_defaults!().0.as_ref().map(|tup| tup.as_slice());
415            let n_defs = defaults.map_or(0, |d| d.len());
416
417            let n_required = code.arg_count as usize - n_defs;
418
419            // Given the number of defaults available, check all the arguments for which we
420            // _don't_ have defaults; if any are missing, raise an exception
421            let mut missing: Vec<_> = (nargs..n_required)
422                .filter_map(|i| {
423                    if fastlocals[i].is_none() {
424                        Some(&code.varnames[i])
425                    } else {
426                        None
427                    }
428                })
429                .collect();
430
431            if !missing.is_empty() {
432                return Err(vm.new_type_error(format_missing_args(
433                    self.qualname.lock().clone(),
434                    "positional",
435                    &mut missing,
436                )));
437            }
438
439            if let Some(defaults) = defaults {
440                let n = core::cmp::min(nargs, n_expected_args);
441                let i = n.saturating_sub(n_required);
442
443                // We have sufficient defaults, so iterate over the corresponding names and use
444                // the default if we don't already have a value
445                for i in i..defaults.len() {
446                    let slot = &mut fastlocals[n_required + i];
447                    if slot.is_none() {
448                        *slot = Some(defaults[i].clone());
449                    }
450                }
451            }
452        };
453
454        if code.kwonlyarg_count > 0 {
455            let mut missing = Vec::new();
456            // Check if kw only arguments are all present:
457            for (slot, kwarg) in fastlocals
458                .iter_mut()
459                .zip(&*code.varnames)
460                .skip(code.arg_count as usize)
461                .take(code.kwonlyarg_count as usize)
462                .filter(|(slot, _)| slot.is_none())
463            {
464                if let Some(defaults) = &get_defaults!().1
465                    && let Some(default) = defaults.get_item_opt(&**kwarg, vm)?
466                {
467                    *slot = Some(default);
468                    continue;
469                }
470
471                // No default value and not specified.
472                missing.push(kwarg);
473            }
474
475            if !missing.is_empty() {
476                return Err(vm.new_type_error(format_missing_args(
477                    self.qualname.lock().clone(),
478                    "keyword-only",
479                    &mut missing,
480                )));
481            }
482        }
483
484        Ok(())
485    }
486
487    /// Set function attribute based on MakeFunctionFlags
488    pub(crate) fn set_function_attribute(
489        &mut self,
490        attr: bytecode::MakeFunctionFlag,
491        attr_value: PyObjectRef,
492        vm: &VirtualMachine,
493    ) -> PyResult<()> {
494        use crate::builtins::PyDict;
495        match attr {
496            bytecode::MakeFunctionFlag::Defaults => {
497                let defaults = match attr_value.downcast::<PyTuple>() {
498                    Ok(tuple) => tuple,
499                    Err(obj) => {
500                        return Err(vm.new_type_error(format!(
501                            "__defaults__ must be a tuple, not {}",
502                            obj.class().name()
503                        )));
504                    }
505                };
506                self.defaults_and_kwdefaults.lock().0 = Some(defaults);
507            }
508            bytecode::MakeFunctionFlag::KwOnlyDefaults => {
509                let kwdefaults = match attr_value.downcast::<PyDict>() {
510                    Ok(dict) => dict,
511                    Err(obj) => {
512                        return Err(vm.new_type_error(format!(
513                            "__kwdefaults__ must be a dict, not {}",
514                            obj.class().name()
515                        )));
516                    }
517                };
518                self.defaults_and_kwdefaults.lock().1 = Some(kwdefaults);
519            }
520            bytecode::MakeFunctionFlag::Annotations => {
521                let annotations = match attr_value.downcast::<PyDict>() {
522                    Ok(dict) => dict,
523                    Err(obj) => {
524                        return Err(vm.new_type_error(format!(
525                            "__annotations__ must be a dict, not {}",
526                            obj.class().name()
527                        )));
528                    }
529                };
530                *self.annotations.lock() = Some(annotations);
531            }
532            bytecode::MakeFunctionFlag::Closure => {
533                let closure_tuple = attr_value
534                    .downcast_exact::<PyTuple>(vm)
535                    .map_err(|obj| {
536                        vm.new_type_error(format!(
537                            "closure must be a tuple, not {}",
538                            obj.class().name()
539                        ))
540                    })?
541                    .into_pyref();
542
543                let typed = closure_tuple.try_into_typed::<PyCell>(vm)?;
544                self.closure = Some(typed);
545            }
546            bytecode::MakeFunctionFlag::TypeParams => {
547                let type_params = attr_value.clone().downcast::<PyTuple>().map_err(|_| {
548                    vm.new_type_error(format!(
549                        "__type_params__ must be a tuple, not {}",
550                        attr_value.class().name()
551                    ))
552                })?;
553                *self.type_params.lock() = type_params;
554            }
555            bytecode::MakeFunctionFlag::Annotate => {
556                if !attr_value.is_callable() {
557                    return Err(vm.new_type_error("__annotate__ must be callable"));
558                }
559                *self.annotate.lock() = Some(attr_value);
560            }
561        }
562        Ok(())
563    }
564}
565
566impl Py<PyFunction> {
567    pub(crate) fn is_optimized_for_call_specialization(&self) -> bool {
568        self.code.flags.contains(bytecode::CodeFlags::OPTIMIZED)
569    }
570
571    /// Whether this function currently has native JIT code. Adaptive Python
572    /// call specializations must yield to that entry point.
573    #[inline]
574    pub(crate) fn is_jitted(&self) -> bool {
575        #[cfg(feature = "jit")]
576        {
577            self.jitted_code.lock().is_some()
578        }
579        #[cfg(not(feature = "jit"))]
580        {
581            false
582        }
583    }
584
585    pub fn invoke_with_locals(
586        &self,
587        func_args: FuncArgs,
588        locals: Option<ArgMapping>,
589        vm: &VirtualMachine,
590    ) -> PyResult {
591        #[cfg(feature = "jit")]
592        if let Some(jitted_code) = self.jitted_code.lock().as_ref() {
593            use crate::convert::ToPyObject;
594            match jit::get_jit_args(self, &func_args, jitted_code, vm) {
595                Ok(args) => {
596                    return Ok(args.invoke().to_pyobject(vm));
597                }
598                Err(err) => info!(
599                    "jit: function `{}` is falling back to being interpreted because of the \
600                    error: {}",
601                    self.code.obj_name, err
602                ),
603            }
604        }
605
606        let code = &*self.code;
607
608        let is_gen = code.flags.contains(bytecode::CodeFlags::GENERATOR);
609        let is_coro = code.flags.contains(bytecode::CodeFlags::COROUTINE);
610        let is_async_gen = code.flags.contains(bytecode::CodeFlags::ASYNC_GENERATOR);
611
612        let needs_heap_frame = is_gen || is_coro || is_async_gen || vm.use_tracing.get();
613
614        if needs_heap_frame {
615            // Heap-allocate FrameObject for generators/coroutines (lifetime
616            // exceeds call stack) or when tracing is active (trace callbacks
617            // need a FrameObject).
618            let code_owned: PyRef<PyCode> = code.to_owned();
619            let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
620                None
621            } else if let Some(locals) = locals {
622                Some(locals)
623            } else {
624                Some(ArgMapping::from_dict_exact(self.globals.clone()))
625            };
626            let use_datastack = !is_gen && !is_coro && !is_async_gen;
627            let frame = FrameObject::new_ref(
628                code_owned,
629                Scope::new(locals, self.globals.clone()),
630                self.builtins.clone(),
631                self.closure.as_ref().map_or(&[], |c| c.as_slice()),
632                Some(self.to_owned().into()),
633                use_datastack,
634                vm,
635            );
636            self.fill_locals_from_args(&frame, func_args, vm)?;
637            if is_gen || is_coro || is_async_gen {
638                return Ok(self.make_generator_or_coro(frame, vm));
639            }
640            // Tracing active: use heap frame with full trace support.
641            let result = vm.run_frame(frame.clone());
642            unsafe {
643                if let Some(base) = frame.iframe_mut().localsplus.release_datastack() {
644                    vm.datastack_pop(base);
645                }
646            }
647            return result;
648        }
649
650        // Fast path: stack-allocated InterpreterFrame, no FrameObject.
651        // No refcount inc for code — it's alive via self.code for the call duration.
652        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
653            crate::frame::FrameLocals::lazy()
654        } else if let Some(locals) = locals {
655            crate::frame::FrameLocals::with_locals(locals)
656        } else {
657            crate::frame::FrameLocals::with_locals(crate::function::ArgMapping::from_dict_exact(
658                self.globals.clone(),
659            ))
660        };
661
662        // Use self.as_object() as raw pointer — no refcount inc/dec.
663        // The function is alive on the caller's stack for the call duration.
664        let iframe = unsafe {
665            // SAFETY: `self` is borrowed for this call; its code, globals,
666            // builtins, and the function object outlive `iframe`.
667            crate::frame::InterpreterFrame::new_on_datastack(
668                &self.code,
669                &self.globals,
670                &self.builtins,
671                Some(self.as_object()),
672                locals,
673                self.closure.as_ref().map_or(&[], |c| c.as_slice()),
674                vm,
675            )
676        };
677        let result = self
678            .fill_locals_from_args_iframe(iframe, func_args, vm)
679            .and_then(|()| vm.run_frame_fast(iframe));
680        // Release data stack memory — must happen on both success and error.
681        unsafe {
682            if let Some((base, size)) = iframe.release_datastack_frame() {
683                vm.datastack_pop_frame(base, size);
684            }
685        }
686        result
687    }
688
689    /// Create generator, coroutine, or async generator from a FrameObject.
690    fn make_generator_or_coro(&self, frame: FrameObjectRef, vm: &VirtualMachine) -> PyObjectRef {
691        let code = frame.iframe().code();
692        let is_async_gen = code.flags.contains(bytecode::CodeFlags::ASYNC_GENERATOR);
693        let is_gen = code.flags.contains(bytecode::CodeFlags::GENERATOR);
694
695        let obj = if is_async_gen {
696            PyAsyncGen::new(frame.clone(), self.__name__(), self.__qualname__()).into_pyobject(vm)
697        } else if is_gen {
698            PyGenerator::new(frame.clone(), self.__name__(), self.__qualname__()).into_pyobject(vm)
699        } else {
700            let origin = crate::coroutine::compute_cr_origin(vm);
701            PyCoroutine::new(frame.clone(), self.__name__(), self.__qualname__(), origin)
702                .into_pyobject(vm)
703        };
704        debug_assert!(
705            !frame.localsplus_is_datastack_backed(),
706            "generator frame is data-stack-backed"
707        );
708        // Both halves are alive (held by `obj` and `frame`), untracked --
709        // generator types and frames both opt out of tracking at allocation --
710        // and enter the GC together.
711        unsafe {
712            crate::gc_state::track_new_pair(
713                core::ptr::NonNull::from(obj.as_object()),
714                core::ptr::NonNull::from(frame.as_object()),
715            );
716        }
717        frame.set_generator(&obj);
718        obj
719    }
720
721    #[inline(always)]
722    pub fn invoke(&self, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
723        self.invoke_with_locals(func_args, None, vm)
724    }
725
726    /// Returns the function version, or 0 if invalidated.
727    #[inline]
728    pub fn func_version(&self) -> u32 {
729        self.func_version.load(Relaxed)
730    }
731
732    /// Returns the current version, assigning a fresh one if previously invalidated.
733    /// Returns 0 if the version counter has overflowed.
734    /// `_PyFunction_GetVersionForCurrentState`
735    pub fn get_version_for_current_state(&self) -> u32 {
736        let v = self.func_version.load(Relaxed);
737        if v != 0 {
738            return v;
739        }
740        let new_v = next_func_version();
741        if new_v == 0 {
742            return 0;
743        }
744        self.func_version.store(new_v, Relaxed);
745        new_v
746    }
747
748    /// Check if this function is eligible for exact-args call specialization.
749    /// Returns true if: CO_OPTIMIZED, no VARARGS, no VARKEYWORDS, no kwonly args,
750    /// and effective_nargs matches co_argcount.
751    pub(crate) fn can_specialize_call(&self, effective_nargs: u32) -> bool {
752        let code: &Py<PyCode> = &self.code;
753        let flags = code.flags;
754        flags.contains(bytecode::CodeFlags::OPTIMIZED)
755            && !flags.intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
756            && code.kwonlyarg_count == 0
757            && code.arg_count == effective_nargs
758    }
759
760    /// True if the code object is a generator, coroutine or async generator.
761    #[inline]
762    pub(crate) fn is_generator_like(&self) -> bool {
763        self.code.flags.intersects(
764            bytecode::CodeFlags::GENERATOR
765                | bytecode::CodeFlags::COROUTINE
766                | bytecode::CodeFlags::ASYNC_GENERATOR,
767        )
768    }
769
770    /// Runtime guard for CALL_*_EXACT_ARGS specialization: check only argcount.
771    /// Other invariants are guaranteed by function versioning and specialization-time checks.
772    #[inline]
773    pub(crate) fn has_exact_argcount(&self, effective_nargs: u32) -> bool {
774        self.code.arg_count == effective_nargs
775    }
776
777    /// Bytes required for this function's frame on RustPython's thread datastack.
778    /// Returns `None` for generator/coroutine code paths that do not push a
779    /// regular datastack-backed frame in the fast call path.
780    pub(crate) fn datastack_frame_size_bytes(&self) -> Option<usize> {
781        datastack_frame_size_bytes_for_code(&self.code)
782    }
783
784    pub(crate) fn prepare_exact_args_frame(
785        &self,
786        args: impl ExactSizeIterator<Item = PyObjectRef>,
787        vm: &VirtualMachine,
788    ) -> FrameObjectRef {
789        let code: PyRef<PyCode> = (*self.code).to_owned();
790
791        debug_assert_eq!(args.len(), code.arg_count as usize);
792        debug_assert!(code.flags.contains(bytecode::CodeFlags::OPTIMIZED));
793        debug_assert!(
794            !code
795                .flags
796                .intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
797        );
798        debug_assert_eq!(code.kwonlyarg_count, 0);
799        debug_assert!(!code.flags.intersects(
800            bytecode::CodeFlags::GENERATOR
801                | bytecode::CodeFlags::COROUTINE
802                | bytecode::CodeFlags::ASYNC_GENERATOR,
803        ));
804
805        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
806            None
807        } else {
808            Some(ArgMapping::from_dict_exact(self.globals.clone()))
809        };
810
811        let frame = FrameObject::new_ref(
812            code,
813            Scope::new(locals, self.globals.clone()),
814            self.builtins.clone(),
815            self.closure.as_ref().map_or(&[], |c| c.as_slice()),
816            Some(self.to_owned().into()),
817            true, // Exact-args fast path is only used for non-gen/coro functions.
818            vm,
819        );
820
821        {
822            let fastlocals = unsafe { frame.fastlocals_mut() };
823            for (slot, arg) in fastlocals.iter_mut().zip(args) {
824                *slot = Some(arg);
825            }
826        }
827
828        frame
829    }
830
831    /// Build the generator/coroutine a generator-like function returns, with
832    /// the call's positional arguments bound straight into the new frame's
833    /// fastlocals.
834    ///
835    /// The counterpart of `prepare_exact_args_frame` for the one call shape it
836    /// refuses. Same preconditions as `can_specialize_call`: every parameter is
837    /// positional and this call fills each of them exactly once, so none of
838    /// what `fill_locals_from_args_inner` exists for -- varargs packing,
839    /// keyword matching, defaults -- can apply, and the `FuncArgs` those need
840    /// is never built.
841    pub(crate) fn make_generator_exact_args(
842        &self,
843        args: impl ExactSizeIterator<Item = PyObjectRef>,
844        vm: &VirtualMachine,
845    ) -> PyObjectRef {
846        let code: PyRef<PyCode> = (*self.code).to_owned();
847
848        debug_assert_eq!(args.len(), code.arg_count as usize);
849        debug_assert!(code.flags.contains(bytecode::CodeFlags::OPTIMIZED));
850        debug_assert!(
851            !code
852                .flags
853                .intersects(bytecode::CodeFlags::VARARGS | bytecode::CodeFlags::VARKEYWORDS)
854        );
855        debug_assert_eq!(code.kwonlyarg_count, 0);
856        debug_assert!(code.flags.intersects(
857            bytecode::CodeFlags::GENERATOR
858                | bytecode::CodeFlags::COROUTINE
859                | bytecode::CodeFlags::ASYNC_GENERATOR,
860        ));
861
862        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
863            None
864        } else {
865            Some(ArgMapping::from_dict_exact(self.globals.clone()))
866        };
867
868        // Heap-backed: the frame outlives the call that made it.
869        let frame = FrameObject::new_ref(
870            code,
871            Scope::new(locals, self.globals.clone()),
872            self.builtins.clone(),
873            self.closure.as_ref().map_or(&[], |c| c.as_slice()),
874            Some(self.to_owned().into()),
875            false,
876            vm,
877        );
878
879        {
880            // SAFETY: the frame was just created and is not executing.
881            let fastlocals = unsafe { frame.fastlocals_mut() };
882            for (slot, arg) in fastlocals.iter_mut().zip(args) {
883                *slot = Some(arg);
884            }
885        }
886
887        self.make_generator_or_coro(frame, vm)
888    }
889
890    pub(crate) fn invoke_prepared_exact_args(
891        &self,
892        args: impl ExactSizeIterator<Item = PyObjectRef>,
893        vm: &VirtualMachine,
894    ) -> PyResult {
895        let code = &*self.code;
896
897        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
898            crate::frame::FrameLocals::lazy()
899        } else {
900            crate::frame::FrameLocals::with_locals(ArgMapping::from_dict_exact(
901                self.globals.clone(),
902            ))
903        };
904
905        let iframe = unsafe {
906            // SAFETY: `self` is borrowed for this call; its code, globals,
907            // builtins, and the function object outlive `iframe`.
908            crate::frame::InterpreterFrame::new_on_datastack(
909                code,
910                &self.globals,
911                &self.builtins,
912                Some(self.as_object()),
913                locals,
914                self.closure.as_ref().map_or(&[], |c| c.as_slice()),
915                vm,
916            )
917        };
918
919        // Fill arguments directly into fastlocals
920        {
921            let fastlocals = iframe.localsplus.fastlocals_mut();
922            for (slot, arg) in fastlocals.iter_mut().zip(args) {
923                *slot = Some(arg);
924            }
925        }
926
927        let result = vm.run_frame_fast(iframe);
928        unsafe {
929            if let Some((base, size)) = iframe.release_datastack_frame() {
930                vm.datastack_pop_frame(base, size);
931            }
932        }
933        result
934    }
935
936    /// Fast path for calling a simple function with exact positional args.
937    /// Skips FuncArgs allocation, prepend_arg, and fill_locals_from_args.
938    /// Only valid when: CO_OPTIMIZED, no VARARGS, no VARKEYWORDS, no kwonlyargs,
939    /// and nargs == co_argcount.
940    pub fn invoke_exact_args(&self, args: Vec<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
941        debug_assert_eq!(args.len(), self.code.arg_count as usize);
942
943        // Generator/coroutine code objects are SIMPLE_FUNCTION in call
944        // specialization classification, but calling one produces a
945        // generator/coroutine object instead of running the frame.
946        if self.is_generator_like() {
947            return Ok(self.make_generator_exact_args(args.into_iter(), vm));
948        }
949        self.invoke_prepared_exact_args(args.into_iter(), vm)
950    }
951
952    /// Like `invoke_exact_args`, but moves the args out of caller-provided
953    /// slots (all filled with `Some`), so callers can stage them in a
954    /// fixed-size stack buffer instead of allocating a Vec per call.
955    pub(crate) fn invoke_exact_args_slots(
956        &self,
957        args: &mut [Option<PyObjectRef>],
958        vm: &VirtualMachine,
959    ) -> PyResult {
960        debug_assert_eq!(args.len(), self.code.arg_count as usize);
961
962        let taken = args
963            .iter_mut()
964            .map(|slot| slot.take().expect("arg slot must be filled"));
965        // Generator/coroutine code objects are SIMPLE_FUNCTION in call
966        // specialization classification, but calling one produces a
967        // generator/coroutine object instead of running the frame.
968        if self.is_generator_like() {
969            return Ok(self.make_generator_exact_args(taken, vm));
970        }
971        self.invoke_prepared_exact_args(taken, vm)
972    }
973}
974
975pub(crate) fn datastack_frame_size_bytes_for_code(code: &Py<PyCode>) -> Option<usize> {
976    if code.flags.intersects(
977        bytecode::CodeFlags::GENERATOR
978            | bytecode::CodeFlags::COROUTINE
979            | bytecode::CodeFlags::ASYNC_GENERATOR,
980    ) {
981        return None;
982    }
983    let nlocalsplus = code.localspluskinds.len();
984    Some(crate::frame::datastack_iframe_total_bytes(
985        nlocalsplus,
986        code.max_stackdepth as usize,
987    ))
988}
989
990impl PyPayload for PyFunction {
991    #[inline]
992    fn class(ctx: &Context) -> &'static Py<PyType> {
993        ctx.types.function_type
994    }
995}
996
997#[pyclass(
998    with(GetDescriptor, Callable, Representable, Constructor),
999    flags(HAS_DICT, HAS_WEAKREF, METHOD_DESCRIPTOR)
1000)]
1001impl Py<PyFunction> {
1002    #[pygetset]
1003    fn __code__(&self) -> PyRef<PyCode> {
1004        (*self.code).to_owned()
1005    }
1006
1007    #[pygetset(setter)]
1008    fn set___code__(&self, code: PyRef<PyCode>, vm: &VirtualMachine) -> PyResult<()> {
1009        let n_free = code.freevars.len();
1010        let n_closure = self.closure.as_ref().map_or(0, |c| c.as_slice().len());
1011        if n_closure != n_free {
1012            return Err(vm.new_value_error(format!(
1013                "{}() requires a code object with {} free vars, not {}",
1014                self.qualname.lock(),
1015                n_closure,
1016                n_free,
1017            )));
1018        }
1019        #[cfg(feature = "jit")]
1020        let mut jit_guard = self.jitted_code.lock();
1021        self.code.swap_to_temporary_refs(code, vm);
1022        #[cfg(feature = "jit")]
1023        {
1024            *jit_guard = None;
1025        }
1026        self.func_version.store(0, Relaxed);
1027        crate::stdlib::_testinternalcapi::note_func_modification();
1028        Ok(())
1029    }
1030
1031    #[pygetset]
1032    pub(crate) fn __defaults__(&self) -> Option<PyTupleRef> {
1033        self.defaults_and_kwdefaults.lock().0.clone()
1034    }
1035    #[pygetset(setter)]
1036    fn set___defaults__(&self, defaults: PySetterValue<Option<PyTupleRef>>) {
1037        self.defaults_and_kwdefaults.lock().0 = match defaults {
1038            PySetterValue::Assign(d) => d,
1039            PySetterValue::Delete => None,
1040        };
1041        self.func_version.store(0, Relaxed);
1042        crate::stdlib::_testinternalcapi::note_func_modification();
1043    }
1044
1045    #[pygetset]
1046    pub(crate) fn __kwdefaults__(&self) -> Option<PyDictRef> {
1047        self.defaults_and_kwdefaults.lock().1.clone()
1048    }
1049    #[pygetset(setter)]
1050    fn set___kwdefaults__(&self, kwdefaults: PySetterValue<Option<PyDictRef>>) {
1051        self.defaults_and_kwdefaults.lock().1 = match kwdefaults {
1052            PySetterValue::Assign(d) => d,
1053            PySetterValue::Delete => None,
1054        };
1055        self.func_version.store(0, Relaxed);
1056        crate::stdlib::_testinternalcapi::note_func_modification();
1057    }
1058
1059    #[pygetset]
1060    fn __name__(&self) -> PyStrRef {
1061        self.name.lock().clone()
1062    }
1063
1064    #[pygetset(setter)]
1065    fn set___name__(&self, name: PyStrRef) {
1066        *self.name.lock() = name;
1067    }
1068
1069    #[pygetset]
1070    fn __annotations__(&self, vm: &VirtualMachine) -> PyResult<PyDictRef> {
1071        // First check if we have cached annotations
1072        {
1073            let annotations = self.annotations.lock();
1074            if let Some(ref ann) = *annotations {
1075                return Ok(ann.clone());
1076            }
1077        }
1078
1079        // Check for callable __annotate__ and clone it before calling
1080        let annotate_fn = {
1081            let annotate = self.annotate.lock();
1082            if let Some(ref func) = *annotate
1083                && func.is_callable()
1084            {
1085                Some(func.clone())
1086            } else {
1087                None
1088            }
1089        };
1090
1091        // Release locks before calling __annotate__ to avoid deadlock
1092        if let Some(annotate_fn) = annotate_fn {
1093            let one = vm.ctx.new_int(1);
1094            let ann_dict = annotate_fn.call((one,), vm)?;
1095            let ann_dict = ann_dict
1096                .downcast::<crate::builtins::PyDict>()
1097                .map_err(|obj| {
1098                    vm.new_type_error(format!(
1099                        "__annotate__ returned non-dict of type '{}'",
1100                        obj.class().name()
1101                    ))
1102                })?;
1103
1104            // Cache the result
1105            *self.annotations.lock() = Some(ann_dict.clone());
1106            return Ok(ann_dict);
1107        }
1108
1109        // No __annotate__ or not callable, create empty dict
1110        let new_dict = vm.ctx.new_dict();
1111        *self.annotations.lock() = Some(new_dict.clone());
1112        Ok(new_dict)
1113    }
1114
1115    #[pygetset(setter)]
1116    fn set___annotations__(
1117        &self,
1118        value: PySetterValue<Option<PyObjectRef>>,
1119        vm: &VirtualMachine,
1120    ) -> PyResult<()> {
1121        match value {
1122            PySetterValue::Assign(Some(value)) => {
1123                let annotations = value.downcast::<crate::builtins::PyDict>().map_err(|_| {
1124                    vm.new_type_error("__annotations__ must be set to a dict object")
1125                })?;
1126                *self.annotations.lock() = Some(annotations);
1127                *self.annotate.lock() = None;
1128            }
1129            PySetterValue::Assign(None) => {
1130                *self.annotations.lock() = None;
1131                *self.annotate.lock() = None;
1132            }
1133            PySetterValue::Delete => {
1134                // del only clears cached annotations; __annotate__ is preserved
1135                *self.annotations.lock() = None;
1136            }
1137        }
1138        Ok(())
1139    }
1140
1141    #[pygetset]
1142    fn __dict__(zelf: &Self, vm: &VirtualMachine) -> PyResult<PyDictRef> {
1143        object::object_get_dict(zelf.as_object().to_owned(), vm)
1144    }
1145
1146    #[pygetset(setter)]
1147    fn set___dict__(zelf: &Self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
1148        object::object_generic_set_dict(zelf.as_object().to_owned(), value, vm)
1149    }
1150
1151    #[pygetset]
1152    fn __annotate__(&self, vm: &VirtualMachine) -> PyObjectRef {
1153        self.annotate
1154            .lock()
1155            .clone()
1156            .unwrap_or_else(|| vm.ctx.none())
1157    }
1158
1159    #[pygetset(setter)]
1160    fn set___annotate__(
1161        &self,
1162        value: PySetterValue<Option<PyObjectRef>>,
1163        vm: &VirtualMachine,
1164    ) -> PyResult<()> {
1165        let annotate = match value {
1166            PySetterValue::Assign(Some(value)) => {
1167                if !value.is_callable() {
1168                    return Err(vm.new_type_error("__annotate__ must be callable or None"));
1169                }
1170                // Clear cached __annotations__ when __annotate__ is set
1171                *self.annotations.lock() = None;
1172                Some(value)
1173            }
1174            PySetterValue::Assign(None) => None,
1175            PySetterValue::Delete => {
1176                return Err(vm.new_type_error("__annotate__ cannot be deleted"));
1177            }
1178        };
1179        *self.annotate.lock() = annotate;
1180        Ok(())
1181    }
1182
1183    #[pygetset]
1184    fn __qualname__(&self) -> PyStrRef {
1185        self.qualname.lock().clone()
1186    }
1187
1188    #[pygetset(setter)]
1189    fn set___qualname__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
1190        match value {
1191            PySetterValue::Assign(value) => {
1192                let Ok(qualname) = value.downcast::<PyStr>() else {
1193                    return Err(vm.new_type_error("__qualname__ must be set to a string object"));
1194                };
1195                *self.qualname.lock() = qualname;
1196            }
1197            PySetterValue::Delete => {
1198                return Err(vm.new_type_error("__qualname__ must be set to a string object"));
1199            }
1200        }
1201        Ok(())
1202    }
1203
1204    #[pygetset]
1205    fn __type_params__(&self) -> PyTupleRef {
1206        self.type_params.lock().clone()
1207    }
1208
1209    #[pygetset(setter)]
1210    fn set___type_params__(
1211        &self,
1212        value: PySetterValue<PyTupleRef>,
1213        vm: &VirtualMachine,
1214    ) -> PyResult<()> {
1215        match value {
1216            PySetterValue::Assign(value) => {
1217                *self.type_params.lock() = value;
1218            }
1219            PySetterValue::Delete => {
1220                return Err(vm.new_type_error("__type_params__ must be set to a tuple object"));
1221            }
1222        }
1223        Ok(())
1224    }
1225
1226    #[cfg(feature = "jit")]
1227    #[pymethod]
1228    fn __jit__(zelf: PyRef<PyFunction>, vm: &VirtualMachine) -> PyResult<()> {
1229        let mut jit_guard = zelf.jitted_code.lock();
1230        if jit_guard.is_some() {
1231            return Ok(());
1232        }
1233        let arg_types = jit::get_jit_arg_types(&zelf, vm)?;
1234        let ret_type = jit::jit_ret_type(&zelf, vm)?;
1235        let code: &Py<PyCode> = &zelf.code;
1236        let compiled = rustpython_jit::compile(&code.code, &arg_types, ret_type)
1237            .map_err(|err| jit::new_jit_error(err.to_string(), vm))?;
1238        *jit_guard = Some(compiled);
1239        Ok(())
1240    }
1241}
1242
1243impl GetDescriptor for PyFunction {
1244    fn descr_get(
1245        zelf: &PyObject,
1246        obj: Option<&PyObject>,
1247        cls: Option<&PyObject>,
1248        vm: &VirtualMachine,
1249    ) -> PyResult {
1250        let (_zelf, obj) = Self::_unwrap(zelf, obj, vm)?;
1251        Ok(if vm.is_none(obj) && !Self::_cls_is(&cls, obj.class()) {
1252            zelf.to_owned()
1253        } else {
1254            PyBoundMethod::new(obj.to_owned(), zelf.to_owned())
1255                .into_ref(&vm.ctx)
1256                .into()
1257        })
1258    }
1259}
1260
1261impl Callable for PyFunction {
1262    type Args = FuncArgs;
1263    #[inline]
1264    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1265        zelf.invoke(args, vm)
1266    }
1267}
1268
1269impl Representable for PyFunction {
1270    #[inline]
1271    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
1272        Ok(format!(
1273            "<function {} at {:#x}>",
1274            zelf.__qualname__(),
1275            zelf.get_id()
1276        ))
1277    }
1278}
1279
1280#[derive(FromArgs)]
1281pub struct PyFunctionNewArgs {
1282    #[pyarg(positional)]
1283    code: PyRef<PyCode>,
1284    #[pyarg(positional)]
1285    globals: PyDictRef,
1286    #[pyarg(any, optional, error_msg = "arg 3 (name) must be None or string")]
1287    name: OptionalArg<PyStrRef>,
1288    #[pyarg(any, optional, error_msg = "arg 4 (defaults) must be None or tuple")]
1289    argdefs: Option<PyTupleRef>,
1290    #[pyarg(any, optional, error_msg = "arg 5 (closure) must be None or tuple")]
1291    closure: Option<PyTupleRef>,
1292    #[pyarg(any, optional, error_msg = "arg 6 (kwdefaults) must be None or dict")]
1293    kwdefaults: Option<PyDictRef>,
1294}
1295
1296impl Constructor for PyFunction {
1297    type Args = PyFunctionNewArgs;
1298
1299    fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
1300        // Handle closure - must be a tuple of cells
1301        let closure = if let Some(closure_tuple) = args.closure {
1302            // Check that closure length matches code's free variables
1303            if closure_tuple.as_slice().len() != args.code.freevars.len() {
1304                return Err(vm.new_value_error(format!(
1305                    "{} requires closure of length {}, not {}",
1306                    args.code.obj_name,
1307                    args.code.freevars.len(),
1308                    closure_tuple.as_slice().len()
1309                )));
1310            }
1311
1312            // Validate that all items are cells and create typed tuple
1313            let typed_closure = closure_tuple.try_into_typed::<PyCell>(vm)?;
1314            Some(typed_closure)
1315        } else if !args.code.freevars.is_empty() {
1316            return Err(vm.new_type_error("arg 5 (closure) must be tuple"));
1317        } else {
1318            None
1319        };
1320
1321        let mut func = Self::new(args.code.clone(), args.globals.clone(), vm)?;
1322        // Set function name if provided
1323        if let Some(name) = args.name.into_option() {
1324            *func.name.lock() = name.clone();
1325            // Also update qualname to match the name
1326            *func.qualname.lock() = name;
1327        }
1328        // Now set additional attributes directly
1329        if let Some(closure_tuple) = closure {
1330            func.closure = Some(closure_tuple);
1331        }
1332        if let Some(argdefs) = args.argdefs {
1333            func.defaults_and_kwdefaults.lock().0 = Some(argdefs);
1334        }
1335        if let Some(kwdefaults) = args.kwdefaults {
1336            func.defaults_and_kwdefaults.lock().1 = Some(kwdefaults);
1337        }
1338
1339        Ok(func)
1340    }
1341}
1342
1343#[pyclass(module = false, name = "method", traverse)]
1344#[derive(Debug)]
1345pub struct PyBoundMethod {
1346    #[pymember(name = "__self__")]
1347    object: PyObjectRef,
1348    #[pymember(name = "__func__")]
1349    function: PyObjectRef,
1350}
1351
1352impl Callable for PyBoundMethod {
1353    type Args = FuncArgs;
1354    #[inline]
1355    fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1356        args.prepend_arg(zelf.object.clone());
1357        zelf.function.call(args, vm)
1358    }
1359}
1360
1361impl Comparable for PyBoundMethod {
1362    fn cmp(
1363        zelf: &Py<Self>,
1364        other: &PyObject,
1365        op: PyComparisonOp,
1366        _vm: &VirtualMachine,
1367    ) -> PyResult<PyComparisonValue> {
1368        op.eq_only(|| {
1369            let other = class_or_notimplemented!(Self, other);
1370            Ok(PyComparisonValue::Implemented(
1371                zelf.function.is(&other.function) && zelf.object.is(&other.object),
1372            ))
1373        })
1374    }
1375}
1376
1377impl Hashable for PyBoundMethod {
1378    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
1379        let self_hash = crate::common::hash::hash_object_id_raw(zelf.object.get_id());
1380        let func_hash = zelf.function.hash(vm)?;
1381        Ok(crate::common::hash::fix_sentinel(self_hash ^ func_hash))
1382    }
1383}
1384
1385impl GetAttr for PyBoundMethod {
1386    fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
1387        let class_attr = vm
1388            .ctx
1389            .interned_str(name)
1390            .and_then(|attr_name| zelf.get_class_attr(attr_name));
1391        if let Some(obj) = class_attr {
1392            return vm.call_if_get_descriptor(&obj, zelf.to_owned().into());
1393        }
1394        zelf.function.get_attr(name, vm)
1395    }
1396}
1397
1398impl GetDescriptor for PyBoundMethod {
1399    fn descr_get(
1400        zelf: &PyObject,
1401        _obj: Option<&PyObject>,
1402        _cls: Option<&PyObject>,
1403        _vm: &VirtualMachine,
1404    ) -> PyResult {
1405        Ok(zelf.to_owned())
1406    }
1407}
1408
1409#[derive(FromArgs)]
1410pub struct PyBoundMethodNewArgs {
1411    #[pyarg(positional)]
1412    function: PyObjectRef,
1413    #[pyarg(positional)]
1414    object: PyObjectRef,
1415}
1416
1417impl Constructor for PyBoundMethod {
1418    type Args = PyBoundMethodNewArgs;
1419
1420    fn py_new(
1421        _cls: &Py<PyType>,
1422        Self::Args { function, object }: Self::Args,
1423        vm: &VirtualMachine,
1424    ) -> PyResult<Self> {
1425        if !function.is_callable() {
1426            return Err(vm.new_type_error("first argument must be callable"));
1427        }
1428        if vm.is_none(&object) {
1429            return Err(vm.new_type_error("instance must not be None"));
1430        }
1431        Ok(Self::new(object, function))
1432    }
1433}
1434
1435impl PyBoundMethod {
1436    #[must_use]
1437    pub const fn new(object: PyObjectRef, function: PyObjectRef) -> Self {
1438        Self { object, function }
1439    }
1440
1441    #[inline]
1442    pub(crate) fn function_obj(&self) -> &PyObject {
1443        &self.function
1444    }
1445
1446    #[inline]
1447    pub(crate) fn self_obj(&self) -> &PyObject {
1448        &self.object
1449    }
1450
1451    #[deprecated(note = "Use `Self::new(object, function).into_ref(ctx)` instead")]
1452    pub fn new_ref(object: PyObjectRef, function: PyObjectRef, ctx: &Context) -> PyRef<Self> {
1453        Self::new(object, function).into_ref(ctx)
1454    }
1455}
1456
1457#[pyclass(
1458    with(
1459        Callable,
1460        Comparable,
1461        Hashable,
1462        GetAttr,
1463        GetDescriptor,
1464        Constructor,
1465        Representable
1466    ),
1467    flags(IMMUTABLETYPE, HAS_WEAKREF)
1468)]
1469impl Py<PyBoundMethod> {
1470    #[pymethod]
1471    fn __reduce__(
1472        &self,
1473        vm: &VirtualMachine,
1474    ) -> PyResult<(PyObjectRef, (PyObjectRef, PyObjectRef))> {
1475        let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
1476        let func_self = self.object.clone();
1477        let func_name = self.function.get_attr("__name__", vm)?;
1478        Ok((builtins_getattr, (func_self, func_name)))
1479    }
1480
1481    #[pygetset]
1482    fn __doc__(&self, vm: &VirtualMachine) -> PyResult {
1483        self.function.get_attr("__doc__", vm)
1484    }
1485
1486    #[pygetset]
1487    fn __module__(&self, vm: &VirtualMachine) -> Option<PyObjectRef> {
1488        self.function.get_attr("__module__", vm).ok()
1489    }
1490
1491    #[pymethod]
1492    fn __dir__(&self, vm: &VirtualMachine) -> PyResult<PyList> {
1493        let func_dir = vm.dir(Some(self.function.clone()))?;
1494
1495        let bound_only = [
1496            "__self__",
1497            "__func__",
1498            "__doc__",
1499            "__module__",
1500            "__call__",
1501            "__get__",
1502            "__repr__",
1503        ];
1504
1505        let mut seen = std::collections::HashSet::new();
1506        let mut result: Vec<PyObjectRef> = Vec::new();
1507
1508        for item in func_dir.borrow_vec().iter() {
1509            if let Ok(s) = item.clone().downcast::<PyStr>() {
1510                seen.insert(s.as_wtf8().to_string());
1511            }
1512            result.push(item.clone());
1513        }
1514
1515        for name in bound_only {
1516            if seen.insert(name.to_owned()) {
1517                result.push(vm.ctx.new_str(name).into());
1518            }
1519        }
1520
1521        Ok(PyList::from(result))
1522    }
1523}
1524
1525impl PyPayload for PyBoundMethod {
1526    #[inline]
1527    fn class(ctx: &Context) -> &'static Py<PyType> {
1528        ctx.types.bound_method_type
1529    }
1530}
1531
1532impl Representable for PyBoundMethod {
1533    #[inline]
1534    fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
1535        let func_name = if let Some(qname) =
1536            vm.get_attribute_opt(&zelf.function, identifier!(vm, __qualname__))?
1537        {
1538            Some(qname)
1539        } else {
1540            vm.get_attribute_opt(&zelf.function, identifier!(vm, __name__))?
1541        };
1542        let func_name: Option<PyStrRef> = func_name.and_then(|o| o.downcast().ok());
1543        let object_repr = zelf.object.repr(vm)?;
1544        let name = func_name
1545            .as_ref()
1546            .map_or_else(|| "?".as_ref(), |s| s.as_wtf8());
1547        Ok(wtf8_concat!(
1548            "<bound method ",
1549            name,
1550            " of ",
1551            object_repr.as_wtf8(),
1552            ">"
1553        ))
1554    }
1555}
1556
1557#[pyclass(module = false, name = "cell", unhashable = true, traverse)]
1558#[derive(Debug, Default)]
1559pub(crate) struct PyCell {
1560    contents: PyMutex<Option<PyObjectRef>>,
1561}
1562
1563pub(crate) type PyCellRef = PyRef<PyCell>;
1564
1565impl PyPayload for PyCell {
1566    #[inline]
1567    fn class(ctx: &Context) -> &'static Py<PyType> {
1568        ctx.types.cell_type
1569    }
1570}
1571
1572impl Constructor for PyCell {
1573    type Args = OptionalArg;
1574
1575    fn py_new(_cls: &Py<PyType>, value: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
1576        Ok(Self::new(value.into_option()))
1577    }
1578}
1579
1580impl PyCell {
1581    pub(crate) const fn new(contents: Option<PyObjectRef>) -> Self {
1582        Self {
1583            contents: PyMutex::new(contents),
1584        }
1585    }
1586
1587    pub(crate) fn get(&self) -> Option<PyObjectRef> {
1588        self.contents.lock().clone()
1589    }
1590
1591    pub(crate) fn set(&self, x: Option<PyObjectRef>) {
1592        // What was here is released after the lock, the way `Py_XSETREF` stores
1593        // before it decrefs. Releasing it under the lock would let a `__del__`
1594        // that reads this cell wait on a lock this call still holds.
1595        let replaced = core::mem::replace(&mut *self.contents.lock(), x);
1596        drop(replaced);
1597    }
1598}
1599
1600#[pyclass(with(Constructor, Representable))]
1601impl Py<PyCell> {
1602    #[pyslot]
1603    fn slot_richcompare(
1604        zelf: &PyObject,
1605        other: &PyObject,
1606        op: PyComparisonOp,
1607        vm: &VirtualMachine,
1608    ) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
1609        let (Some(zelf), Some(other)) = (
1610            zelf.downcast_ref::<PyCell>(),
1611            other.downcast_ref::<PyCell>(),
1612        ) else {
1613            return Ok(Either::B(PyComparisonValue::NotImplemented));
1614        };
1615        // compare cells by contents; empty cells come before anything else
1616        match (zelf.get(), other.get()) {
1617            (Some(a), Some(b)) => a.rich_compare(b, op, vm).map(Either::A),
1618            (a, b) => Ok(Either::B(op.eval_ord(b.is_none().cmp(&a.is_none())).into())),
1619        }
1620    }
1621
1622    #[pygetset]
1623    fn cell_contents(&self, vm: &VirtualMachine) -> PyResult {
1624        self.get()
1625            .ok_or_else(|| vm.new_value_error("Cell is empty"))
1626    }
1627
1628    #[pygetset(setter)]
1629    fn set_cell_contents(&self, x: PySetterValue) {
1630        match x {
1631            PySetterValue::Assign(value) => self.set(Some(value)),
1632            PySetterValue::Delete => self.set(None),
1633        }
1634    }
1635}
1636
1637impl Representable for PyCell {
1638    #[inline]
1639    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
1640        let id = zelf.get_id();
1641        Ok(match zelf.get() {
1642            Some(value) => {
1643                let type_name = value.class().slot_name();
1644                // CPython renders the type name with "%.80s", which reads at
1645                // most 80 bytes and drops a character left incomplete by the cut.
1646                let mut end = type_name.len().min(80);
1647                while !type_name.is_char_boundary(end) {
1648                    end -= 1;
1649                }
1650                format!(
1651                    "<cell at {id:#x}: {} object at {:#x}>",
1652                    &type_name[..end],
1653                    value.get_id()
1654                )
1655            }
1656            None => format!("<cell at {id:#x}: empty>"),
1657        })
1658    }
1659}
1660
1661/// Largest keyword count the in-place fast path below handles with a
1662/// stack-allocated scratch buffer. Calls with more keywords than this simply
1663/// fall back to the slow path (extremely rare in practice).
1664const MAX_INLINE_KW: usize = 16;
1665
1666/// Try to resolve every keyword in `kwnames` to a distinct fastlocals slot in
1667/// `posonlyarg_count..arg_count` that isn't already filled by a positional
1668/// argument, without allocating an `IndexMap`, a `Vec`, or cloning any
1669/// keyword name.
1670///
1671/// On success, `args` is reordered into positional order in place (ready for
1672/// [`PyFunction::prepare_exact_args_frame`]) and returned as `Ok`. On any
1673/// mismatch (too many keywords, unknown keyword, positional/keyword overlap,
1674/// non-str/non-UTF8 name) `args` is hand back completely untouched as `Err`
1675/// so the caller can fall back to the slow path, which reproduces CPython's
1676/// exact error messages.
1677///
1678/// Only called when `nargs + kwnames.len() == code.arg_count`, i.e. every
1679/// parameter is exactly filled by the call with no defaults needed. That
1680/// invariant means the keyword values, initially at `args[nargs..]`, are
1681/// exactly the values for slots `nargs..arg_count` in some order — so the
1682/// whole reorder happens by draining that suffix into a small on-stack
1683/// buffer and pushing it back in the resolved order. `args`'s original
1684/// allocation is reused; no new allocation is needed.
1685fn try_reorder_simple_kwargs(
1686    code: &Py<PyCode>,
1687    mut args: Vec<PyObjectRef>,
1688    nargs: usize,
1689    kwnames: &[PyObjectRef],
1690) -> Result<Vec<PyObjectRef>, Vec<PyObjectRef>> {
1691    let arg_count = code.arg_count as usize;
1692    let posonly = code.posonlyarg_count as usize;
1693    let kw_count = kwnames.len();
1694    if kw_count > MAX_INLINE_KW {
1695        return Err(args);
1696    }
1697
1698    // Resolve target slots (relative to `nargs`) first, without touching
1699    // `args`, so a mismatch can bail out leaving `args` untouched.
1700    let mut rel_positions = [0usize; MAX_INLINE_KW];
1701    for (i, name_obj) in kwnames.iter().enumerate() {
1702        let Some(name_str) = name_obj.downcast_ref::<PyStr>().and_then(|s| s.to_str()) else {
1703            return Err(args);
1704        };
1705        let Some(pos) = code.varnames[posonly..arg_count]
1706            .iter()
1707            .position(|v| v.as_str() == name_str)
1708            .map(|p| p + posonly)
1709        else {
1710            // Unexpected keyword argument; let the slow path report it.
1711            return Err(args);
1712        };
1713        let rel = match pos.checked_sub(nargs) {
1714            // Positional/keyword overlap; let the slow path report the
1715            // exact "multiple values for argument" error.
1716            None => return Err(args),
1717            Some(rel) => rel,
1718        };
1719        if rel_positions[..i].contains(&rel) {
1720            // Duplicate keyword landing on the same slot.
1721            return Err(args);
1722        }
1723        rel_positions[i] = rel;
1724    }
1725
1726    // Every keyword maps to a distinct free slot in nargs..arg_count.
1727    // Drain the keyword values into a stack buffer ordered by slot, then
1728    // push them back — reusing `args`'s own allocation, no heap Vec needed.
1729    let mut buf: [Option<PyObjectRef>; MAX_INLINE_KW] = [const { None }; MAX_INLINE_KW];
1730    for (i, value) in args.drain(nargs..nargs + kw_count).enumerate() {
1731        buf[rel_positions[i]] = Some(value);
1732    }
1733    for slot in buf.iter_mut().take(kw_count) {
1734        args.push(slot.take().unwrap());
1735    }
1736    Ok(args)
1737}
1738
1739/// Vectorcall implementation for PyFunction (PEP 590).
1740/// Takes owned args to avoid cloning when filling fastlocals.
1741pub(crate) fn vectorcall_function(
1742    zelf_obj: &PyObject,
1743    mut args: Vec<PyObjectRef>,
1744    nargs: usize,
1745    kwnames: Option<&[PyObjectRef]>,
1746    vm: &VirtualMachine,
1747) -> PyResult {
1748    let zelf: &Py<PyFunction> = zelf_obj.downcast_ref().unwrap();
1749    let code: &Py<PyCode> = &zelf.code;
1750
1751    let has_kwargs = kwnames.is_some_and(|kw| !kw.is_empty());
1752    if zelf.is_jitted() {
1753        let func_args = if has_kwargs {
1754            FuncArgs::from_vectorcall_owned(args, nargs, kwnames)
1755        } else {
1756            args.truncate(nargs);
1757            FuncArgs::from(args)
1758        };
1759        return zelf.invoke(func_args, vm);
1760    }
1761
1762    // Positional-only signature that a call can fill exactly, whether or not
1763    // the body is a generator: the two differ only in what the frame is for.
1764    let positional_only = code.flags.contains(bytecode::CodeFlags::OPTIMIZED)
1765        && !code.flags.contains(bytecode::CodeFlags::VARARGS)
1766        && !code.flags.contains(bytecode::CodeFlags::VARKEYWORDS)
1767        && code.kwonlyarg_count == 0;
1768    let is_generator_like = code.flags.intersects(
1769        bytecode::CodeFlags::GENERATOR
1770            | bytecode::CodeFlags::COROUTINE
1771            | bytecode::CodeFlags::ASYNC_GENERATOR,
1772    );
1773    let base_simple = positional_only && !is_generator_like;
1774
1775    if !has_kwargs && positional_only && is_generator_like && nargs == code.arg_count as usize {
1776        // FAST PATH: generator/coroutine call, exact arg count. Binds the
1777        // arguments into the new frame and hands back the generator without
1778        // building a `FuncArgs`. This is the shape every generator expression
1779        // is called in, and a fresh `MAKE_FUNCTION` each time keeps those out
1780        // of the call-site specialization that would otherwise catch it.
1781        args.truncate(nargs);
1782        return Ok(zelf.make_generator_exact_args(args.into_iter(), vm));
1783    }
1784
1785    if !has_kwargs && base_simple && nargs == code.arg_count as usize {
1786        // FAST PATH: simple positional-only call, exact arg count.
1787        // Move owned args directly into fastlocals — no clone needed.
1788        args.truncate(nargs);
1789        let frame = zelf.prepare_exact_args_frame(args.into_iter(), vm);
1790
1791        let result = vm.run_frame(frame.clone());
1792        crate::frame::release_datastack_frame(&frame, vm);
1793        return result;
1794    }
1795
1796    if has_kwargs
1797        && base_simple
1798        && let Some(kwnames) = kwnames
1799        && nargs + kwnames.len() == code.arg_count as usize
1800    {
1801        // FAST PATH: plain function, no *args/**kwargs/kwonly, every
1802        // parameter filled exactly by this call. Reorder into positional
1803        // order with no IndexMap/Wtf8Buf allocation; any mismatch falls
1804        // through to the slow path below with `args` untouched.
1805        match try_reorder_simple_kwargs(code, args, nargs, kwnames) {
1806            Ok(ordered) => {
1807                let frame = zelf.prepare_exact_args_frame(ordered.into_iter(), vm);
1808                let result = vm.run_frame(frame.clone());
1809                crate::frame::release_datastack_frame(&frame, vm);
1810                return result;
1811            }
1812            Err(restored) => {
1813                args = restored;
1814            }
1815        }
1816    }
1817
1818    // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
1819    let func_args = if has_kwargs {
1820        FuncArgs::from_vectorcall_owned(args, nargs, kwnames)
1821    } else {
1822        args.truncate(nargs);
1823        FuncArgs::from(args)
1824    };
1825
1826    zelf.invoke(func_args, vm)
1827}
1828
1829/// Vectorcall implementation for PyBoundMethod (PEP 590).
1830fn vectorcall_bound_method(
1831    zelf_obj: &PyObject,
1832    mut args: Vec<PyObjectRef>,
1833    nargs: usize,
1834    kwnames: Option<&[PyObjectRef]>,
1835    vm: &VirtualMachine,
1836) -> PyResult {
1837    let zelf: &Py<PyBoundMethod> = zelf_obj.downcast_ref().unwrap();
1838
1839    // Insert self at front of existing Vec (avoids 2nd allocation).
1840    // O(n) memmove is cheaper than a 2nd heap alloc+dealloc for typical arg counts.
1841    args.insert(0, zelf.object.clone());
1842    let new_nargs = nargs + 1;
1843    zelf.function.vectorcall(args, new_nargs, kwnames, vm)
1844}
1845
1846pub(crate) fn init(context: &'static Context) {
1847    PyFunction::extend_class(context, context.types.function_type);
1848    context
1849        .types
1850        .function_type
1851        .slots
1852        .vectorcall
1853        .store(Some(vectorcall_function));
1854
1855    PyBoundMethod::extend_class(context, context.types.bound_method_type);
1856    context
1857        .types
1858        .bound_method_type
1859        .slots
1860        .vectorcall
1861        .store(Some(vectorcall_bound_method));
1862
1863    PyCell::extend_class(context, context.types.cell_type);
1864}