Skip to content

Commit 56ea69b

Browse files
committed
Fix function attribute tests: __defaults__ del, __code__ free vars check, bound method dir()
- Support `del func.__defaults__` and `del func.__kwdefaults__` by changing setter signatures to `PySetterValue<Option<...>>` so Delete is treated as setting the value to None (matching CPython behaviour) - Validate free-variable count when assigning to `func.__code__`: raise ValueError when the new code object's freevars count doesn't match the existing closure size - Add `__dir__` to `PyBoundMethod` that delegates to the underlying function's dir(), so attributes set on the function show up in `dir(bound_method)` - Remove `@unittest.expectedFailure` from the four tests that now pass: test_blank_func_defaults, test_func_default_args, test___code__, test_dir_includes_correct_attrs
1 parent e79df4a commit 56ea69b

2 files changed

Lines changed: 29 additions & 11 deletions

File tree

Lib/test/test_funcattrs.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ class FunctionPropertiesTest(FuncAttrsTest):
5050
def test_module(self):
5151
self.assertEqual(self.b.__module__, __name__)
5252

53-
@unittest.expectedFailure # TODO: RUSTPYTHON
5453
def test_dir_includes_correct_attrs(self):
5554
self.b.known_attr = 7
5655
self.assertIn('known_attr', dir(self.b),
@@ -238,7 +237,6 @@ def not_generic(): pass
238237
func.__type_params__ = (T,)
239238
self.assertEqual(func.__type_params__, (T,))
240239

241-
@unittest.expectedFailure # TODO: RUSTPYTHON
242240
def test___code__(self):
243241
num_one, num_two = 7, 8
244242
def a(): pass
@@ -269,13 +267,11 @@ def e(): return num_one, num_two
269267
self.fail("__code__ with different numbers of free vars should "
270268
"not be possible")
271269

272-
@unittest.expectedFailure # TODO: RUSTPYTHON
273270
def test_blank_func_defaults(self):
274271
self.assertEqual(self.b.__defaults__, None)
275272
del self.b.__defaults__
276273
self.assertEqual(self.b.__defaults__, None)
277274

278-
@unittest.expectedFailure # TODO: RUSTPYTHON
279275
def test_func_default_args(self):
280276
def first_func(a, b):
281277
return a+b

crates/vm/src/builtins/function.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
mod jit;
33

44
use super::{
5-
PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyModule, PyStr, PyStrRef, PyTuple,
6-
PyTupleRef, PyType, object,
5+
PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyList, PyModule, PyStr, PyStrRef,
6+
PyTuple, PyTupleRef, PyType, object,
77
};
88
use crate::common::hash::PyHash;
99
use crate::common::lock::PyMutex;
@@ -788,7 +788,17 @@ impl PyFunction {
788788
}
789789

790790
#[pygetset(setter)]
791-
fn set___code__(&self, code: PyRef<PyCode>, vm: &VirtualMachine) {
791+
fn set___code__(&self, code: PyRef<PyCode>, vm: &VirtualMachine) -> PyResult<()> {
792+
let nfree = code.freevars.len();
793+
let nclosure = self.closure.as_ref().map_or(0, |c| c.len());
794+
if nclosure != nfree {
795+
return Err(vm.new_value_error(format!(
796+
"{}() requires a code object with {} free vars, not {}",
797+
self.qualname.lock(),
798+
nclosure,
799+
nfree,
800+
)));
801+
}
792802
#[cfg(feature = "jit")]
793803
let mut jit_guard = self.jitted_code.lock();
794804
self.code.swap_to_temporary_refs(code, vm);
@@ -797,15 +807,19 @@ impl PyFunction {
797807
*jit_guard = None;
798808
}
799809
self.func_version.store(0, Relaxed);
810+
Ok(())
800811
}
801812

802813
#[pygetset]
803814
fn __defaults__(&self) -> Option<PyTupleRef> {
804815
self.defaults_and_kwdefaults.lock().0.clone()
805816
}
806817
#[pygetset(setter)]
807-
fn set___defaults__(&self, defaults: Option<PyTupleRef>) {
808-
self.defaults_and_kwdefaults.lock().0 = defaults;
818+
fn set___defaults__(&self, defaults: PySetterValue<Option<PyTupleRef>>) {
819+
self.defaults_and_kwdefaults.lock().0 = match defaults {
820+
PySetterValue::Assign(d) => d,
821+
PySetterValue::Delete => None,
822+
};
809823
self.func_version.store(0, Relaxed);
810824
}
811825

@@ -814,8 +828,11 @@ impl PyFunction {
814828
self.defaults_and_kwdefaults.lock().1.clone()
815829
}
816830
#[pygetset(setter)]
817-
fn set___kwdefaults__(&self, kwdefaults: Option<PyDictRef>) {
818-
self.defaults_and_kwdefaults.lock().1 = kwdefaults;
831+
fn set___kwdefaults__(&self, kwdefaults: PySetterValue<Option<PyDictRef>>) {
832+
self.defaults_and_kwdefaults.lock().1 = match kwdefaults {
833+
PySetterValue::Assign(d) => d,
834+
PySetterValue::Delete => None,
835+
};
819836
self.func_version.store(0, Relaxed);
820837
}
821838

@@ -1308,6 +1325,11 @@ impl PyBoundMethod {
13081325
fn __module__(&self, vm: &VirtualMachine) -> Option<PyObjectRef> {
13091326
self.function.get_attr("__module__", vm).ok()
13101327
}
1328+
1329+
#[pymethod]
1330+
fn __dir__(&self, vm: &VirtualMachine) -> PyResult<PyList> {
1331+
vm.dir(Some(self.function.clone()))
1332+
}
13111333
}
13121334

13131335
impl PyPayload for PyBoundMethod {

0 commit comments

Comments
 (0)