Implement general __get__/__set__ descriptors

property objects are no longer a special case and have been simplified
old-style native properties can probably all be removed as well, but, todo
This commit is contained in:
K. Lange 2021-03-10 20:24:12 +09:00
parent 96a403b34c
commit 6b3f8de63b
12 changed files with 186 additions and 105 deletions

View File

@ -7,6 +7,7 @@
static KrkClass * Helper;
static KrkClass * LicenseReader;
static KrkClass * property;
FUNC_SIG(list,__init__);
FUNC_SIG(list,sort);
@ -743,57 +744,70 @@ KRK_METHOD(LicenseReader,__call__,{
return krk_runtimeError(vm.exceptions->typeError, "unexpected error");
})
static KrkValue _property_init(int argc, KrkValue argv[], int hasKw) {
if (argc != 2) {
return krk_runtimeError(vm.exceptions->notImplementedError, "Additional arguments to @property() are not supported.");
#define IS_property(o) (krk_isInstanceOf(o,property))
#define AS_property(o) (AS_INSTANCE(o))
KRK_METHOD(property,__init__,{
METHOD_TAKES_AT_LEAST(1);
METHOD_TAKES_AT_MOST(2); /* TODO fdel */
krk_attachNamedValue(&self->fields, "fget", argv[1]);
/* Try to attach doc */
if (IS_NATIVE(argv[1]) && AS_NATIVE(argv[1])->doc) {
krk_attachNamedValue(&self->fields, "__doc__",
OBJECT_VAL(krk_copyString(AS_NATIVE(argv[1])->doc, strlen(AS_NATIVE(argv[1])->doc))));
} else if (IS_CLOSURE(argv[1])) {
krk_attachNamedValue(&self->fields, "__doc__",
OBJECT_VAL(AS_CLOSURE(argv[1])->function->docstring));
}
return OBJECT_VAL(krk_newProperty(argv[1]));
}
static KrkValue _property_repr(int argc, KrkValue argv[], int hasKw) {
if (argc != 1 || !IS_PROPERTY(argv[0])) return krk_runtimeError(vm.exceptions->typeError, "?");
struct StringBuilder sb = {0};
pushStringBuilderStr(&sb, "property(", 9);
KrkValue method = AS_PROPERTY(argv[0])->method;
if (IS_NATIVE(method)) {
pushStringBuilderStr(&sb, (char*)AS_NATIVE(method)->name, strlen(AS_NATIVE(method)->name));
} else if (IS_CLOSURE(method)) {
pushStringBuilderStr(&sb, AS_CLOSURE(method)->function->name->chars, AS_CLOSURE(method)->function->name->length);
/* Try to attach name */
if (IS_NATIVE(argv[1]) && AS_NATIVE(argv[1])->name) {
krk_attachNamedValue(&self->fields, "__name__",
OBJECT_VAL(krk_copyString(AS_NATIVE(argv[1])->name, strlen(AS_NATIVE(argv[1])->name))));
} else if (IS_CLOSURE(argv[1])) {
krk_attachNamedValue(&self->fields, "__name__",
OBJECT_VAL(AS_CLOSURE(argv[1])->function->name));
}
pushStringBuilder(&sb,')');
return finishStringBuilder(&sb);
}
if (argc > 2)
krk_attachNamedValue(&self->fields, "fset", argv[2]);
static KrkValue _property_doc(int argc, KrkValue argv[], int hasKw) {
if (argc != 1 || !IS_PROPERTY(argv[0])) return krk_runtimeError(vm.exceptions->typeError, "?");
KrkValue method = AS_PROPERTY(argv[0])->method;
if (IS_NATIVE(method) && AS_NATIVE(method)->doc) {
return OBJECT_VAL(krk_copyString(AS_NATIVE(method)->doc, strlen(AS_NATIVE(method)->doc)));
} else if (IS_CLOSURE(method)) {
return OBJECT_VAL(AS_CLOSURE(method)->function->docstring);
}
return NONE_VAL();
}
return argv[0];
})
static KrkValue _property_name(int argc, KrkValue argv[], int hasKw) {
if (argc != 1 || !IS_PROPERTY(argv[0])) return krk_runtimeError(vm.exceptions->typeError, "?");
KrkValue method = AS_PROPERTY(argv[0])->method;
if (IS_NATIVE(method) && AS_NATIVE(method)->name) {
return OBJECT_VAL(krk_copyString(AS_NATIVE(method)->name, strlen(AS_NATIVE(method)->name)));
} else if (IS_CLOSURE(method)) {
return OBJECT_VAL(AS_CLOSURE(method)->function->name);
}
return NONE_VAL();
}
KRK_METHOD(property,setter,{
METHOD_TAKES_EXACTLY(1);
krk_attachNamedValue(&self->fields, "fset", argv[1]);
return argv[0]; /* Return the original property */
})
static KrkValue _property_method(int argc, KrkValue argv[], int hasKw) {
if (argc != 1 || !IS_PROPERTY(argv[0])) return krk_runtimeError(vm.exceptions->typeError, "?");
return AS_PROPERTY(argv[0])->method;
}
KRK_METHOD(property,__get__,{
METHOD_TAKES_EXACTLY(1); /* the owner */
KrkValue fget;
if (!krk_tableGet(&self->fields, OBJECT_VAL(S("fget")), &fget))
return krk_runtimeError(vm.exceptions->valueError, "property object is missing 'fget' attribute");
krk_push(argv[1]);
return krk_callSimple(fget, 1, 0);
})
KRK_METHOD(property,__set__,{
METHOD_TAKES_EXACTLY(2); /* the owner and the value */
krk_push(argv[1]);
krk_push(argv[2]);
KrkValue fset;
if (krk_tableGet(&self->fields, OBJECT_VAL(S("fset")), &fset))
return krk_callSimple(fset, 2, 0);
KrkValue fget;
if (krk_tableGet(&self->fields, OBJECT_VAL(S("fget")), &fget))
return krk_callSimple(fget, 2, 0);
return krk_runtimeError(vm.exceptions->attributeError, "attribute can not be set");
})
static KrkValue _id(int argc, KrkValue argv[], int hasKw) {
if (argc != 1) return krk_runtimeError(vm.exceptions->argumentError, "expected exactly one argument");
@ -852,13 +866,12 @@ void _createAndBind_builtins(void) {
"attaching new properties to the @c \\__builtins__ instance."
);
krk_makeClass(vm.builtins, &vm.baseClasses->propertyClass, "property", vm.baseClasses->objectClass);
krk_defineNative(&vm.baseClasses->propertyClass->methods, ".__init__", _property_init);
krk_defineNative(&vm.baseClasses->propertyClass->methods, ".__repr__", _property_repr);
krk_defineNative(&vm.baseClasses->propertyClass->methods, ":__doc__", _property_doc);
krk_defineNative(&vm.baseClasses->propertyClass->methods, ":__name__", _property_name);
krk_defineNative(&vm.baseClasses->propertyClass->methods, ":__method__", _property_method);
krk_finalizeClass(vm.baseClasses->propertyClass);
property = krk_makeClass(vm.builtins, &vm.baseClasses->propertyClass, "property", vm.baseClasses->objectClass);
BIND_METHOD(property,__init__);
BIND_METHOD(property,__get__);
BIND_METHOD(property,__set__);
BIND_METHOD(property,setter);
krk_finalizeClass(property);
krk_makeClass(vm.builtins, &Helper, "Helper", vm.baseClasses->objectClass);
KRK_DOC(Helper,

View File

@ -87,10 +87,6 @@ static void freeObject(KrkObj * object) {
FREE(KrkBytes, bytes);
break;
}
case OBJ_PROPERTY: {
FREE(KrkProperty, object);
break;
}
}
}
@ -181,11 +177,6 @@ static void blackenObject(KrkObj * object) {
markArray(&tuple->values);
break;
}
case OBJ_PROPERTY: {
KrkProperty * property = (KrkProperty *)object;
krk_markValue(property->method);
break;
}
case OBJ_NATIVE:
case OBJ_STRING:
case OBJ_BYTES:

View File

@ -278,12 +278,6 @@ KrkUpvalue * krk_newUpvalue(int slot) {
return upvalue;
}
KrkProperty * krk_newProperty(KrkValue method) {
KrkProperty * property = ALLOCATE_OBJECT(KrkProperty, OBJ_PROPERTY);
property->method = method;
return property;
}
KrkClass * krk_newClass(KrkString * name, KrkClass * baseClass) {
KrkClass * _class = ALLOCATE_OBJECT(KrkClass, OBJ_CLASS);
_class->name = name;

View File

@ -24,7 +24,6 @@ typedef enum {
OBJ_BOUND_METHOD,
OBJ_TUPLE,
OBJ_BYTES,
OBJ_PROPERTY,
} ObjType;
#undef KrkObj
@ -182,6 +181,8 @@ typedef struct KrkClass {
KrkObj * _setslice;
KrkObj * _delslice;
KrkObj * _contains;
KrkObj * _descget;
KrkObj * _descset;
} KrkClass;
/**
@ -240,19 +241,6 @@ typedef struct {
KrkValueArray values;
} KrkTuple;
/**
* @brief Dynamic property.
* @extends KrkObj
*
* A property is a value that is determined at runtime by a function and
* for which modifications using the dot operator and an assignment result
* in a function call.
*/
typedef struct {
KrkObj obj;
KrkValue method;
} KrkProperty;
/**
* @brief Mutable array of values.
* @extends KrkInstance
@ -381,7 +369,6 @@ extern KrkClass * krk_newClass(KrkString * name, KrkClass * base);
extern KrkInstance * krk_newInstance(KrkClass * _class);
extern KrkBoundMethod * krk_newBoundMethod(KrkValue receiver, KrkObj * method);
extern KrkTuple * krk_newTuple(size_t length);
extern KrkProperty * krk_newProperty(KrkValue method);
extern KrkBytes * krk_newBytes(size_t length, uint8_t * source);
extern void krk_bytesUpdateHash(KrkBytes * bytes);
extern void krk_tupleUpdateHash(KrkTuple * self);
@ -414,8 +401,6 @@ extern void krk_tupleUpdateHash(KrkTuple * self);
#define AS_BOUND_METHOD(value) ((KrkBoundMethod*)AS_OBJECT(value))
#define IS_TUPLE(value) isObjType(value, OBJ_TUPLE)
#define AS_TUPLE(value) ((KrkTuple *)AS_OBJECT(value))
#define IS_PROPERTY(value) isObjType(value, OBJ_PROPERTY)
#define AS_PROPERTY(value) ((KrkProperty *)AS_OBJECT(value))
#define AS_LIST(value) (&((KrkList *)AS_OBJECT(value))->values)
#define AS_DICT(value) (&((KrkDict *)AS_OBJECT(value))->entries)

View File

@ -229,7 +229,7 @@ void _createAndBind_threadsMod(void) {
KRK_DOC(BIND_METHOD(Thread,start), "Start the thread. A thread may only be started once.");
KRK_DOC(BIND_METHOD(Thread,join), "Join the thread. Does not return until the thread finishes.");
KRK_DOC(BIND_METHOD(Thread,is_alive), "Query the status of the thread.");
KRK_DOC(AS_NATIVE(BIND_PROP(Thread,tid)->method), "The platform-specific thread identifier, if available. Usually an integer.");
KRK_DOC(BIND_PROP(Thread,tid), "The platform-specific thread identifier, if available. Usually an integer.");
krk_finalizeClass(Thread);
krk_makeClass(threadsModule, &Lock, "Lock", vm.baseClasses->objectClass);

View File

@ -348,13 +348,14 @@ KrkNative * krk_defineNative(KrkTable * table, const char * name, NativeFn funct
* be used with the "fields" table rather than the methods table. This will eventually replace
* the ":field" option for defineNative().
*/
KrkProperty * krk_defineNativeProperty(KrkTable * table, const char * name, NativeFn function) {
KrkNative * krk_defineNativeProperty(KrkTable * table, const char * name, NativeFn function) {
KrkNative * func = krk_newNative(function, name, 1);
krk_push(OBJECT_VAL(func));
KrkProperty * property = krk_newProperty(krk_peek(0));
KrkInstance * property = krk_newInstance(vm.baseClasses->propertyClass);
krk_attachNamedObject(table, name, (KrkObj*)property);
krk_attachNamedObject(&property->fields, "fget", (KrkObj*)func);
krk_pop();
return property;
return func;
}
/**
@ -410,6 +411,8 @@ void krk_finalizeClass(KrkClass * _class) {
{&_class->_getattr, METHOD_GETATTR},
{&_class->_dir, METHOD_DIR},
{&_class->_contains, METHOD_CONTAINS},
{&_class->_descget, METHOD_DESCGET},
{&_class->_descset, METHOD_DESCSET},
{NULL, 0},
};
@ -481,8 +484,6 @@ inline KrkClass * krk_getType(KrkValue of) {
return vm.baseClasses->tupleClass;
case OBJ_BYTES:
return vm.baseClasses->bytesClass;
case OBJ_PROPERTY:
return vm.baseClasses->propertyClass;
case OBJ_INSTANCE:
return AS_INSTANCE(of)->_class;
default:
@ -906,10 +907,6 @@ int krk_bindMethod(KrkClass * _class, KrkString * name) {
if (!_class) return 0;
if (IS_NATIVE(method) && ((KrkNative*)AS_OBJECT(method))->isMethod == 2) {
out = AS_NATIVE(method)->function(1, (KrkValue[]){krk_peek(0)}, 0);
} else if (IS_PROPERTY(method)) {
/* Need to push for property object */
krk_push(krk_peek(0));
out = krk_callSimple(AS_PROPERTY(method)->method, 1, 0);
} else if (IS_CLOSURE(method) && (AS_CLOSURE(method)->function->isClassMethod)) {
out = OBJECT_VAL(krk_newBoundMethod(OBJECT_VAL(_class), AS_OBJECT(method)));
} else if (IS_CLOSURE(method) && (AS_CLOSURE(method)->function->isStaticMethod)) {
@ -917,6 +914,14 @@ int krk_bindMethod(KrkClass * _class, KrkString * name) {
} else if (IS_CLOSURE(method) || IS_NATIVE(method)) {
out = OBJECT_VAL(krk_newBoundMethod(krk_peek(0), AS_OBJECT(method)));
} else {
/* Does it have a descriptor __get__? */
KrkClass * type = krk_getType(method);
if (type->_descget) {
krk_push(method);
krk_swap(1);
krk_push(krk_callSimple(OBJECT_VAL(type->_descget), 2, 0));
return 1;
}
out = method;
}
krk_pop();
@ -1186,6 +1191,9 @@ void krk_initVM(int flags) {
/* Attribute access */
_(METHOD_GETATTR, "__getattr__"),
_(METHOD_DIR, "__dir__"),
/* Descriptor methods */
_(METHOD_DESCGET, "__get__"),
_(METHOD_DESCSET, "__set__"),
#undef _
};
for (size_t i = 0; i < METHOD__MAX; ++i) {
@ -1806,28 +1814,41 @@ static int valueDelProperty(KrkString * name) {
return 0;
}
static int trySetDescriptor(KrkValue owner, KrkString * name, KrkValue value) {
KrkClass * _class = krk_getType(owner);
KrkValue property;
while (_class) {
if (krk_tableGet(&_class->methods, OBJECT_VAL(name), &property)) break;
_class = _class->base;
}
if (_class) {
KrkClass * type = krk_getType(property);
if (type->_descset) {
/* Need to rearrange arguments */
krk_push(property); /* owner value property */
krk_swap(2); /* property value owner */
krk_swap(1); /* property owner value */
krk_push(krk_callSimple(OBJECT_VAL(type->_descset), 3, 0));
return 1;
}
}
return 0;
}
static int valueSetProperty(KrkString * name) {
KrkValue owner = krk_peek(1);
KrkValue value = krk_peek(0);
if (IS_INSTANCE(owner)) {
if (krk_tableSet(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), value)) {
KrkClass * _class = AS_INSTANCE(owner)->_class;
KrkValue method;
while (_class) {
if (krk_tableGet(&_class->methods, OBJECT_VAL(name), &method)) break;
_class = _class->base;
}
if (_class && IS_PROPERTY(method)) {
if (trySetDescriptor(owner, name, value)) {
krk_tableDelete(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name));
krk_push(krk_callSimple(AS_PROPERTY(method)->method, 2, 0));
return 1;
}
}
} else if (IS_CLASS(owner)) {
krk_tableSet(&AS_CLASS(owner)->methods, OBJECT_VAL(name), value);
} else {
/* TODO: Setters for other things */
return 0;
return (trySetDescriptor(owner,name,value));
}
krk_swap(1);
krk_pop();

View File

@ -88,6 +88,8 @@ typedef enum {
METHOD_SETSLICE,
METHOD_DELSLICE,
METHOD_CONTAINS,
METHOD_DESCGET,
METHOD_DESCSET,
METHOD__MAX,
} KrkSpecialMethods;
@ -423,7 +425,7 @@ extern KrkNative * krk_defineNative(KrkTable * table, const char * name, NativeF
* @param func Native function pointer to attach
* @return A pointer to the property object created.
*/
extern KrkProperty * krk_defineNativeProperty(KrkTable * table, const char * name, NativeFn func);
extern KrkNative * krk_defineNativeProperty(KrkTable * table, const char * name, NativeFn func);
/**
* @brief Attach a value to an attribute table.

41
test/testDescriptor.krk Normal file
View File

@ -0,0 +1,41 @@
def intDescriptor(inst, *args):
if args:
print("Setter called on",inst,"with value",args)
else:
print("Getter called on",inst)
return inst * inst
int.foo = property(intDescriptor)
print(2.foo)
2.foo = 72
# Now let's try an example straight from the Python docs
class LoggedAgeAccess:
def __get__(self, obj, objtype=None):
let value = obj._age
print("Accessing 'age' of", obj.name)
return value
def __set__(self, obj, value):
print("Updating 'age' of", obj.name, "to", value)
obj._age = value
class Person:
age = LoggedAgeAccess()
def __init__(self, name, age):
self.name = name
self.age = age
def birthday(self):
self.age += 1
let mary = Person('Mary M', 30)
let dave = Person('Dave D', 40)
print(*(x for x in dir(mary) if x not in dir(type(mary))))
print(*(x for x in dir(dave) if x not in dir(type(dave))))
print(mary.age)
mary.birthday()
print(dave.age)

View File

@ -0,0 +1,13 @@
Getter called on 2
4
Setter called on 2 with value [72]
Updating 'age' of Mary M to 30
Updating 'age' of Dave D to 40
_age name
_age name
Accessing 'age' of Mary M
30
Accessing 'age' of Mary M
Updating 'age' of Mary M to 31
Accessing 'age' of Dave D
40

View File

@ -27,3 +27,19 @@ print(f.bar)
Foo.fromString("test")
f.fromString("test")
Bar.fromString("test")
class Baz(object):
myBar = 42
@property
def bar(self):
print("I am a Python-style @property!")
return self.myBar
@bar.setter
def bar(self,value):
print("I am a Python-style @property's setter called with", value)
self.myBar = value
let b = Baz()
print(b.bar)
b.bar = 0xDEADBEEF
print(b.bar)

View File

@ -6,3 +6,8 @@ Called as a setter: [102]
<class '__main__.Foo'> test
<class '__main__.Foo'> test
<class '__main__.Bar'> test
I am a Python-style @property!
42
I am a Python-style @property's setter called with 3735928559
I am a Python-style @property!
3735928559

View File

@ -1,6 +1,6 @@
True
True
['__class__', '__dir__', '__doc__', '__hash__', '__init__', '__method__', '__module__', '__name__', '__repr__', '__str__']
['__class__', '__dir__', '__doc__', '__get__', '__hash__', '__init__', '__module__', '__name__', '__repr__', '__set__', '__str__', 'fget', 'setter']
p retrieved from A
{'a': 45}
calling property from subclass