1#[cfg(feature = "jit")]
2mod jitfunc;
3
4use super::{
5 tuple::PyTupleTyped, PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef,
6 PyTupleRef, PyType, PyTypeRef,
7};
8#[cfg(feature = "jit")]
9use crate::common::lock::OnceCell;
10use crate::common::lock::PyMutex;
11use crate::convert::ToPyObject;
12use crate::function::ArgMapping;
13use crate::object::{Traverse, TraverseFn};
14use crate::{
15 bytecode,
16 class::PyClassImpl,
17 frame::Frame,
18 function::{FuncArgs, OptionalArg, PyComparisonValue, PySetterValue},
19 scope::Scope,
20 types::{
21 Callable, Comparable, Constructor, GetAttr, GetDescriptor, PyComparisonOp, Representable,
22 },
23 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
24};
25use itertools::Itertools;
26#[cfg(feature = "jit")]
27use rustpython_jit::CompiledCode;
28
29#[pyclass(module = false, name = "function", traverse = "manual")]
30#[derive(Debug)]
31pub struct PyFunction {
32 code: PyRef<PyCode>,
33 globals: PyDictRef,
34 closure: Option<PyTupleTyped<PyCellRef>>,
35 defaults_and_kwdefaults: PyMutex<(Option<PyTupleRef>, Option<PyDictRef>)>,
36 name: PyMutex<PyStrRef>,
37 qualname: PyMutex<PyStrRef>,
38 type_params: PyMutex<PyTupleRef>,
39 #[cfg(feature = "jit")]
40 jitted_code: OnceCell<CompiledCode>,
41}
42
43unsafe impl Traverse for PyFunction {
44 fn traverse(&self, tracer_fn: &mut TraverseFn) {
45 self.globals.traverse(tracer_fn);
46 self.closure.traverse(tracer_fn);
47 self.defaults_and_kwdefaults.traverse(tracer_fn);
48 }
49}
50
51impl PyFunction {
52 pub(crate) fn new(
53 code: PyRef<PyCode>,
54 globals: PyDictRef,
55 closure: Option<PyTupleTyped<PyCellRef>>,
56 defaults: Option<PyTupleRef>,
57 kw_only_defaults: Option<PyDictRef>,
58 qualname: PyStrRef,
59 type_params: PyTupleRef,
60 ) -> Self {
61 let name = PyMutex::new(code.obj_name.to_owned());
62 PyFunction {
63 code,
64 globals,
65 closure,
66 defaults_and_kwdefaults: PyMutex::new((defaults, kw_only_defaults)),
67 name,
68 qualname: PyMutex::new(qualname),
69 type_params: PyMutex::new(type_params),
70 #[cfg(feature = "jit")]
71 jitted_code: OnceCell::new(),
72 }
73 }
74
75 fn fill_locals_from_args(
76 &self,
77 frame: &Frame,
78 func_args: FuncArgs,
79 vm: &VirtualMachine,
80 ) -> PyResult<()> {
81 let code = &*self.code;
82 let nargs = func_args.args.len();
83 let nexpected_args = code.arg_count as usize;
84 let total_args = code.arg_count as usize + code.kwonlyarg_count as usize;
85 let mut fastlocals = frame.fastlocals.lock();
94
95 let mut args_iter = func_args.args.into_iter();
96
97 for (local, arg) in Iterator::zip(
101 fastlocals.iter_mut().take(nexpected_args),
102 args_iter.by_ref().take(nargs),
103 ) {
104 *local = Some(arg);
105 }
106
107 let mut vararg_offset = total_args;
108 if code.flags.contains(bytecode::CodeFlags::HAS_VARARGS) {
110 let vararg_value = vm.ctx.new_tuple(args_iter.collect());
111 fastlocals[vararg_offset] = Some(vararg_value.into());
112 vararg_offset += 1;
113 } else {
114 if nargs > nexpected_args {
116 return Err(vm.new_type_error(format!(
117 "{}() takes {} positional arguments but {} were given",
118 self.qualname(),
119 nexpected_args,
120 nargs
121 )));
122 }
123 }
124
125 let kwargs = if code.flags.contains(bytecode::CodeFlags::HAS_VARKEYWORDS) {
127 let d = vm.ctx.new_dict();
128 fastlocals[vararg_offset] = Some(d.clone().into());
129 Some(d)
130 } else {
131 None
132 };
133
134 let argpos = |range: std::ops::Range<_>, name: &str| {
135 code.varnames
136 .iter()
137 .enumerate()
138 .skip(range.start)
139 .take(range.end - range.start)
140 .find(|(_, s)| s.as_str() == name)
141 .map(|(p, _)| p)
142 };
143
144 let mut posonly_passed_as_kwarg = Vec::new();
145 for (name, value) in func_args.kwargs {
147 if let Some(pos) = argpos(code.posonlyarg_count as usize..total_args, &name) {
149 let slot = &mut fastlocals[pos];
150 if slot.is_some() {
151 return Err(vm.new_type_error(format!(
152 "{}() got multiple values for argument '{}'",
153 self.qualname(),
154 name
155 )));
156 }
157 *slot = Some(value);
158 } else if let Some(kwargs) = kwargs.as_ref() {
159 kwargs.set_item(&name, value, vm)?;
160 } else if argpos(0..code.posonlyarg_count as usize, &name).is_some() {
161 posonly_passed_as_kwarg.push(name);
162 } else {
163 return Err(vm.new_type_error(format!(
164 "{}() got an unexpected keyword argument '{}'",
165 self.qualname(),
166 name
167 )));
168 }
169 }
170 if !posonly_passed_as_kwarg.is_empty() {
171 return Err(vm.new_type_error(format!(
172 "{}() got some positional-only arguments passed as keyword arguments: '{}'",
173 self.qualname(),
174 posonly_passed_as_kwarg.into_iter().format(", "),
175 )));
176 }
177
178 let mut defaults_and_kwdefaults = None;
179 macro_rules! get_defaults {
181 () => {{
182 defaults_and_kwdefaults
183 .get_or_insert_with(|| self.defaults_and_kwdefaults.lock().clone())
184 }};
185 }
186
187 if nargs < nexpected_args {
190 let defaults = get_defaults!().0.as_ref().map(|tup| tup.as_slice());
191 let ndefs = defaults.map_or(0, |d| d.len());
192
193 let nrequired = code.arg_count as usize - ndefs;
194
195 let mut missing: Vec<_> = (nargs..nrequired)
198 .filter_map(|i| {
199 if fastlocals[i].is_none() {
200 Some(&code.varnames[i])
201 } else {
202 None
203 }
204 })
205 .collect();
206 let missing_args_len = missing.len();
207
208 if !missing.is_empty() {
209 let last = if missing.len() > 1 {
210 missing.pop()
211 } else {
212 None
213 };
214
215 let (and, right) = if let Some(last) = last {
216 (
217 if missing.len() == 1 {
218 "' and '"
219 } else {
220 "', and '"
221 },
222 last.as_str(),
223 )
224 } else {
225 ("", "")
226 };
227
228 return Err(vm.new_type_error(format!(
229 "{}() missing {} required positional argument{}: '{}{}{}'",
230 self.qualname(),
231 missing_args_len,
232 if missing_args_len == 1 { "" } else { "s" },
233 missing.iter().join("', '"),
234 and,
235 right,
236 )));
237 }
238
239 if let Some(defaults) = defaults {
240 let n = std::cmp::min(nargs, nexpected_args);
241 let i = n.saturating_sub(nrequired);
242
243 for i in i..defaults.len() {
246 let slot = &mut fastlocals[nrequired + i];
247 if slot.is_none() {
248 *slot = Some(defaults[i].clone());
249 }
250 }
251 }
252 };
253
254 if code.kwonlyarg_count > 0 {
255 for (slot, kwarg) in fastlocals
259 .iter_mut()
260 .zip(&*code.varnames)
261 .skip(code.arg_count as usize)
262 .take(code.kwonlyarg_count as usize)
263 .filter(|(slot, _)| slot.is_none())
264 {
265 if let Some(defaults) = &get_defaults!().1 {
266 if let Some(default) = defaults.get_item_opt(&**kwarg, vm)? {
267 *slot = Some(default);
268 continue;
269 }
270 }
271
272 return Err(
274 vm.new_type_error(format!("Missing required kw only argument: '{kwarg}'"))
275 );
276 }
277 }
278
279 if let Some(cell2arg) = code.cell2arg.as_deref() {
280 for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
281 let x = fastlocals[*arg_idx as usize].take();
282 frame.cells_frees[cell_idx].set(x);
283 }
284 }
285
286 Ok(())
287 }
288
289 pub fn invoke_with_locals(
290 &self,
291 func_args: FuncArgs,
292 locals: Option<ArgMapping>,
293 vm: &VirtualMachine,
294 ) -> PyResult {
295 #[cfg(feature = "jit")]
296 if let Some(jitted_code) = self.jitted_code.get() {
297 match jitfunc::get_jit_args(self, &func_args, jitted_code, vm) {
298 Ok(args) => {
299 return Ok(args.invoke().to_pyobject(vm));
300 }
301 Err(err) => info!(
302 "jit: function `{}` is falling back to being interpreted because of the \
303 error: {}",
304 self.code.obj_name, err
305 ),
306 }
307 }
308
309 let code = &self.code;
310
311 let locals = if self.code.flags.contains(bytecode::CodeFlags::NEW_LOCALS) {
312 ArgMapping::from_dict_exact(vm.ctx.new_dict())
313 } else if let Some(locals) = locals {
314 locals
315 } else {
316 ArgMapping::from_dict_exact(self.globals.clone())
317 };
318
319 let frame = Frame::new(
321 code.clone(),
322 Scope::new(Some(locals), self.globals.clone()),
323 vm.builtins.dict(),
324 self.closure.as_ref().map_or(&[], |c| c.as_slice()),
325 vm,
326 )
327 .into_ref(&vm.ctx);
328
329 self.fill_locals_from_args(&frame, func_args, vm)?;
330
331 let is_gen = code.flags.contains(bytecode::CodeFlags::IS_GENERATOR);
333 let is_coro = code.flags.contains(bytecode::CodeFlags::IS_COROUTINE);
334 match (is_gen, is_coro) {
335 (true, false) => Ok(PyGenerator::new(frame, self.name()).into_pyobject(vm)),
336 (false, true) => Ok(PyCoroutine::new(frame, self.name()).into_pyobject(vm)),
337 (true, true) => Ok(PyAsyncGen::new(frame, self.name()).into_pyobject(vm)),
338 (false, false) => vm.run_frame(frame),
339 }
340 }
341
342 #[inline(always)]
343 pub fn invoke(&self, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
344 self.invoke_with_locals(func_args, None, vm)
345 }
346}
347
348impl PyPayload for PyFunction {
349 fn class(ctx: &Context) -> &'static Py<PyType> {
350 ctx.types.function_type
351 }
352}
353
354#[pyclass(
355 with(GetDescriptor, Callable, Representable),
356 flags(HAS_DICT, METHOD_DESCRIPTOR)
357)]
358impl PyFunction {
359 #[pygetset(magic)]
360 fn code(&self) -> PyRef<PyCode> {
361 self.code.clone()
362 }
363
364 #[pygetset(magic)]
365 fn defaults(&self) -> Option<PyTupleRef> {
366 self.defaults_and_kwdefaults.lock().0.clone()
367 }
368 #[pygetset(magic, setter)]
369 fn set_defaults(&self, defaults: Option<PyTupleRef>) {
370 self.defaults_and_kwdefaults.lock().0 = defaults
371 }
372
373 #[pygetset(magic)]
374 fn kwdefaults(&self) -> Option<PyDictRef> {
375 self.defaults_and_kwdefaults.lock().1.clone()
376 }
377 #[pygetset(magic, setter)]
378 fn set_kwdefaults(&self, kwdefaults: Option<PyDictRef>) {
379 self.defaults_and_kwdefaults.lock().1 = kwdefaults
380 }
381
382 #[pymember(magic)]
388 fn globals(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
389 let zelf = Self::_as_pyref(&zelf, vm)?;
390 Ok(zelf.globals.clone().into())
391 }
392
393 #[pymember(magic)]
394 fn closure(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
395 let zelf = Self::_as_pyref(&zelf, vm)?;
396 Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.to_pyobject(vm))))
397 }
398
399 #[pygetset(magic)]
400 fn name(&self) -> PyStrRef {
401 self.name.lock().clone()
402 }
403
404 #[pygetset(magic, setter)]
405 fn set_name(&self, name: PyStrRef) {
406 *self.name.lock() = name;
407 }
408
409 #[pygetset(magic)]
410 fn qualname(&self) -> PyStrRef {
411 self.qualname.lock().clone()
412 }
413
414 #[pygetset(magic, setter)]
415 fn set_qualname(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
416 match value {
417 PySetterValue::Assign(value) => {
418 let Ok(qualname) = value.downcast::<PyStr>() else {
419 return Err(vm.new_type_error(
420 "__qualname__ must be set to a string object".to_string(),
421 ));
422 };
423 *self.qualname.lock() = qualname;
424 }
425 PySetterValue::Delete => {
426 return Err(
427 vm.new_type_error("__qualname__ must be set to a string object".to_string())
428 );
429 }
430 }
431 Ok(())
432 }
433
434 #[pygetset(magic)]
435 fn type_params(&self) -> PyTupleRef {
436 self.type_params.lock().clone()
437 }
438
439 #[pygetset(magic, setter)]
440 fn set_type_params(
441 &self,
442 value: PySetterValue<PyTupleRef>,
443 vm: &VirtualMachine,
444 ) -> PyResult<()> {
445 match value {
446 PySetterValue::Assign(value) => {
447 *self.type_params.lock() = value;
448 }
449 PySetterValue::Delete => {
450 return Err(
451 vm.new_type_error("__type_params__ must be set to a tuple object".to_string())
452 );
453 }
454 }
455 Ok(())
456 }
457
458 #[cfg(feature = "jit")]
459 #[pymethod(magic)]
460 fn jit(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> {
461 zelf.jitted_code
462 .get_or_try_init(|| {
463 let arg_types = jitfunc::get_jit_arg_types(&zelf, vm)?;
464 rustpython_jit::compile(&zelf.code.code, &arg_types)
465 .map_err(|err| jitfunc::new_jit_error(err.to_string(), vm))
466 })
467 .map(drop)
468 }
469}
470
471impl GetDescriptor for PyFunction {
472 fn descr_get(
473 zelf: PyObjectRef,
474 obj: Option<PyObjectRef>,
475 cls: Option<PyObjectRef>,
476 vm: &VirtualMachine,
477 ) -> PyResult {
478 let (_zelf, obj) = Self::_unwrap(&zelf, obj, vm)?;
479 let obj = if vm.is_none(&obj) && !Self::_cls_is(&cls, obj.class()) {
480 zelf
481 } else {
482 PyBoundMethod::new_ref(obj, zelf, &vm.ctx).into()
483 };
484 Ok(obj)
485 }
486}
487
488impl Callable for PyFunction {
489 type Args = FuncArgs;
490 #[inline]
491 fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
492 zelf.invoke(args, vm)
493 }
494}
495
496impl Representable for PyFunction {
497 #[inline]
498 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
499 Ok(format!(
500 "<function {} at {:#x}>",
501 zelf.qualname(),
502 zelf.get_id()
503 ))
504 }
505}
506
507#[pyclass(module = false, name = "method", traverse)]
508#[derive(Debug)]
509pub struct PyBoundMethod {
510 object: PyObjectRef,
511 function: PyObjectRef,
512}
513
514impl Callable for PyBoundMethod {
515 type Args = FuncArgs;
516 #[inline]
517 fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
518 args.prepend_arg(zelf.object.clone());
519 zelf.function.call(args, vm)
520 }
521}
522
523impl Comparable for PyBoundMethod {
524 fn cmp(
525 zelf: &Py<Self>,
526 other: &PyObject,
527 op: PyComparisonOp,
528 _vm: &VirtualMachine,
529 ) -> PyResult<PyComparisonValue> {
530 op.eq_only(|| {
531 let other = class_or_notimplemented!(Self, other);
532 Ok(PyComparisonValue::Implemented(
533 zelf.function.is(&other.function) && zelf.object.is(&other.object),
534 ))
535 })
536 }
537}
538
539impl GetAttr for PyBoundMethod {
540 fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
541 let class_attr = vm
542 .ctx
543 .interned_str(name)
544 .and_then(|attr_name| zelf.get_class_attr(attr_name));
545 if let Some(obj) = class_attr {
546 return vm.call_if_get_descriptor(&obj, zelf.to_owned().into());
547 }
548 zelf.function.get_attr(name, vm)
549 }
550}
551
552#[derive(FromArgs)]
553pub struct PyBoundMethodNewArgs {
554 #[pyarg(positional)]
555 function: PyObjectRef,
556 #[pyarg(positional)]
557 object: PyObjectRef,
558}
559
560impl Constructor for PyBoundMethod {
561 type Args = PyBoundMethodNewArgs;
562
563 fn py_new(
564 cls: PyTypeRef,
565 Self::Args { function, object }: Self::Args,
566 vm: &VirtualMachine,
567 ) -> PyResult {
568 PyBoundMethod::new(object, function)
569 .into_ref_with_type(vm, cls)
570 .map(Into::into)
571 }
572}
573
574impl PyBoundMethod {
575 fn new(object: PyObjectRef, function: PyObjectRef) -> Self {
576 PyBoundMethod { object, function }
577 }
578
579 pub fn new_ref(object: PyObjectRef, function: PyObjectRef, ctx: &Context) -> PyRef<Self> {
580 PyRef::new_ref(
581 Self::new(object, function),
582 ctx.types.bound_method_type.to_owned(),
583 None,
584 )
585 }
586}
587
588#[pyclass(
589 with(Callable, Comparable, GetAttr, Constructor, Representable),
590 flags(HAS_DICT)
591)]
592impl PyBoundMethod {
593 #[pymethod(magic)]
594 fn reduce(
595 &self,
596 vm: &VirtualMachine,
597 ) -> (Option<PyObjectRef>, (PyObjectRef, Option<PyObjectRef>)) {
598 let builtins_getattr = vm.builtins.get_attr("getattr", vm).ok();
599 let funcself = self.object.clone();
600 let funcname = self.function.get_attr("__name__", vm).ok();
601 (builtins_getattr, (funcself, funcname))
602 }
603
604 #[pygetset(magic)]
605 fn doc(&self, vm: &VirtualMachine) -> PyResult {
606 self.function.get_attr("__doc__", vm)
607 }
608
609 #[pygetset(magic)]
610 fn func(&self) -> PyObjectRef {
611 self.function.clone()
612 }
613
614 #[pygetset(name = "__self__")]
615 fn get_self(&self) -> PyObjectRef {
616 self.object.clone()
617 }
618
619 #[pygetset(magic)]
620 fn module(&self, vm: &VirtualMachine) -> Option<PyObjectRef> {
621 self.function.get_attr("__module__", vm).ok()
622 }
623
624 #[pygetset(magic)]
625 fn qualname(&self, vm: &VirtualMachine) -> PyResult {
626 if self
627 .function
628 .fast_isinstance(vm.ctx.types.builtin_function_or_method_type)
629 {
630 let obj_name = vm.get_attribute_opt(self.object.clone(), "__qualname__")?;
634 let obj_name: Option<PyStrRef> = obj_name.and_then(|o| o.downcast().ok());
635 return Ok(vm
636 .ctx
637 .new_str(format!(
638 "{}.__new__",
639 obj_name.as_ref().map_or("?", |s| s.as_str())
640 ))
641 .into());
642 }
643 self.function.get_attr("__qualname__", vm)
644 }
645}
646
647impl PyPayload for PyBoundMethod {
648 fn class(ctx: &Context) -> &'static Py<PyType> {
649 ctx.types.bound_method_type
650 }
651}
652
653impl Representable for PyBoundMethod {
654 #[inline]
655 fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
656 #[allow(clippy::needless_match)] let funcname =
658 if let Some(qname) = vm.get_attribute_opt(zelf.function.clone(), "__qualname__")? {
659 Some(qname)
660 } else {
661 vm.get_attribute_opt(zelf.function.clone(), "__name__")?
662 };
663 let funcname: Option<PyStrRef> = funcname.and_then(|o| o.downcast().ok());
664 Ok(format!(
665 "<bound method {} of {}>",
666 funcname.as_ref().map_or("?", |s| s.as_str()),
667 &zelf.object.repr(vm)?.as_str(),
668 ))
669 }
670}
671
672#[pyclass(module = false, name = "cell", traverse)]
673#[derive(Debug, Default)]
674pub(crate) struct PyCell {
675 contents: PyMutex<Option<PyObjectRef>>,
676}
677pub(crate) type PyCellRef = PyRef<PyCell>;
678
679impl PyPayload for PyCell {
680 fn class(ctx: &Context) -> &'static Py<PyType> {
681 ctx.types.cell_type
682 }
683}
684
685impl Constructor for PyCell {
686 type Args = OptionalArg;
687
688 fn py_new(cls: PyTypeRef, value: Self::Args, vm: &VirtualMachine) -> PyResult {
689 Self::new(value.into_option())
690 .into_ref_with_type(vm, cls)
691 .map(Into::into)
692 }
693}
694
695#[pyclass(with(Constructor))]
696impl PyCell {
697 pub fn new(contents: Option<PyObjectRef>) -> Self {
698 Self {
699 contents: PyMutex::new(contents),
700 }
701 }
702
703 pub fn get(&self) -> Option<PyObjectRef> {
704 self.contents.lock().clone()
705 }
706 pub fn set(&self, x: Option<PyObjectRef>) {
707 *self.contents.lock() = x;
708 }
709
710 #[pygetset]
711 fn cell_contents(&self, vm: &VirtualMachine) -> PyResult {
712 self.get()
713 .ok_or_else(|| vm.new_value_error("Cell is empty".to_owned()))
714 }
715 #[pygetset(setter)]
716 fn set_cell_contents(&self, x: PyObjectRef) {
717 self.set(Some(x))
718 }
719}
720
721pub fn init(context: &Context) {
722 PyFunction::extend_class(context, context.types.function_type);
723 PyBoundMethod::extend_class(context, context.types.bound_method_type);
724 PyCell::extend_class(context, context.types.cell_type);
725}