Rudimentary __setattr__ support

This commit is contained in:
K. Lange 2022-07-07 14:54:30 +09:00
parent cf98b93836
commit c10a457242
12 changed files with 127 additions and 8 deletions

View File

@ -115,6 +115,24 @@ KRK_Method(object,__eq__) {
return NOTIMPL_VAL(); return NOTIMPL_VAL();
} }
extern KrkValue krk_instanceSetAttribute_wrapper(KrkValue owner, KrkString * name, KrkValue to);
/**
* This must be marked as static so it doesn't get bound, or every object will
* have a functioning __setattr__ and the VM will get a lot slower... I think...
*/
KRK_Method(object,__setattr__) {
METHOD_TAKES_EXACTLY(2);
if (!IS_STRING(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected str");
if (!IS_INSTANCE(argv[0])) {
return krk_valueSetAttribute(argv[0], AS_CSTRING(argv[1]), argv[2]);
}
/* It's an instance, that presumably does not have a `__setattr__`? */
return krk_instanceSetAttribute_wrapper(argv[0], AS_STRING(argv[1]), argv[2]);
}
/** /**
* object.__str__() / object.__repr__() * object.__str__() / object.__repr__()
* *
@ -1100,6 +1118,7 @@ void _createAndBind_builtins(void) {
BIND_METHOD(object,__str__); BIND_METHOD(object,__str__);
BIND_METHOD(object,__hash__); BIND_METHOD(object,__hash__);
BIND_METHOD(object,__eq__); BIND_METHOD(object,__eq__);
BIND_METHOD(object,__setattr__)->obj.flags = KRK_OBJ_FLAGS_FUNCTION_IS_STATIC_METHOD;
krk_defineNative(&object->methods, "__repr__", FUNC_NAME(object,__str__)); krk_defineNative(&object->methods, "__repr__", FUNC_NAME(object,__str__));
krk_finalizeClass(object); krk_finalizeClass(object);
KRK_DOC(object, KRK_DOC(object,

View File

@ -237,6 +237,7 @@ typedef struct KrkClass {
KrkObj * _set_name; KrkObj * _set_name;
KrkObj * _matmul, * _rmatmul, * _imatmul; KrkObj * _matmul, * _rmatmul, * _imatmul;
KrkObj * _pos; KrkObj * _pos;
KrkObj * _setattr;
} KrkClass; } KrkClass;
/** /**

View File

@ -45,6 +45,7 @@ CACHED_METHOD(INVERT, "__invert__", _invert)
CACHED_METHOD(NEGATE, "__neg__", _negate) CACHED_METHOD(NEGATE, "__neg__", _negate)
CACHED_METHOD(SETNAME, "__set_name__", _set_name) CACHED_METHOD(SETNAME, "__set_name__", _set_name)
CACHED_METHOD(POS, "__pos__", _pos) CACHED_METHOD(POS, "__pos__", _pos)
CACHED_METHOD(SETATTR, "__setattr__", _setattr)
/* These are not methods */ /* These are not methods */
SPECIAL_ATTRS(CLASS, "__class__") SPECIAL_ATTRS(CLASS, "__class__")

View File

@ -133,6 +133,16 @@ int krk_tableSet(KrkTable * table, KrkValue key, KrkValue value) {
return isNewKey; return isNewKey;
} }
int krk_tableSetIfExists(KrkTable * table, KrkValue key, KrkValue value) {
if (table->count == 0) return 0;
KrkTableEntry * entry = krk_findEntry(table->entries, table->capacity, key);
if (!entry) return 0;
if (IS_KWARGS(entry->key)) return 0; /* Not found */
entry->key = key;
entry->value = value;
return 1;
}
void krk_tableAddAll(KrkTable * from, KrkTable * to) { void krk_tableAddAll(KrkTable * from, KrkTable * to) {
for (size_t i = 0; i < from->capacity; ++i) { for (size_t i = 0; i < from->capacity; ++i) {
KrkTableEntry * entry = &from->entries[i]; KrkTableEntry * entry = &from->entries[i];

View File

@ -525,7 +525,7 @@ void krk_finalizeClass(KrkClass * _class) {
if (krk_tableGet(&_base->methods, vm.specialMethodNames[entry->index], &tmp)) break; if (krk_tableGet(&_base->methods, vm.specialMethodNames[entry->index], &tmp)) break;
_base = _base->base; _base = _base->base;
} }
if (_base && (IS_CLOSURE(tmp) || IS_NATIVE(tmp))) { if (_base && (IS_CLOSURE(tmp) || IS_NATIVE(tmp)) && !(AS_OBJECT(tmp)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_STATIC_METHOD)) {
*entry->method = AS_OBJECT(tmp); *entry->method = AS_OBJECT(tmp);
} }
} }
@ -2409,10 +2409,46 @@ static int trySetDescriptor(KrkValue owner, KrkString * name, KrkValue value) {
return 0; return 0;
} }
extern int krk_tableSetIfExists(KrkTable * table, KrkValue key, KrkValue value);
_noexport
KrkValue krk_instanceSetAttribute_wrapper(KrkValue owner, KrkString * name, KrkValue to) {
if (!krk_tableSetIfExists(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), to)) {
/* That might have raised an exception. */
if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) return NONE_VAL();
/* Entry did not exist, check for properties */
KrkClass * _class = krk_getType(owner);
KrkValue property;
do {
if (krk_tableGet_fast(&_class->methods, name, &property)) break;
_class = _class->base;
} while (_class);
if (_class) {
KrkClass * type = krk_getType(property);
if (type->_descset) {
krk_push(property);
krk_push(owner);
krk_push(to);
return krk_callDirect(type->_descset, 3);
}
}
/* No descriptor, go ahead and set. */
krk_tableSet(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), to);
}
return to;
}
static int valueSetProperty(KrkString * name) { static int valueSetProperty(KrkString * name) {
KrkValue owner = krk_peek(1); KrkValue owner = krk_peek(1);
KrkValue value = krk_peek(0); KrkValue value = krk_peek(0);
if (IS_INSTANCE(owner)) { if (IS_INSTANCE(owner)) {
KrkClass * type = AS_INSTANCE(owner)->_class;
if (unlikely(type->_setattr != NULL)) {
krk_push(OBJECT_VAL(name));
krk_swap(1);
krk_push(krk_callDirect(type->_setattr, 3));
return 1;
}
if (krk_tableSet(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), value)) { if (krk_tableSet(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), value)) {
if (trySetDescriptor(owner, name, value)) { if (trySetDescriptor(owner, name, value)) {
krk_tableDelete(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name)); krk_tableDelete(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name));

22
test.krk Normal file
View File

@ -0,0 +1,22 @@
class Foo(object):
myBar = 42
@staticmethod
def foo():
print("No args!")
@property
def bar(*setter):
if setter:
print("Called as a setter:", setter)
self.myBar = setter[0]
else:
print("Called as __get__:")
return self.myBar
def __setattr__(self, string, value):
print("set",string,"to",value)
return object.__setattr__(self,string,value)
let f = Foo()
print(f.bar)
print(f.bar = 'hi')
print(f.bar)

View File

@ -1,4 +1,4 @@
['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__str__', 'longList', 'ofAttributes', 'onThatObject', 'thatWeWantToSet'] ['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__setattr__', '__str__', 'longList', 'ofAttributes', 'onThatObject', 'thatWeWantToSet']
1 1
2 2
3 3

View File

@ -7,10 +7,10 @@ False
[1, 3, 4, 5] [1, 3, 4, 5]
[1, 3, 4] [1, 3, 4]
list index out of range: 3 list index out of range: 3
['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__str__', 'baz', 'qux'] ['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__setattr__', '__str__', 'baz', 'qux']
42 42
['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__str__', 'qux'] ['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__setattr__', '__str__', 'qux']
hi hi
'object' object has no attribute 'baz' 'object' object has no attribute 'baz'
['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__str__'] ['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__setattr__', '__str__']
'object' object has no attribute 'bar' 'object' object has no attribute 'bar'

View File

@ -1,3 +1,3 @@
['__class__', '__dir__', '__eq__', '__func__', '__getattr__', '__hash__', '__init__', '__module__', '__qualname__', '__repr__', '__str__', '_dict'] ['__class__', '__dir__', '__eq__', '__func__', '__getattr__', '__hash__', '__init__', '__module__', '__qualname__', '__repr__', '__setattr__', '__str__', '_dict']
1 1
['__class__', '__dir__', '__eq__', '__func__', '__hash__', '__module__', '__qualname__', '__repr__', '__str__', 'butts'] ['__class__', '__dir__', '__eq__', '__func__', '__hash__', '__module__', '__qualname__', '__repr__', '__setattr__', '__str__', 'butts']

22
test/testSetAttrClass.krk Normal file
View File

@ -0,0 +1,22 @@
class Foo(object):
myBar = 42
@staticmethod
def foo():
print("No args!")
@property
def bar(*setter):
if setter:
print("Called as a setter:", setter)
self.myBar = setter[0]
else:
print("Called as __get__:")
return self.myBar
def __setattr__(self, string, value):
print("set",string,"to",value)
return object.__setattr__(self,string,value)
let f = Foo()
print(f.bar)
print(f.bar = 'hi')
print(f.bar)

View File

@ -0,0 +1,8 @@
Called as __get__:
42
set bar to hi
Called as a setter: ['hi']
set myBar to hi
hi
Called as __get__:
hi

View File

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