Skip to content
21 changes: 6 additions & 15 deletions Lib/test/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, '<staticmethod({!r})>')

# 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, '<classmethod({!r})>')

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_reprlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,6 @@ def inner():
r'int object at 0x[0-9A-Fa-f]+>')
self.assertRegex(r(x), r'<cell at 0x.*\.\.\..*>')

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_descriptors(self):
eq = self.assertEqual
# method descriptors
Expand Down
2 changes: 1 addition & 1 deletion parser/src/python.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ ClassDef: ast::Stmt = {

// Decorators:
Decorator: ast::Expr = {
<location:@L>"@" <p:Test> "\n" => {
<location:@L>"@" <p:NamedExpressionTest> "\n" => {
p
},
};
Expand Down
18 changes: 9 additions & 9 deletions parser/src/python.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 64 additions & 8 deletions vm/src/builtins/classmethod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{PyBoundMethod, PyType, PyTypeRef};
use super::{PyBoundMethod, PyStr, PyType, PyTypeRef};
use crate::{
class::PyClassImpl,
common::lock::PyMutex,
Expand Down Expand Up @@ -53,22 +53,33 @@ impl GetDescriptor for PyClassMethod {
cls: Option<PyObjectRef>,
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<PyObjectRef> = 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)),
}
}
}

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)
}
}

Expand Down Expand Up @@ -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<String> {
let callable = self.callable.lock().repr(vm).unwrap();
let class = Self::class(vm);

match (
class
.qualname(vm)
.downcast_ref::<PyStr>()
.map(|n| n.as_str()),
class.module(vm).downcast_ref::<PyStr>().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__") {
Expand Down
63 changes: 60 additions & 3 deletions vm/src/builtins/staticmethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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<String> {
let callable = self.callable.repr(vm).unwrap();
let class = Self::class(vm);

match (
class
.qualname(vm)
.downcast_ref::<PyStr>()
.map(|n| n.as_str()),
class.module(vm).downcast_ref::<PyStr>().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__") {
Expand Down