Skip to content

Commit 73ae7a6

Browse files
authored
Adds __qualname__ to PyBuiltinMethod
1 parent b57c36e commit 73ae7a6

File tree

9 files changed

+111
-28
lines changed

9 files changed

+111
-28
lines changed

derive/src/pyclass.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,18 +317,25 @@ where
317317
let item_meta = MethodItemMeta::from_attr(ident.clone(), &item_attr)?;
318318

319319
let py_name = item_meta.method_name()?;
320-
let build_func = Ident::new(&format!("build_{}", &self.method_type), args.item.span());
321320
let tokens = {
322321
let doc = args.attrs.doc().map_or_else(
323322
TokenStream::new,
324323
|doc| quote!(.with_doc(#doc.to_owned(), ctx)),
325324
);
325+
let build_func = match self.method_type.as_str() {
326+
"method" => quote!(.build_method(ctx, class.clone())),
327+
"classmethod" => quote!(.build_classmethod(ctx, class.clone())),
328+
other => unreachable!(
329+
"Only 'method' and 'classmethod' are supported, got {}",
330+
other
331+
),
332+
};
326333
quote! {
327334
class.set_str_attr(
328335
#py_name,
329336
ctx.make_funcdef(#py_name, Self::#ident)
330337
#doc
331-
.#build_func(ctx),
338+
#build_func
332339
);
333340
}
334341
};

extra_tests/snippets/builtin_type.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,60 @@ class D:
4444
assert iter.__class__.__qualname__ == 'builtin_function_or_method'
4545
assert type(iter).__module__ == 'builtins'
4646
assert type(iter).__qualname__ == 'builtin_function_or_method'
47+
48+
49+
# Regression to
50+
# https://github.com/RustPython/RustPython/issues/2767
51+
52+
# Marked as `#[pymethod]`:
53+
assert str.replace.__qualname__ == 'str.replace'
54+
assert str().replace.__qualname__ == 'str.replace'
55+
assert int.to_bytes.__qualname__ == 'int.to_bytes'
56+
assert int().to_bytes.__qualname__ == 'int.to_bytes'
57+
58+
# Marked as `#[pyclassmethod]`:
59+
assert dict.fromkeys.__qualname__ == 'dict.fromkeys'
60+
assert object.__init_subclass__.__qualname__ == 'object.__init_subclass__'
61+
62+
# Dynamic with `#[extend_class]`:
63+
assert bytearray.maketrans.__qualname__ == 'bytearray.maketrans'
64+
65+
66+
# Third-party:
67+
class MyTypeWithMethod:
68+
def method(self):
69+
pass
70+
71+
@classmethod
72+
def clsmethod(cls):
73+
pass
74+
75+
@staticmethod
76+
def stmethod():
77+
pass
78+
79+
class N:
80+
def m(self):
81+
pass
82+
83+
@classmethod
84+
def c(cls):
85+
pass
86+
87+
@staticmethod
88+
def s():
89+
pass
90+
91+
assert MyTypeWithMethod.method.__qualname__ == 'MyTypeWithMethod.method'
92+
assert MyTypeWithMethod().method.__qualname__ == 'MyTypeWithMethod.method'
93+
assert MyTypeWithMethod.clsmethod.__qualname__ == 'MyTypeWithMethod.clsmethod'
94+
assert MyTypeWithMethod().clsmethod.__qualname__ == 'MyTypeWithMethod.clsmethod'
95+
assert MyTypeWithMethod.stmethod.__qualname__ == 'MyTypeWithMethod.stmethod'
96+
assert MyTypeWithMethod().stmethod.__qualname__ == 'MyTypeWithMethod.stmethod'
97+
98+
assert MyTypeWithMethod.N.m.__qualname__ == 'MyTypeWithMethod.N.m'
99+
assert MyTypeWithMethod().N.m.__qualname__ == 'MyTypeWithMethod.N.m'
100+
assert MyTypeWithMethod.N.c.__qualname__ == 'MyTypeWithMethod.N.c'
101+
assert MyTypeWithMethod().N.c.__qualname__ == 'MyTypeWithMethod.N.c'
102+
assert MyTypeWithMethod.N.s.__qualname__ == 'MyTypeWithMethod.N.s'
103+
assert MyTypeWithMethod().N.s.__qualname__ == 'MyTypeWithMethod.N.s'

vm/src/builtins/builtinfunc.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ impl PyNativeFuncDef {
3838
pub fn build_function(self, ctx: &PyContext) -> PyObjectRef {
3939
self.into_function().build(ctx)
4040
}
41-
pub fn build_method(self, ctx: &PyContext) -> PyObjectRef {
41+
pub fn build_method(self, ctx: &PyContext, class: PyTypeRef) -> PyObjectRef {
4242
PyObject::new(
43-
PyBuiltinMethod::from(self),
43+
PyBuiltinMethod { value: self, class },
4444
ctx.types.method_descriptor_type.clone(),
4545
None,
4646
)
4747
}
48-
pub fn build_classmethod(self, ctx: &PyContext) -> PyObjectRef {
48+
pub fn build_classmethod(self, ctx: &PyContext, class: PyTypeRef) -> PyObjectRef {
4949
// TODO: classmethod_descriptor
5050
PyObject::new(
51-
PyClassMethod::from(self.build_method(ctx)),
51+
PyClassMethod::from(self.build_method(ctx, class)),
5252
ctx.types.classmethod_type.clone(),
5353
None,
5454
)
@@ -140,9 +140,12 @@ impl PyBuiltinFunction {
140140
}
141141
}
142142

143+
// PyCMethodObject in
144+
// https://github.com/python/cpython/blob/main/Include/cpython/methodobject.h
143145
#[pyclass(module = false, name = "method_descriptor")]
144146
pub struct PyBuiltinMethod {
145147
value: PyNativeFuncDef,
148+
class: PyTypeRef,
146149
}
147150

148151
impl PyValue for PyBuiltinMethod {
@@ -157,12 +160,6 @@ impl fmt::Debug for PyBuiltinMethod {
157160
}
158161
}
159162

160-
impl From<PyNativeFuncDef> for PyBuiltinMethod {
161-
fn from(value: PyNativeFuncDef) -> Self {
162-
Self { value }
163-
}
164-
}
165-
166163
impl SlotDescriptor for PyBuiltinMethod {
167164
fn descr_get(
168165
zelf: PyObjectRef,
@@ -195,6 +192,11 @@ impl PyBuiltinMethod {
195192
self.value.name.clone()
196193
}
197194
#[pyproperty(magic)]
195+
fn qualname(&self, vm: &VirtualMachine) -> PyObjectRef {
196+
vm.ctx
197+
.new_str(format!("{}.{}", self.class.name, &self.value.name))
198+
}
199+
#[pyproperty(magic)]
198200
fn doc(&self) -> Option<PyStrRef> {
199201
self.value.doc.clone()
200202
}

vm/src/builtins/bytearray.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub(crate) fn init(context: &PyContext) {
9393
PyByteArray::extend_class(context, &context.types.bytearray_type);
9494
let bytearray_type = &context.types.bytearray_type;
9595
extend_class!(context, bytearray_type, {
96-
"maketrans" => context.new_method("maketrans", PyBytesInner::maketrans),
96+
"maketrans" => context.new_method("maketrans", PyBytesInner::maketrans, bytearray_type.clone()),
9797
});
9898

9999
PyByteArrayIterator::extend_class(context, &context.types.bytearray_iterator_type);

vm/src/builtins/bytes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub(crate) fn init(context: &PyContext) {
9393
PyBytes::extend_class(context, &context.types.bytes_type);
9494
let bytes_type = &context.types.bytes_type;
9595
extend_class!(context, bytes_type, {
96-
"maketrans" => context.new_method("maketrans", PyBytesInner::maketrans),
96+
"maketrans" => context.new_method("maketrans", PyBytesInner::maketrans, bytes_type.clone()),
9797
});
9898
PyBytesIterator::extend_class(context, &context.types.bytes_iterator_type);
9999
}

vm/src/exceptions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ impl ExceptionZoo {
666666
});
667667

668668
extend_class!(ctx, &excs.import_error, {
669-
"__init__" => ctx.new_method("__init__", import_error_init),
669+
"__init__" => ctx.new_method("__init__", import_error_init, excs.import_error.clone()),
670670
"msg" => ctx.new_readonly_getset("msg", make_arg_getter(0)),
671671
});
672672

@@ -675,7 +675,7 @@ impl ExceptionZoo {
675675
});
676676

677677
extend_class!(ctx, &excs.key_error, {
678-
"__str__" => ctx.new_method("__str__", key_error_str),
678+
"__str__" => ctx.new_method("__str__", key_error_str, excs.key_error.clone()),
679679
});
680680

681681
let errno_getter = ctx.new_readonly_getset("errno", |exc: PyBaseExceptionRef| {

vm/src/pyobject.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,25 +296,40 @@ impl PyContext {
296296
self.make_funcdef(name, f).build_function(self)
297297
}
298298

299-
pub fn new_method<F, FKind>(&self, name: impl Into<String>, f: F) -> PyObjectRef
299+
pub fn new_method<F, FKind>(
300+
&self,
301+
name: impl Into<String>,
302+
f: F,
303+
class: PyTypeRef,
304+
) -> PyObjectRef
300305
where
301306
F: IntoPyNativeFunc<FKind>,
302307
{
303-
self.make_funcdef(name, f).build_method(self)
308+
self.make_funcdef(name, f).build_method(self, class)
304309
}
305310

306-
pub fn new_classmethod<F, FKind>(&self, name: impl Into<String>, f: F) -> PyObjectRef
311+
pub fn new_classmethod<F, FKind>(
312+
&self,
313+
name: impl Into<String>,
314+
f: F,
315+
class: PyTypeRef,
316+
) -> PyObjectRef
307317
where
308318
F: IntoPyNativeFunc<FKind>,
309319
{
310-
self.make_funcdef(name, f).build_classmethod(self)
311-
}
312-
pub fn new_staticmethod<F, FKind>(&self, name: impl Into<String>, f: F) -> PyObjectRef
320+
self.make_funcdef(name, f).build_classmethod(self, class)
321+
}
322+
pub fn new_staticmethod<F, FKind>(
323+
&self,
324+
name: impl Into<String>,
325+
f: F,
326+
class: PyTypeRef,
327+
) -> PyObjectRef
313328
where
314329
F: IntoPyNativeFunc<FKind>,
315330
{
316331
PyObject::new(
317-
PyStaticMethod::from(self.new_method(name, f)),
332+
PyStaticMethod::from(self.new_method(name, f, class)),
318333
self.types.staticmethod_type.clone(),
319334
None,
320335
)

wasm/lib/src/convert.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
216216
}
217217
} else if js_val.is_function() {
218218
let func = js_sys::Function::from(js_val);
219-
vm.ctx.new_method(
219+
vm.ctx.new_function(
220220
func.name(),
221221
move |args: FuncArgs, vm: &VirtualMachine| -> PyResult {
222222
let this = Object::new();

wasm/lib/src/wasm_builtins.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ pub fn make_stdout_object(
2424
write_f: impl Fn(&str, &VirtualMachine) -> PyResult<()> + 'static,
2525
) -> PyObjectRef {
2626
let ctx = &vm.ctx;
27+
// there's not really any point to storing this class so that there's a consistent type object,
28+
// we just want a half-decent repr() output
29+
let cls = py_class!(ctx, "JSStdout", &vm.ctx.types.object_type, {});
2730
let write_method = ctx.new_method(
2831
"write",
2932
move |_self: PyObjectRef, data: PyStrRef, vm: &VirtualMachine| -> PyResult<()> {
3033
write_f(data.as_str(), vm)
3134
},
35+
cls.clone(),
3236
);
33-
let flush_method = ctx.new_method("flush", |_self: PyObjectRef| {});
34-
// there's not really any point to storing this class so that there's a consistent type object,
35-
// we just want a half-decent repr() output
36-
let cls = py_class!(ctx, "JSStdout", &vm.ctx.types.object_type, {
37+
let flush_method = ctx.new_method("flush", |_self: PyObjectRef| {}, cls.clone());
38+
extend_class!(ctx, cls, {
3739
"write" => write_method,
3840
"flush" => flush_method,
3941
});

0 commit comments

Comments
 (0)