diff --git a/Lib/test/test_decorators.py b/Lib/test/test_decorators.py index abd0f81f4f..57a741ffd2 100644 --- a/Lib/test/test_decorators.py +++ b/Lib/test/test_decorators.py @@ -94,16 +94,12 @@ def func(x): self.assertEqual(repr(wrapper), format_str.format(func)) return wrapper - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_staticmethod(self): wrapper = self.check_wrapper_attrs(staticmethod, '') # bpo-43682: Static methods are callable since Python 3.10 self.assertEqual(wrapper(1), 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_classmethod(self): wrapper = self.check_wrapper_attrs(classmethod, '') @@ -203,13 +199,12 @@ def unimp(func): code = compile(codestr, "test", "exec") self.assertRaises(exc, eval, code, context) - # TODO: RUSTPYTHON; := operator is invalid syntax - # def test_expressions(self): - # for expr in ( - # "(x,)", "(x, y)", "x := y", "(x := y)", "x @y", "(x @ y)", "x[0]", - # "w[x].y.z", "w + x - (y + z)", "x(y)()(z)", "[w, x, y][z]", "x.y", - # ): - # compile(f"@{expr}\ndef f(): pass", "test", "exec") + def test_expressions(self): + for expr in ( + "(x,)", "(x, y)", "x := y", "(x := y)", "x @y", "(x @ y)", "x[0]", + "w[x].y.z", "w + x - (y + z)", "x(y)()(z)", "[w, x, y][z]", "x.y", + ): + compile(f"@{expr}\ndef f(): pass", "test", "exec") def test_double(self): class C(object): @@ -297,8 +292,6 @@ def bar(): return 42 self.assertEqual(bar(), 42) self.assertEqual(actions, expected_actions) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_wrapped_descriptor_inside_classmethod(self): class BoundWrapper: def __init__(self, wrapped): @@ -337,8 +330,6 @@ def outer(cls): self.assertEqual(Class().inner(), 'spam') self.assertEqual(Class().outer(), 'eggs') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_wrapped_classmethod_inside_classmethod(self): class MyClassMethod1: def __init__(self, func): diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index bc0d4d1f7a..611fb9d1e4 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -198,8 +198,6 @@ def inner(): r'int object at 0x[0-9A-Fa-f]+>') self.assertRegex(r(x), r'') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_descriptors(self): eq = self.assertEqual # method descriptors diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 0d2ca33164..af6532badc 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -634,7 +634,7 @@ ClassDef: ast::Stmt = { // Decorators: Decorator: ast::Expr = { - "@" "\n" => { + "@" "\n" => { p }, }; diff --git a/parser/src/python.rs b/parser/src/python.rs index d264222f2f..4957fa4653 100644 --- a/parser/src/python.rs +++ b/parser/src/python.rs @@ -1,5 +1,5 @@ -// auto-generated: "lalrpop 0.19.7" -// sha3: 39aa91107495c6c1fc36b3a29432d9734e32e59d677b96962503bc0f6716 +// auto-generated: "lalrpop 0.19.8" +// sha3: 86d5818e500762c6e3eb3bc4602ed02373d87800aad460d22ece9749fd8fe424 use crate::ast; use crate::fstring::parse_located_fstring; use crate::function::{ArgumentList, parse_args, parse_params}; @@ -968,7 +968,7 @@ mod __parse__Top { // State 416 0, 0, 0, 0, 0, 0, 0, -852, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 417 - 0, -218, -218, 0, -218, 0, -218, -218, -218, -218, 0, 0, -218, 0, -218, -218, 0, 0, -218, 0, -218, -218, 0, 0, -218, 103, 0, -218, -218, 0, -218, 0, -218, -218, -218, -218, 0, -218, 0, 0, 0, 0, -218, -218, -218, 0, -218, 0, 0, -218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -218, 0, 0, 0, -218, 0, -218, -218, 0, 0, -218, -218, 0, 0, 0, 0, 0, 0, 0, 0, -218, 0, -218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -218, -218, -218, 0, -218, 0, -218, -218, -218, -218, 0, 0, -218, 0, -218, -218, 0, 0, -218, 0, -218, -218, 0, 0, -218, 103, 0, -218, -218, 0, -218, 0, -218, -218, -218, -218, 0, -218, 0, 0, 0, 0, -218, -218, -218, 0, -218, 0, 0, -218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -218, 0, 0, 0, -218, 0, -218, -218, 0, 0, -218, -218, 0, 0, 0, 0, 0, 0, 0, 0, -218, 0, -218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 418 0, 0, 0, 0, 0, 0, 0, -850, 0, 0, 0, 0, 0, 0, -850, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -850, 0, 0, 0, 0, 0, -850, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -850, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -850, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 419 @@ -984,7 +984,7 @@ mod __parse__Top { // State 424 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 499, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 425 - 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -432, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -432, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 426 -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, 0, -220, 0, -220, -220, -220, -220, -220, 0, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, -220, 0, 0, 0, -220, -220, -220, -220, -220, -220, 0, -220, 0, 0, 0, 0, 0, 0, 0, -220, 0, 0, -220, -220, -220, 0, -220, 0, -220, -220, 0, 0, -220, -220, 0, 0, 0, 0, 0, 0, 0, 0, -220, -220, -220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 427 @@ -1234,7 +1234,7 @@ mod __parse__Top { // State 549 -110, 0, 0, -110, 0, -110, 0, -110, 0, 0, -110, -110, 0, -110, -110, 0, -110, 0, 0, 0, 0, 0, -110, -110, -110, 0, -110, 0, 0, -110, 0, -110, 0, 0, 0, 0, -110, 0, -110, 0, 0, 0, 0, 0, 0, -110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 550 - 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -431, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 551 -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, 0, -225, 0, -225, -225, -225, -225, -225, 0, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, 0, 0, 0, -225, -225, -225, -225, -225, -225, 0, -225, 0, 0, 0, 0, 0, 0, 0, -225, 0, 0, -225, -225, -225, 0, -225, 0, -225, -225, 0, 0, -225, -225, 0, 0, 0, 0, 0, 0, 0, 0, -225, -225, -225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // State 552 @@ -3836,7 +3836,7 @@ mod __parse__Top { 169 => 202, 201 => 228, 230 => 261, - 13 | 15 | 19 | 32 | 37 | 105..=106 | 114 | 147..=148 | 155 | 203 | 231 => 417, + 13 | 15 | 19 | 24 | 32 | 37 | 105..=106 | 114 | 147..=148 | 155 | 203 | 231 => 417, 17 | 60..=61 | 107 | 111 | 149..=150 | 152..=153 | 183 | 186..=188 | 211..=216 | 242..=247 | 249 | 265..=266 | 268 | 270..=272 | 287..=292 | 304..=307 | 316 => 428, 26 => 457, 44 | 92 | 122 | 157 => 478, @@ -3944,6 +3944,7 @@ mod __parse__Top { 171 => 53, 172 => match state { 44 | 92 | 122 | 157 => 93, + 24 => 455, 32 => 468, 37 => 471, 203 => 673, @@ -4080,8 +4081,7 @@ mod __parse__Top { 97 => 143, 142 => 181, 1 | 36 | 39 | 54 | 71..=72 | 101 | 126 | 159 => 343, - 15 | 32 | 37 | 44 | 92 | 105..=106 | 114 | 122 | 147..=148 | 155 | 157 | 203 | 231 => 425, - 24 => 455, + 15 | 24 | 32 | 37 | 44 | 92 | 105..=106 | 114 | 122 | 147..=148 | 155 | 157 | 203 | 231 => 425, 25 => 456, 35 => 469, 38 | 77 | 138 | 178 => 472, @@ -16852,7 +16852,7 @@ mod __parse__Top { _: core::marker::PhantomData<()>, ) -> (usize, usize) { - // Decorator = "@", Test, "\n" => ActionFn(793); + // Decorator = "@", NamedExpressionTest, "\n" => ActionFn(793); assert!(__symbols.len() >= 3); let __sym2 = __pop_Variant0(__symbols); let __sym1 = __pop_Variant52(__symbols); diff --git a/vm/src/builtins/classmethod.rs b/vm/src/builtins/classmethod.rs index 882e412eb5..4bb4a2af9a 100644 --- a/vm/src/builtins/classmethod.rs +++ b/vm/src/builtins/classmethod.rs @@ -1,4 +1,4 @@ -use super::{PyBoundMethod, PyType, PyTypeRef}; +use super::{PyBoundMethod, PyStr, PyType, PyTypeRef}; use crate::{ class::PyClassImpl, common::lock::PyMutex, @@ -53,10 +53,13 @@ impl GetDescriptor for PyClassMethod { cls: Option, vm: &VirtualMachine, ) -> PyResult { - let (zelf, obj) = Self::_unwrap(zelf, obj, vm)?; - let cls = cls.unwrap_or_else(|| obj.class().clone().into()); - let callable = zelf.callable.lock().clone(); - Ok(PyBoundMethod::new_ref(cls, callable, &vm.ctx).into()) + let (zelf, _obj) = Self::_unwrap(zelf, obj, vm)?; + let cls = cls.unwrap_or_else(|| _obj.class().clone().into()); + let call_descr_get: PyResult = zelf.callable.lock().get_attr("__get__", vm); + match call_descr_get { + Err(_) => Ok(PyBoundMethod::new_ref(cls, zelf.callable.lock().clone(), &vm.ctx).into()), + Ok(call_descr_get) => vm.invoke(&call_descr_get, (cls.clone(), cls)), + } } } @@ -64,11 +67,19 @@ impl Constructor for PyClassMethod { type Args = PyObjectRef; fn py_new(cls: PyTypeRef, callable: Self::Args, vm: &VirtualMachine) -> PyResult { - PyClassMethod { + let doc = callable.get_attr("__doc__", vm); + + let result = PyClassMethod { callable: PyMutex::new(callable), } - .into_ref_with_type(vm, cls) - .map(Into::into) + .into_ref_with_type(vm, cls)?; + let obj = PyObjectRef::from(result); + + if let Ok(doc) = doc { + obj.set_attr("__doc__", doc, vm)?; + } + + Ok(obj) } } @@ -100,6 +111,51 @@ impl PyClassMethod { self.callable.lock().clone() } + #[pyproperty(magic)] + fn wrapped(&self) -> PyObjectRef { + self.callable.lock().clone() + } + + #[pyproperty(magic)] + fn module(&self, vm: &VirtualMachine) -> PyResult { + self.callable.lock().get_attr("__module__", vm) + } + + #[pyproperty(magic)] + fn qualname(&self, vm: &VirtualMachine) -> PyResult { + self.callable.lock().get_attr("__qualname__", vm) + } + + #[pyproperty(magic)] + fn name(&self, vm: &VirtualMachine) -> PyResult { + self.callable.lock().get_attr("__name__", vm) + } + + #[pyproperty(magic)] + fn annotations(&self, vm: &VirtualMachine) -> PyResult { + self.callable.lock().get_attr("__annotations__", vm) + } + + #[pymethod(magic)] + fn repr(&self, vm: &VirtualMachine) -> Option { + let callable = self.callable.lock().repr(vm).unwrap(); + let class = Self::class(vm); + + match ( + class + .qualname(vm) + .downcast_ref::() + .map(|n| n.as_str()), + class.module(vm).downcast_ref::().map(|m| m.as_str()), + ) { + (None, _) => None, + (Some(qualname), Some(module)) if module != "builtins" => { + Some(format!("<{}.{}({})>", module, qualname, callable)) + } + _ => Some(format!("<{}({})>", class.slot_name(), callable)), + } + } + #[pyproperty(magic)] fn isabstractmethod(&self, vm: &VirtualMachine) -> PyObjectRef { match vm.get_attribute_opt(self.callable.lock().clone(), "__isabstractmethod__") { diff --git a/vm/src/builtins/staticmethod.rs b/vm/src/builtins/staticmethod.rs index 88f8317d8f..5198bc04a8 100644 --- a/vm/src/builtins/staticmethod.rs +++ b/vm/src/builtins/staticmethod.rs @@ -41,9 +41,16 @@ impl Constructor for PyStaticMethod { type Args = PyObjectRef; fn py_new(cls: PyTypeRef, callable: Self::Args, vm: &VirtualMachine) -> PyResult { - PyStaticMethod { callable } - .into_ref_with_type(vm, cls) - .map(Into::into) + let doc = callable.get_attr("__doc__", vm); + + let result = PyStaticMethod { callable }.into_ref_with_type(vm, cls)?; + let obj = PyObjectRef::from(result); + + if let Ok(doc) = doc { + obj.set_attr("__doc__", doc, vm)?; + } + + Ok(obj) } } @@ -68,6 +75,56 @@ impl PyStaticMethod { #[pyimpl(with(Callable, GetDescriptor, Constructor), flags(BASETYPE, HAS_DICT))] impl PyStaticMethod { + #[pyproperty(magic)] + fn func(&self) -> PyObjectRef { + self.callable.clone() + } + + #[pyproperty(magic)] + fn wrapped(&self) -> PyObjectRef { + self.callable.clone() + } + + #[pyproperty(magic)] + fn module(&self, vm: &VirtualMachine) -> PyResult { + self.callable.get_attr("__module__", vm) + } + + #[pyproperty(magic)] + fn qualname(&self, vm: &VirtualMachine) -> PyResult { + self.callable.get_attr("__qualname__", vm) + } + + #[pyproperty(magic)] + fn name(&self, vm: &VirtualMachine) -> PyResult { + self.callable.get_attr("__name__", vm) + } + + #[pyproperty(magic)] + fn annotations(&self, vm: &VirtualMachine) -> PyResult { + self.callable.get_attr("__annotations__", vm) + } + + #[pymethod(magic)] + fn repr(&self, vm: &VirtualMachine) -> Option { + let callable = self.callable.repr(vm).unwrap(); + let class = Self::class(vm); + + match ( + class + .qualname(vm) + .downcast_ref::() + .map(|n| n.as_str()), + class.module(vm).downcast_ref::().map(|m| m.as_str()), + ) { + (None, _) => None, + (Some(qualname), Some(module)) if module != "builtins" => { + Some(format!("<{}.{}({})>", module, qualname, callable)) + } + _ => Some(format!("<{}({})>", class.slot_name(), callable)), + } + } + #[pyproperty(magic)] fn isabstractmethod(&self, vm: &VirtualMachine) -> PyObjectRef { match vm.get_attribute_opt(self.callable.clone(), "__isabstractmethod__") {