diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index f24521341..ff696c5c1 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -50,7 +50,6 @@ class FunctionPropertiesTest(FuncAttrsTest): def test_module(self): self.assertEqual(self.b.__module__, __name__) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_dir_includes_correct_attrs(self): self.b.known_attr = 7 self.assertIn('known_attr', dir(self.b), @@ -238,7 +237,6 @@ class FunctionPropertiesTest(FuncAttrsTest): func.__type_params__ = (T,) self.assertEqual(func.__type_params__, (T,)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test___code__(self): num_one, num_two = 7, 8 def a(): pass @@ -269,13 +267,11 @@ class FunctionPropertiesTest(FuncAttrsTest): self.fail("__code__ with different numbers of free vars should " "not be possible") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_blank_func_defaults(self): self.assertEqual(self.b.__defaults__, None) del self.b.__defaults__ self.assertEqual(self.b.__defaults__, None) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_func_default_args(self): def first_func(a, b): return a+b diff --git a/Lib/test/test_unittest/testmock/testpatch.py b/Lib/test/test_unittest/testmock/testpatch.py index 4f4b7f757..bd85fdcfc 100644 --- a/Lib/test/test_unittest/testmock/testpatch.py +++ b/Lib/test/test_unittest/testmock/testpatch.py @@ -2012,7 +2012,6 @@ class PatchTest(unittest.TestCase): self.assertEqual(dic2, origdic2) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_special_attrs(self): def foo(x=0): """TEST""" diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index f97037560..770af5621 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -2,8 +2,8 @@ mod jit; use super::{ - PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyModule, PyStr, PyStrRef, PyTuple, - PyTupleRef, PyType, object, + PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyList, PyModule, PyStr, PyStrRef, + PyTuple, PyTupleRef, PyType, object, }; use crate::common::hash::PyHash; use crate::common::lock::PyMutex; @@ -788,7 +788,17 @@ impl PyFunction { } #[pygetset(setter)] - fn set___code__(&self, code: PyRef, vm: &VirtualMachine) { + fn set___code__(&self, code: PyRef, vm: &VirtualMachine) -> PyResult<()> { + let n_free = code.freevars.len(); + let n_closure = self.closure.as_ref().map_or(0, |c| c.len()); + if n_closure != n_free { + return Err(vm.new_value_error(format!( + "{}() requires a code object with {} free vars, not {}", + self.qualname.lock(), + n_closure, + n_free, + ))); + } #[cfg(feature = "jit")] let mut jit_guard = self.jitted_code.lock(); self.code.swap_to_temporary_refs(code, vm); @@ -797,6 +807,7 @@ impl PyFunction { *jit_guard = None; } self.func_version.store(0, Relaxed); + Ok(()) } #[pygetset] @@ -804,8 +815,11 @@ impl PyFunction { self.defaults_and_kwdefaults.lock().0.clone() } #[pygetset(setter)] - fn set___defaults__(&self, defaults: Option) { - self.defaults_and_kwdefaults.lock().0 = defaults; + fn set___defaults__(&self, defaults: PySetterValue>) { + self.defaults_and_kwdefaults.lock().0 = match defaults { + PySetterValue::Assign(d) => d, + PySetterValue::Delete => None, + }; self.func_version.store(0, Relaxed); } @@ -814,8 +828,11 @@ impl PyFunction { self.defaults_and_kwdefaults.lock().1.clone() } #[pygetset(setter)] - fn set___kwdefaults__(&self, kwdefaults: Option) { - self.defaults_and_kwdefaults.lock().1 = kwdefaults; + fn set___kwdefaults__(&self, kwdefaults: PySetterValue>) { + self.defaults_and_kwdefaults.lock().1 = match kwdefaults { + PySetterValue::Assign(d) => d, + PySetterValue::Delete => None, + }; self.func_version.store(0, Relaxed); } @@ -1308,6 +1325,39 @@ impl PyBoundMethod { fn __module__(&self, vm: &VirtualMachine) -> Option { self.function.get_attr("__module__", vm).ok() } + + #[pymethod] + fn __dir__(&self, vm: &VirtualMachine) -> PyResult { + let func_dir = vm.dir(Some(self.function.clone()))?; + + let bound_only = [ + "__self__", + "__func__", + "__doc__", + "__module__", + "__call__", + "__get__", + "__repr__", + ]; + + let mut seen = std::collections::HashSet::new(); + let mut result: Vec = Vec::new(); + + for item in func_dir.borrow_vec().iter() { + if let Ok(s) = item.clone().downcast::() { + seen.insert(s.as_wtf8().to_string()); + } + result.push(item.clone()); + } + + for name in bound_only { + if seen.insert(name.to_owned()) { + result.push(vm.ctx.new_str(name).into()); + } + } + + Ok(PyList::from(result)) + } } impl PyPayload for PyBoundMethod {