diff --git a/src/builtins.c b/src/builtins.c index bc58757..b925101 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -115,6 +115,24 @@ KRK_Method(object,__eq__) { 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__() * @@ -1100,6 +1118,7 @@ void _createAndBind_builtins(void) { BIND_METHOD(object,__str__); BIND_METHOD(object,__hash__); 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_finalizeClass(object); KRK_DOC(object, diff --git a/src/kuroko/object.h b/src/kuroko/object.h index 533a8e6..984c216 100644 --- a/src/kuroko/object.h +++ b/src/kuroko/object.h @@ -237,6 +237,7 @@ typedef struct KrkClass { KrkObj * _set_name; KrkObj * _matmul, * _rmatmul, * _imatmul; KrkObj * _pos; + KrkObj * _setattr; } KrkClass; /** diff --git a/src/methods.h b/src/methods.h index 8fef54d..0fa2e31 100644 --- a/src/methods.h +++ b/src/methods.h @@ -45,6 +45,7 @@ CACHED_METHOD(INVERT, "__invert__", _invert) CACHED_METHOD(NEGATE, "__neg__", _negate) CACHED_METHOD(SETNAME, "__set_name__", _set_name) CACHED_METHOD(POS, "__pos__", _pos) +CACHED_METHOD(SETATTR, "__setattr__", _setattr) /* These are not methods */ SPECIAL_ATTRS(CLASS, "__class__") diff --git a/src/table.c b/src/table.c index 2375e34..9a9f209 100644 --- a/src/table.c +++ b/src/table.c @@ -133,6 +133,16 @@ int krk_tableSet(KrkTable * table, KrkValue key, KrkValue value) { 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) { for (size_t i = 0; i < from->capacity; ++i) { KrkTableEntry * entry = &from->entries[i]; diff --git a/src/vm.c b/src/vm.c index 4e1cf89..02f21a3 100644 --- a/src/vm.c +++ b/src/vm.c @@ -525,7 +525,7 @@ void krk_finalizeClass(KrkClass * _class) { if (krk_tableGet(&_base->methods, vm.specialMethodNames[entry->index], &tmp)) break; _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); } } @@ -2409,10 +2409,46 @@ static int trySetDescriptor(KrkValue owner, KrkString * name, KrkValue value) { 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) { KrkValue owner = krk_peek(1); KrkValue value = krk_peek(0); 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 (trySetDescriptor(owner, name, value)) { krk_tableDelete(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name)); diff --git a/test.krk b/test.krk new file mode 100644 index 0000000..9356a33 --- /dev/null +++ b/test.krk @@ -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) diff --git a/test/testAttributePacking.krk.expect b/test/testAttributePacking.krk.expect index ede9afe..42a70c3 100644 --- a/test/testAttributePacking.krk.expect +++ b/test/testAttributePacking.krk.expect @@ -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 2 3 diff --git a/test/testDel.krk.expect b/test/testDel.krk.expect index 8740a38..0ff66f9 100644 --- a/test/testDel.krk.expect +++ b/test/testDel.krk.expect @@ -7,10 +7,10 @@ False [1, 3, 4, 5] [1, 3, 4] list index out of range: 3 -['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__str__', 'baz', 'qux'] +['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__setattr__', '__str__', 'baz', 'qux'] 42 -['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__str__', 'qux'] +['__class__', '__dir__', '__eq__', '__hash__', '__repr__', '__setattr__', '__str__', 'qux'] hi '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' diff --git a/test/testGetattrDir.krk.expect b/test/testGetattrDir.krk.expect index 71ae650..269b0be 100644 --- a/test/testGetattrDir.krk.expect +++ b/test/testGetattrDir.krk.expect @@ -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 -['__class__', '__dir__', '__eq__', '__func__', '__hash__', '__module__', '__qualname__', '__repr__', '__str__', 'butts'] +['__class__', '__dir__', '__eq__', '__func__', '__hash__', '__module__', '__qualname__', '__repr__', '__setattr__', '__str__', 'butts'] diff --git a/test/testSetAttrClass.krk b/test/testSetAttrClass.krk new file mode 100644 index 0000000..9356a33 --- /dev/null +++ b/test/testSetAttrClass.krk @@ -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) diff --git a/test/testSetAttrClass.krk.expect b/test/testSetAttrClass.krk.expect new file mode 100644 index 0000000..52752f7 --- /dev/null +++ b/test/testSetAttrClass.krk.expect @@ -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 diff --git a/test/testSubclassPropertySuperCall.krk.expect b/test/testSubclassPropertySuperCall.krk.expect index 2a79822..61f0deb 100644 --- a/test/testSubclassPropertySuperCall.krk.expect +++ b/test/testSubclassPropertySuperCall.krk.expect @@ -1,6 +1,6 @@ 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 {'a': 45} calling property from subclass