Skip to content

Commit 08e66b5

Browse files
committed
Create workaround for properties on None
1 parent a5050eb commit 08e66b5

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

vm/src/obj/objnone.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::function::PyFuncArgs;
2+
use crate::obj::objproperty::PyPropertyRef;
3+
use crate::obj::objstr::PyStringRef;
24
use crate::pyobject::{
3-
IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol,
5+
AttributeProtocol, IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue,
6+
TryFromObject, TypeProtocol,
47
};
58
use crate::vm::VirtualMachine;
69

@@ -39,6 +42,57 @@ impl PyNoneRef {
3942
fn bool(self, _vm: &mut VirtualMachine) -> PyResult<bool> {
4043
Ok(false)
4144
}
45+
46+
fn get_attribute(self, name: PyStringRef, vm: &mut VirtualMachine) -> PyResult {
47+
trace!("None.__getattribute__({:?}, {:?})", self, name);
48+
let cls = self.typ().into_object();
49+
50+
// Properties use a comparision with None to determine if they are either invoked by am
51+
// instance binding or a class binding. But if the object itself is None then this detection
52+
// won't work. Instead we call a special function on property that bypasses this check, as
53+
// we are invoking it as a instance binding.
54+
//
55+
// In CPython they instead call the slot tp_descr_get with NULL to indicates it's an
56+
// instance binding.
57+
// https://github.com/python/cpython/blob/master/Objects/typeobject.c#L3281
58+
fn call_descriptor(
59+
descriptor: PyObjectRef,
60+
get_func: PyObjectRef,
61+
obj: PyObjectRef,
62+
cls: PyObjectRef,
63+
vm: &mut VirtualMachine,
64+
) -> PyResult {
65+
if let Ok(property) = PyPropertyRef::try_from_object(vm, descriptor.clone()) {
66+
property.instance_binding_get(obj, vm)
67+
} else {
68+
vm.invoke(get_func, vec![descriptor, obj, cls])
69+
}
70+
}
71+
72+
if let Some(attr) = cls.get_attr(&name.value) {
73+
let attr_class = attr.typ();
74+
if attr_class.has_attr("__set__") {
75+
if let Some(get_func) = attr_class.get_attr("__get__") {
76+
return call_descriptor(attr, get_func, self.into_object(), cls.clone(), vm);
77+
}
78+
}
79+
}
80+
81+
if let Some(obj_attr) = self.as_object().get_attr(&name.value) {
82+
Ok(obj_attr)
83+
} else if let Some(attr) = cls.get_attr(&name.value) {
84+
let attr_class = attr.typ();
85+
if let Some(get_func) = attr_class.get_attr("__get__") {
86+
call_descriptor(attr, get_func, self.into_object(), cls.clone(), vm)
87+
} else {
88+
Ok(attr)
89+
}
90+
} else if let Some(getter) = cls.get_attr("__getattr__") {
91+
vm.invoke(getter, vec![self.into_object(), name.into_object()])
92+
} else {
93+
Err(vm.new_attribute_error(format!("{} has no attribute '{}'", self.as_object(), name)))
94+
}
95+
}
4296
}
4397

4498
fn none_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
@@ -55,5 +109,6 @@ pub fn init(context: &PyContext) {
55109
"__new__" => context.new_rustfunc(none_new),
56110
"__repr__" => context.new_rustfunc(PyNoneRef::repr),
57111
"__bool__" => context.new_rustfunc(PyNoneRef::bool),
112+
"__getattribute__" => context.new_rustfunc(PyNoneRef::get_attribute)
58113
});
59114
}

vm/src/obj/objproperty.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ impl PyPropertyRef {
6868

6969
// Descriptor methods
7070

71+
// specialised version that doesn't check for None
72+
pub(crate) fn instance_binding_get(
73+
self,
74+
obj: PyObjectRef,
75+
vm: &mut VirtualMachine,
76+
) -> PyResult {
77+
if let Some(getter) = self.getter.as_ref() {
78+
vm.invoke(getter.clone(), obj)
79+
} else {
80+
Err(vm.new_attribute_error("unreadable attribute".to_string()))
81+
}
82+
}
83+
7184
fn get(self, obj: PyObjectRef, _owner: PyClassRef, vm: &mut VirtualMachine) -> PyResult {
7285
if let Some(getter) = self.getter.as_ref() {
7386
if obj.is(&vm.ctx.none) {

0 commit comments

Comments
 (0)