diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index ecd574ab83..901f596cc3 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -698,8 +698,6 @@ class NewPoint(tuple): self.assertEqual(np.x, 1) self.assertEqual(np.y, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_new_builtins_issue_43102(self): obj = namedtuple('C', ()) new_func = obj.__new__ diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 05ffafd1d0..b1334d22a4 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -8,7 +8,7 @@ use super::{ #[cfg(feature = "jit")] use crate::common::lock::OnceCell; use crate::common::lock::PyMutex; -use crate::convert::ToPyObject; +use crate::convert::{ToPyObject, TryFromObject}; use crate::function::ArgMapping; use crate::object::{Traverse, TraverseFn}; use crate::{ @@ -31,6 +31,7 @@ use rustpython_jit::CompiledCode; pub struct PyFunction { code: PyRef, globals: PyDictRef, + builtins: PyObjectRef, closure: Option>, defaults_and_kwdefaults: PyMutex<(Option, Option)>, name: PyMutex, @@ -53,6 +54,7 @@ unsafe impl Traverse for PyFunction { impl PyFunction { #[allow(clippy::too_many_arguments)] + #[inline] pub(crate) fn new( code: PyRef, globals: PyDictRef, @@ -62,24 +64,42 @@ impl PyFunction { qualname: PyStrRef, type_params: PyTupleRef, annotations: PyDictRef, - module: PyObjectRef, doc: PyObjectRef, - ) -> Self { + vm: &VirtualMachine, + ) -> PyResult { let name = PyMutex::new(code.obj_name.to_owned()); - PyFunction { + let module = vm.unwrap_or_none(globals.get_item_opt(identifier!(vm, __name__), vm)?); + let builtins = globals.get_item("__builtins__", vm).unwrap_or_else(|_| { + // If not in globals, inherit from current execution context + if let Some(frame) = vm.current_frame() { + frame.builtins.clone().into() + } else { + vm.builtins.clone().into() + } + }); + + let func = PyFunction { code, globals, + builtins, closure, defaults_and_kwdefaults: PyMutex::new((defaults, kw_only_defaults)), name, qualname: PyMutex::new(qualname), type_params: PyMutex::new(type_params), - #[cfg(feature = "jit")] - jitted_code: OnceCell::new(), annotations: PyMutex::new(annotations), module: PyMutex::new(module), doc: PyMutex::new(doc), - } + #[cfg(feature = "jit")] + jitted_code: OnceCell::new(), + }; + + // let name = qualname.as_str().split('.').next_back().unwrap(); + // func.set_attr(identifier!(vm, __name__), vm.new_pyobj(name), vm)?; + // func.set_attr(identifier!(vm, __qualname__), qualname, vm)?; + // func.set_attr(identifier!(vm, __doc__), doc, vm)?; + + Ok(func) } fn fill_locals_from_args( @@ -362,7 +382,7 @@ impl PyPayload for PyFunction { } #[pyclass( - with(GetDescriptor, Callable, Representable), + with(GetDescriptor, Callable, Representable, Constructor), flags(HAS_DICT, METHOD_DESCRIPTOR) )] impl PyFunction { @@ -406,6 +426,12 @@ impl PyFunction { Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.to_pyobject(vm)))) } + #[pymember(magic)] + fn builtins(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult { + let zelf = Self::_as_pyref(&zelf, vm)?; + Ok(zelf.builtins.clone()) + } + #[pygetset(magic)] fn name(&self) -> PyStrRef { self.name.lock().clone() @@ -555,6 +581,81 @@ impl Representable for PyFunction { } } +#[derive(FromArgs)] +pub struct PyFunctionNewArgs { + #[pyarg(positional)] + code: PyRef, + #[pyarg(positional)] + globals: PyDictRef, + #[pyarg(any, optional)] + name: OptionalArg, + #[pyarg(any, optional)] + defaults: OptionalArg, + #[pyarg(any, optional)] + closure: OptionalArg, + #[pyarg(any, optional)] + kwdefaults: OptionalArg, +} + +impl Constructor for PyFunction { + type Args = PyFunctionNewArgs; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Handle closure - must be a tuple of cells + let closure = if let Some(closure_tuple) = args.closure.into_option() { + // Check that closure length matches code's free variables + if closure_tuple.len() != args.code.freevars.len() { + return Err(vm.new_value_error(format!( + "{} requires closure of length {}, not {}", + args.code.obj_name, + args.code.freevars.len(), + closure_tuple.len() + ))); + } + + // Validate that all items are cells and create typed tuple + let typed_closure = + PyTupleTyped::::try_from_object(vm, closure_tuple.into())?; + Some(typed_closure) + } else if !args.code.freevars.is_empty() { + return Err(vm.new_type_error("arg 5 (closure) must be tuple".to_owned())); + } else { + None + }; + + // Get function name - use provided name or default to code object name + let name = args + .name + .into_option() + .unwrap_or_else(|| PyStr::from(args.code.obj_name.as_str()).into_ref(&vm.ctx)); + + // Get qualname - for now just use the name + let qualname = name.clone(); + + // Create empty type_params and annotations + let type_params = vm.ctx.new_tuple(vec![]); + let annotations = vm.ctx.new_dict(); + + // Get doc from code object - for now just use None + let doc = vm.ctx.none(); + + let func = PyFunction::new( + args.code, + args.globals, + closure, + args.defaults.into_option(), + args.kwdefaults.into_option(), + qualname, + type_params, + annotations, + doc, + vm, + )?; + + func.into_ref_with_type(vm, cls).map(Into::into) + } +} + #[pyclass(module = false, name = "method", traverse)] #[derive(Debug)] pub struct PyBoundMethod { diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 6539a5a390..925899564a 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1905,8 +1905,6 @@ impl ExecutingFrame<'_> { None }; - let module = vm.unwrap_or_none(self.globals.get_item_opt(identifier!(vm, __name__), vm)?); - // pop argc arguments // argument: name, args, globals // let scope = self.scope.clone(); @@ -1919,16 +1917,11 @@ impl ExecutingFrame<'_> { qualified_name.clone(), type_params, annotations.downcast().unwrap(), - module, vm.ctx.none(), - ) + vm, + )? .into_pyobject(vm); - let name = qualified_name.as_str().split('.').next_back().unwrap(); - func_obj.set_attr(identifier!(vm, __name__), vm.new_pyobj(name), vm)?; - func_obj.set_attr(identifier!(vm, __qualname__), qualified_name, vm)?; - func_obj.set_attr(identifier!(vm, __doc__), vm.ctx.none(), vm)?; - self.push_value(func_obj); Ok(None) }