Skip to content

Commit 4ccdc19

Browse files
authored
Implement __slots__ with member_descriptor (RustPython#4106)
1 parent 05d73dd commit 4ccdc19

File tree

11 files changed

+390
-24
lines changed

11 files changed

+390
-24
lines changed

Lib/test/test_rlcompleter.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ def bar(self):
9393
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
9494
self.assertEqual(f.calls, 1)
9595

96-
# TODO: RUSTPYTHON
97-
@unittest.expectedFailure
9896
def test_uncreated_attr(self):
9997
# Attributes like properties and slots should be completed even when
10098
# they haven't been created on an instance

derive/src/pyclass.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ enum AttrName {
2424
Slot,
2525
Attr,
2626
ExtendClass,
27+
Member,
2728
}
2829

2930
impl std::fmt::Display for AttrName {
@@ -36,6 +37,7 @@ impl std::fmt::Display for AttrName {
3637
Self::Slot => "pyslot",
3738
Self::Attr => "pyattr",
3839
Self::ExtendClass => "extend_class",
40+
Self::Member => "pymember",
3941
};
4042
s.fmt(f)
4143
}
@@ -52,6 +54,7 @@ impl FromStr for AttrName {
5254
"pyslot" => Self::Slot,
5355
"pyattr" => Self::Attr,
5456
"extend_class" => Self::ExtendClass,
57+
"pymember" => Self::Member,
5558
s => {
5659
return Err(s.to_owned());
5760
}
@@ -423,6 +426,11 @@ struct ExtendClassItem {
423426
inner: ContentItemInner<AttrName>,
424427
}
425428

429+
/// #[pymember]
430+
struct MemberItem {
431+
inner: ContentItemInner<AttrName>,
432+
}
433+
426434
impl ContentItem for MethodItem {
427435
type AttrName = AttrName;
428436
fn inner(&self) -> &ContentItemInner<AttrName> {
@@ -453,6 +461,12 @@ impl ContentItem for ExtendClassItem {
453461
&self.inner
454462
}
455463
}
464+
impl ContentItem for MemberItem {
465+
type AttrName = AttrName;
466+
fn inner(&self) -> &ContentItemInner<AttrName> {
467+
&self.inner
468+
}
469+
}
456470

457471
struct ImplItemArgs<'a, Item: ItemLike> {
458472
item: &'a Item,
@@ -661,6 +675,42 @@ where
661675
}
662676
}
663677

678+
impl<Item> ImplItem<Item> for MemberItem
679+
where
680+
Item: ItemLike + ToTokens + GetIdent,
681+
{
682+
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()> {
683+
let func = args
684+
.item
685+
.function_or_method()
686+
.map_err(|_| self.new_syn_error(args.item.span(), "can only be on a method"))?;
687+
let ident = &func.sig().ident;
688+
689+
let item_attr = args.attrs.remove(self.index());
690+
let item_meta = MemberItemMeta::from_attr(ident.clone(), &item_attr)?;
691+
692+
let py_name = item_meta.member_name()?;
693+
let tokens = {
694+
quote_spanned! { ident.span() =>
695+
class.set_str_attr(
696+
#py_name,
697+
ctx.new_member(#py_name, Self::#ident, class),
698+
ctx,
699+
);
700+
}
701+
};
702+
703+
args.context.impl_extend_items.add_item(
704+
ident.clone(),
705+
vec![py_name],
706+
args.cfgs.to_vec(),
707+
tokens,
708+
5,
709+
)?;
710+
Ok(())
711+
}
712+
}
713+
664714
#[derive(Default)]
665715
#[allow(clippy::type_complexity)]
666716
struct GetSetNursery {
@@ -930,6 +980,28 @@ impl SlotItemMeta {
930980
}
931981
}
932982

983+
struct MemberItemMeta(ItemMetaInner);
984+
985+
impl ItemMeta for MemberItemMeta {
986+
const ALLOWED_NAMES: &'static [&'static str] = &["magic"];
987+
988+
fn from_inner(inner: ItemMetaInner) -> Self {
989+
Self(inner)
990+
}
991+
fn inner(&self) -> &ItemMetaInner {
992+
&self.0
993+
}
994+
}
995+
996+
impl MemberItemMeta {
997+
fn member_name(&self) -> Result<String> {
998+
let inner = self.inner();
999+
let magic = inner._bool("magic")?;
1000+
let name = inner.item_name();
1001+
Ok(if magic { format!("__{}__", name) } else { name })
1002+
}
1003+
}
1004+
9331005
struct ExtractedImplAttrs {
9341006
with_impl: TokenStream,
9351007
with_slots: TokenStream,
@@ -1049,6 +1121,9 @@ where
10491121
ExtendClass => Box::new(ExtendClassItem {
10501122
inner: ContentItemInner { index, attr_name },
10511123
}),
1124+
Member => Box::new(MemberItem {
1125+
inner: ContentItemInner { index, attr_name },
1126+
}),
10521127
})
10531128
}
10541129

derive/src/util.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub(crate) const ALL_ALLOWED_NAMES: &[&str] = &[
2323
"pyattr",
2424
"pyslot",
2525
"extend_class",
26+
"pymember",
2627
];
2728

2829
#[derive(Clone)]

vm/src/builtins/descriptor.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use super::{PyStr, PyType, PyTypeRef};
2+
use crate::{
3+
class::PyClassImpl,
4+
types::{Constructor, GetDescriptor, Unconstructible},
5+
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
6+
};
7+
use rustpython_common::lock::PyRwLock;
8+
9+
#[derive(Debug)]
10+
pub struct DescrObject {
11+
pub typ: PyTypeRef,
12+
pub name: String,
13+
pub qualname: PyRwLock<Option<String>>,
14+
}
15+
16+
#[derive(Debug)]
17+
pub enum MemberKind {
18+
ObjectEx = 16,
19+
}
20+
21+
pub enum MemberGetter {
22+
Getter(fn(&VirtualMachine, PyObjectRef) -> PyResult),
23+
Offset(usize),
24+
}
25+
26+
pub struct MemberDef {
27+
pub name: String,
28+
pub kind: MemberKind,
29+
pub getter: MemberGetter,
30+
pub doc: Option<String>,
31+
}
32+
33+
impl MemberDef {
34+
fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
35+
match self.getter {
36+
MemberGetter::Getter(getter) => (getter)(vm, obj),
37+
MemberGetter::Offset(offset) => get_slot_from_object(obj, offset, self, vm),
38+
}
39+
}
40+
}
41+
42+
impl std::fmt::Debug for MemberDef {
43+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44+
f.debug_struct("MemberDef")
45+
.field("name", &self.name)
46+
.field("kind", &self.kind)
47+
.field("doc", &self.doc)
48+
.finish()
49+
}
50+
}
51+
52+
#[pyclass(name = "member_descriptor", module = false)]
53+
#[derive(Debug)]
54+
pub struct MemberDescrObject {
55+
pub common: DescrObject,
56+
pub member: MemberDef,
57+
}
58+
59+
impl PyPayload for MemberDescrObject {
60+
fn class(vm: &VirtualMachine) -> &'static Py<PyType> {
61+
vm.ctx.types.member_descriptor_type
62+
}
63+
}
64+
65+
fn calculate_qualname(descr: &DescrObject, vm: &VirtualMachine) -> PyResult<Option<String>> {
66+
if let Some(qualname) = vm.get_attribute_opt(descr.typ.to_owned().into(), "__qualname__")? {
67+
let str = qualname.downcast::<PyStr>().map_err(|_| {
68+
vm.new_type_error(
69+
"<descriptor>.__objclass__.__qualname__ is not a unicode object".to_owned(),
70+
)
71+
})?;
72+
Ok(Some(format!("{}.{}", str, descr.name)))
73+
} else {
74+
Ok(None)
75+
}
76+
}
77+
78+
#[pyclass(with(GetDescriptor, Constructor), flags(BASETYPE))]
79+
impl MemberDescrObject {
80+
#[pymethod(magic)]
81+
fn repr(zelf: PyRef<Self>) -> String {
82+
format!(
83+
"<member '{}' of '{}' objects>",
84+
zelf.common.name,
85+
zelf.common.typ.name(),
86+
)
87+
}
88+
89+
#[pyproperty(magic)]
90+
fn doc(zelf: PyRef<Self>) -> Option<String> {
91+
zelf.member.doc.to_owned()
92+
}
93+
94+
#[pyproperty(magic)]
95+
fn qualname(&self, vm: &VirtualMachine) -> PyResult<Option<String>> {
96+
let qualname = self.common.qualname.read();
97+
Ok(if qualname.is_none() {
98+
drop(qualname);
99+
let calculated = calculate_qualname(&self.common, vm)?;
100+
*self.common.qualname.write() = calculated.to_owned();
101+
calculated
102+
} else {
103+
qualname.to_owned()
104+
})
105+
}
106+
}
107+
108+
// PyMember_GetOne
109+
fn get_slot_from_object(
110+
obj: PyObjectRef,
111+
offset: usize,
112+
member: &MemberDef,
113+
vm: &VirtualMachine,
114+
) -> PyResult {
115+
let slot = match member.kind {
116+
MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| {
117+
vm.new_attribute_error(format!(
118+
"'{}' object has no attribute '{}'",
119+
obj.class().name(),
120+
member.name
121+
))
122+
})?,
123+
};
124+
Ok(slot)
125+
}
126+
127+
impl Unconstructible for MemberDescrObject {}
128+
129+
impl GetDescriptor for MemberDescrObject {
130+
fn descr_get(
131+
zelf: PyObjectRef,
132+
obj: Option<PyObjectRef>,
133+
_cls: Option<PyObjectRef>,
134+
vm: &VirtualMachine,
135+
) -> PyResult {
136+
match obj {
137+
Some(x) => {
138+
let zelf = Self::_zelf(zelf, vm)?;
139+
zelf.member.get(x, vm)
140+
}
141+
None => Ok(zelf),
142+
}
143+
}
144+
}
145+
146+
pub fn init(context: &Context) {
147+
let member_descriptor_type = &context.types.member_descriptor_type;
148+
MemberDescrObject::extend_class(context, member_descriptor_type);
149+
}

vm/src/builtins/function.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use super::{
55
tuple::PyTupleTyped, PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef,
66
PyTupleRef, PyType, PyTypeRef,
77
};
8+
#[cfg(feature = "jit")]
9+
use crate::common::lock::OnceCell;
810
use crate::common::lock::PyMutex;
11+
use crate::convert::ToPyObject;
912
use crate::function::ArgMapping;
1013
use crate::{
1114
bytecode,
@@ -16,8 +19,6 @@ use crate::{
1619
types::{Callable, Comparable, Constructor, GetAttr, GetDescriptor, PyComparisonOp},
1720
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
1821
};
19-
#[cfg(feature = "jit")]
20-
use crate::{common::lock::OnceCell, convert::ToPyObject};
2122
use itertools::Itertools;
2223
#[cfg(feature = "jit")]
2324
use rustpython_jit::CompiledCode;
@@ -352,14 +353,21 @@ impl PyFunction {
352353
self.defaults_and_kwdefaults.lock().1 = kwdefaults
353354
}
354355

355-
#[pyproperty(magic)]
356-
fn globals(&self) -> PyDictRef {
357-
self.globals.clone()
356+
// {"__closure__", T_OBJECT, OFF(func_closure), READONLY},
357+
// {"__doc__", T_OBJECT, OFF(func_doc), 0},
358+
// {"__globals__", T_OBJECT, OFF(func_globals), READONLY},
359+
// {"__module__", T_OBJECT, OFF(func_module), 0},
360+
// {"__builtins__", T_OBJECT, OFF(func_builtins), READONLY},
361+
#[pymember(magic)]
362+
fn globals(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
363+
let zelf = Self::_zelf(zelf, vm)?;
364+
Ok(zelf.globals.clone().into())
358365
}
359366

360-
#[pyproperty(magic)]
361-
fn closure(&self) -> Option<PyTupleTyped<PyCellRef>> {
362-
self.closure.clone()
367+
#[pymember(magic)]
368+
fn closure(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
369+
let zelf = Self::_zelf(zelf, vm)?;
370+
Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.to_pyobject(vm))))
363371
}
364372

365373
#[pyproperty(magic)]

vm/src/builtins/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ pub use zip::PyZip;
8989
#[path = "union.rs"]
9090
pub(crate) mod union_;
9191
pub use union_::PyUnion;
92+
pub(crate) mod descriptor;
9293

9394
pub use float::try_to_bigint as try_f64_to_bigint;
9495
pub use int::try_to_float as try_bigint_to_f64;

0 commit comments

Comments
 (0)