diff --git a/src/include/wren.h b/src/include/wren.h index 7845911ce..30a9d9386 100644 --- a/src/include/wren.h +++ b/src/include/wren.h @@ -401,6 +401,19 @@ WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); // Gets the type of the object in [slot]. WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); +// Try to freeze the object in [slot] for eternity. +WREN_API bool wrenFreezeSlot(WrenVM* vm, int slot); + +// Test if the object in [slot] is frozen. +WREN_API bool wrenIsSlotFrozen(WrenVM* vm, int slot); + +// Try to (un)freeze the object in [slot]. +WREN_API bool wrenSetSlotFrozen(WrenVM* vm, int slot, bool isFrozen); + +// Try to (un)freeze the object in [slot] using [secretSlot]. +WREN_API bool wrenSetSlotFrozenWithSecret(WrenVM* vm, int slot, bool isFrozen, + int secretSlot); + // Reads a boolean value from [slot]. // // It is an error to call this if the slot does not contain a boolean value. diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index d0a121f8c..1660efee1 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -321,6 +321,8 @@ DEF_PRIMITIVE(list_new) DEF_PRIMITIVE(list_add) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); RETURN_VAL(args[1]); } @@ -330,6 +332,8 @@ DEF_PRIMITIVE(list_add) // minimize stack churn. DEF_PRIMITIVE(list_addCore) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); // Return the list. @@ -338,6 +342,8 @@ DEF_PRIMITIVE(list_addCore) DEF_PRIMITIVE(list_clear) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + wrenValueBufferClear(vm, &AS_LIST(args[0])->elements); RETURN_NULL; } @@ -349,6 +355,8 @@ DEF_PRIMITIVE(list_count) DEF_PRIMITIVE(list_insert) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + ObjList* list = AS_LIST(args[0]); // count + 1 here so you can "insert" at the very end. @@ -392,6 +400,8 @@ DEF_PRIMITIVE(list_iteratorValue) DEF_PRIMITIVE(list_removeAt) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + ObjList* list = AS_LIST(args[0]); uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index"); if (index == UINT32_MAX) return false; @@ -400,6 +410,8 @@ DEF_PRIMITIVE(list_removeAt) } DEF_PRIMITIVE(list_removeValue) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + ObjList* list = AS_LIST(args[0]); int index = wrenListIndexOf(vm, list, args[1]); if(index == -1) RETURN_NULL; @@ -414,6 +426,8 @@ DEF_PRIMITIVE(list_indexOf) DEF_PRIMITIVE(list_swap) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + ObjList* list = AS_LIST(args[0]); uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0"); if (indexA == UINT32_MAX) return false; @@ -461,6 +475,8 @@ DEF_PRIMITIVE(list_subscript) DEF_PRIMITIVE(list_subscriptSetter) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + ObjList* list = AS_LIST(args[0]); uint32_t index = validateIndex(vm, args[1], list->elements.count, "Subscript"); @@ -488,6 +504,8 @@ DEF_PRIMITIVE(map_subscript) DEF_PRIMITIVE(map_subscriptSetter) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + if (!validateKey(vm, args[1])) return false; wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); @@ -499,6 +517,8 @@ DEF_PRIMITIVE(map_subscriptSetter) // minimize stack churn. DEF_PRIMITIVE(map_addCore) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + if (!validateKey(vm, args[1])) return false; wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); @@ -509,6 +529,8 @@ DEF_PRIMITIVE(map_addCore) DEF_PRIMITIVE(map_clear) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + wrenMapClear(vm, AS_MAP(args[0])); RETURN_NULL; } @@ -560,6 +582,8 @@ DEF_PRIMITIVE(map_iterate) DEF_PRIMITIVE(map_remove) { + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + if (!validateKey(vm, args[1])) return false; RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1])); @@ -885,6 +909,30 @@ DEF_PRIMITIVE(object_is) RETURN_BOOL(false); } +DEF_PRIMITIVE(object_freeze) +{ + RETURN_BOOL(wrenFreeze(args[0])); +} + +DEF_PRIMITIVE(object_isFrozen) +{ + RETURN_BOOL(wrenIsFrozen(args[0])); +} + +DEF_PRIMITIVE(object_setFrozen) +{ + if (!validateBool(vm, args[1], "Value")) return false; + + RETURN_BOOL(wrenSetFrozen(args[0], AS_BOOL(args[1]))); +} + +DEF_PRIMITIVE(object_setFrozenWithSecret) +{ + if (!validateBool(vm, args[1], "Value")) return false; + + RETURN_BOOL(wrenSetFrozenWithSecret(args[0], AS_BOOL(args[1]), args[2])); +} + DEF_PRIMITIVE(object_toString) { Obj* obj = AS_OBJ(args[0]); @@ -897,6 +945,12 @@ DEF_PRIMITIVE(object_type) RETURN_OBJ(wrenGetClass(vm, args[0])); } +DEF_PRIMITIVE(object_validateIsNotFrozen) +{ + VALIDATE_VALUE_IS_NOT_FROZEN(args[0]); + RETURN_VAL(args[0]); +} + DEF_PRIMITIVE(range_from) { RETURN_NUM(AS_RANGE(args[0])->from); @@ -1247,9 +1301,14 @@ void wrenInitializeCore(WrenVM* vm) PRIMITIVE(vm->objectClass, "!", object_not); PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); + PRIMITIVE(vm->objectClass, "freeze()", object_freeze); PRIMITIVE(vm->objectClass, "is(_)", object_is); + PRIMITIVE(vm->objectClass, "isFrozen", object_isFrozen); + PRIMITIVE(vm->objectClass, "setFrozen(_)", object_setFrozen); + PRIMITIVE(vm->objectClass, "setFrozen(_,_)", object_setFrozenWithSecret); PRIMITIVE(vm->objectClass, "toString", object_toString); PRIMITIVE(vm->objectClass, "type", object_type); + PRIMITIVE(vm->objectClass, "validateIsNotFrozen()", object_validateIsNotFrozen); // Now we can define Class, which is a subclass of Object. vm->classClass = defineClass(vm, coreModule, "Class"); diff --git a/src/vm/wren_core.wren b/src/vm/wren_core.wren index f073062c2..998d45ecc 100644 --- a/src/vm/wren_core.wren +++ b/src/vm/wren_core.wren @@ -409,6 +409,7 @@ class MapEntry { construct new(key, value) { _key = key _value = value + freeze() } key { _key } diff --git a/src/vm/wren_core.wren.inc b/src/vm/wren_core.wren.inc index be296cdf7..d785f7193 100644 --- a/src/vm/wren_core.wren.inc +++ b/src/vm/wren_core.wren.inc @@ -411,6 +411,7 @@ static const char* coreModuleSource = " construct new(key, value) {\n" " _key = key\n" " _value = value\n" +" freeze()\n" " }\n" "\n" " key { _key }\n" diff --git a/src/vm/wren_primitive.h b/src/vm/wren_primitive.h index 3f9b6f722..823def4d5 100644 --- a/src/vm/wren_primitive.h +++ b/src/vm/wren_primitive.h @@ -63,6 +63,17 @@ return false; \ } while (false) +#define ERROR_MSG_OBJECT_IS_FROZEN "Object is frozen" + +#define VALIDATE_VALUE_IS_NOT_FROZEN(value) \ + do \ + { \ + if (wrenIsFrozen(value)) \ + { \ + RETURN_ERROR(ERROR_MSG_OBJECT_IS_FROZEN); \ + } \ + } while (false) + // Validates that the given [arg] is a bool. Returns true if it is. If not, // reports an error and returns false. bool validateBool(WrenVM* vm, Value arg, const char* argName); diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index c49a3b6be..f947d1cb8 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -38,11 +38,19 @@ static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) { obj->type = type; obj->isDark = false; + obj->isFrozen = false; + obj->frozenSecret = FREEZE_SECRET_DEFAULT; obj->classObj = classObj; obj->next = vm->first; vm->first = obj; } +static void freezeObj(Obj* obj) +{ + obj->isFrozen = true; + obj->frozenSecret = FREEZE_SECRET_ETERNAL; +} + ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) { ObjClass* classObj = ALLOCATE(vm, ObjClass); @@ -664,6 +672,7 @@ Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) { ObjRange* range = ALLOCATE(vm, ObjRange); initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass); + freezeObj(&range->obj); range->from = from; range->to = to; range->isInclusive = isInclusive; @@ -716,6 +725,7 @@ Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length) ASSERT(length == 0 || text != NULL, "Unexpected NULL string."); ObjString* string = allocateString(vm, length); + freezeObj(&string->obj); // Copy the string (if given one). if (length > 0 && text != NULL) memcpy(string->value, text, length); @@ -983,6 +993,8 @@ void wrenGrayObj(WrenVM* vm, Obj* obj) // It's been reached. obj->isDark = true; + wrenGrayValue(vm, obj->frozenSecret); + // Add it to the gray list so it can be recursively explored for // more marks later. if (vm->grayCount >= vm->grayCapacity) diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index 2bbadeff1..c9b0bb462 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -141,6 +141,9 @@ struct sObj ObjType type; bool isDark; + bool isFrozen; + Value frozenSecret; + // The object's class. ObjClass* classObj; diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 96c570a14..99354582f 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -885,6 +885,9 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) } \ } while (false) + #define ERROR_ON_VALUE_IS_NOT_FROZEN(value) \ + ERROR_ON(wrenIsFrozen(value), ERROR_MSG_OBJECT_IS_FROZEN) + #if WREN_DEBUG_TRACE_INSTRUCTIONS // Prints the stack and instruction before each instruction is executed. #define DEBUG_TRACE_INSTRUCTIONS() \ @@ -1110,6 +1113,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) CASE_CODE(STORE_UPVALUE): { + ERROR_ON_VALUE_IS_NOT_FROZEN(OBJ_VAL(frame->closure)); ObjUpvalue** upvalues = frame->closure->upvalues; *upvalues[READ_BYTE()]->value = PEEK(); DISPATCH(); @@ -1128,6 +1132,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) uint8_t field = READ_BYTE(); Value receiver = stackStart[0]; ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ERROR_ON_VALUE_IS_NOT_FROZEN(receiver); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); instance->fields[field] = PEEK(); @@ -1150,6 +1155,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) uint8_t field = READ_BYTE(); Value receiver = POP(); ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ERROR_ON_VALUE_IS_NOT_FROZEN(receiver); ObjInstance* instance = AS_INSTANCE(receiver); ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); instance->fields[field] = PEEK(); @@ -1683,6 +1689,35 @@ WrenType wrenGetSlotType(WrenVM* vm, int slot) return WREN_TYPE_UNKNOWN; } +bool wrenFreezeSlot(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + + return wrenFreeze(vm->apiStack[slot]); +} + +bool wrenIsSlotFrozen(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + + return wrenIsFrozen(vm->apiStack[slot]); +} + +bool wrenSetSlotFrozen(WrenVM* vm, int slot, bool isFrozen) +{ + validateApiSlot(vm, slot); + + return wrenSetFrozen(vm->apiStack[slot], isFrozen); +} + +bool wrenSetSlotFrozenWithSecret(WrenVM* vm, int slot, bool isFrozen, int secretSlot) +{ + validateApiSlot(vm, slot); + validateApiSlot(vm, secretSlot); + + return wrenSetFrozenWithSecret(vm->apiStack[slot], isFrozen, vm->apiStack[secretSlot]); +} + bool wrenGetSlotBool(WrenVM* vm, int slot) { validateApiSlot(vm, slot); diff --git a/src/vm/wren_vm.h b/src/vm/wren_vm.h index 7ab74c9ee..96192c5bf 100644 --- a/src/vm/wren_vm.h +++ b/src/vm/wren_vm.h @@ -248,4 +248,54 @@ static inline bool wrenIsFalsyValue(Value value) return IS_FALSE(value) || IS_NULL(value); } +#define FREEZE_SECRET_DEFAULT NULL_VAL +#define FREEZE_SECRET_ETERNAL UNDEFINED_VAL + +static inline bool wrenIsFrozen(Value value) +{ + if (IS_OBJ(value)) return AS_OBJ(value)->isFrozen; + + return true; +} + +static inline bool wrenSetFrozenWithSecret(Value value, bool isFrozen, Value secret) +{ + if (IS_OBJ(value)) + { + Obj* valueObj = AS_OBJ(value); + + if (isFrozen) + { + // Trying to set secret + if (!wrenIsFrozen(value)) + { + valueObj->isFrozen = true; + valueObj->frozenSecret = secret; + } + } + else + { + // Trying to clear secret + if (wrenIsFrozen(value) && + wrenValuesSame(valueObj->frozenSecret, secret)) + { + valueObj->isFrozen = false; + valueObj->frozenSecret = FREEZE_SECRET_DEFAULT; + } + } + } + + return wrenIsFrozen(value); +} + +static inline bool wrenSetFrozen(Value value, bool isFrozen) +{ + return wrenSetFrozenWithSecret(value, isFrozen, FREEZE_SECRET_DEFAULT); +} + +static inline bool wrenFreeze(Value value) +{ + return wrenSetFrozenWithSecret(value, true, FREEZE_SECRET_ETERNAL); +} + #endif diff --git a/test/core/bool/is_frozen.wren b/test/core/bool/is_frozen.wren new file mode 100644 index 000000000..f55fe7e60 --- /dev/null +++ b/test/core/bool/is_frozen.wren @@ -0,0 +1,3 @@ + +System.print(false.isFrozen) // expect: true +System.print(true.isFrozen) // expect: true diff --git a/test/core/bool/set_frozen.wren b/test/core/bool/set_frozen.wren new file mode 100644 index 000000000..8e836a38e --- /dev/null +++ b/test/core/bool/set_frozen.wren @@ -0,0 +1,3 @@ + +System.print(false.setFrozen(false)) // expect: true +System.print(true.setFrozen(false)) // expect: true diff --git a/test/core/class/is_frozen.wren b/test/core/class/is_frozen.wren new file mode 100644 index 000000000..a97de1c5e --- /dev/null +++ b/test/core/class/is_frozen.wren @@ -0,0 +1,16 @@ + +class Foo { +} + +// Check core bootstrap classes +System.print(Class.isFrozen) // expect: false +System.print(Object.isFrozen) // expect: false +System.print(Object.type.isFrozen) // expect: false + +// Check a core class +System.print(List.isFrozen) // expect: false +System.print(List.type.isFrozen) // expect: false + +// Check a user defined class +System.print(Foo.isFrozen) // expect: false +System.print(Foo.type.isFrozen) // expect: false diff --git a/test/core/function/frozen_are_immutable.wren b/test/core/function/frozen_are_immutable.wren new file mode 100644 index 000000000..78285b4c5 --- /dev/null +++ b/test/core/function/frozen_are_immutable.wren @@ -0,0 +1,10 @@ + +// Check against local upvalues + +{ + var foo + var fn = Fn.new { foo = 42 } // expect runtime error: Object is frozen + + System.print(fn.freeze()) // expect: true + System.print(fn.call()) +} diff --git a/test/core/function/frozen_are_immutable_global.wren b/test/core/function/frozen_are_immutable_global.wren new file mode 100644 index 000000000..c7c3e2717 --- /dev/null +++ b/test/core/function/frozen_are_immutable_global.wren @@ -0,0 +1,8 @@ + +// Check against global upvalues + +var foo +var fn = Fn.new { foo = 42 } + +System.print(fn.setFrozen(true)) // expect: true +System.print(fn.call()) // expect: 42 diff --git a/test/core/function/is_frozen.wren b/test/core/function/is_frozen.wren new file mode 100644 index 000000000..a5dc16e19 --- /dev/null +++ b/test/core/function/is_frozen.wren @@ -0,0 +1,5 @@ + +var fn = Fn.new { 42 } + +System.print(fn.isFrozen) // expect: false +System.print(fn.call()) // expect: 42 diff --git a/test/core/function/set_frozen.wren b/test/core/function/set_frozen.wren new file mode 100644 index 000000000..3e5e5a1f7 --- /dev/null +++ b/test/core/function/set_frozen.wren @@ -0,0 +1,6 @@ + +var fn = Fn.new { 42 } + +System.print(fn.freeze()) // expect: true +System.print(fn.isFrozen) // expect: true +System.print(fn.call()) // expect: 42 diff --git a/test/core/list/is_frozen.wren b/test/core/list/is_frozen.wren new file mode 100644 index 000000000..e89c94ebe --- /dev/null +++ b/test/core/list/is_frozen.wren @@ -0,0 +1,2 @@ + +System.print([].isFrozen) // expect: false diff --git a/test/core/map/is_frozen.wren b/test/core/map/is_frozen.wren new file mode 100644 index 000000000..53b03723f --- /dev/null +++ b/test/core/map/is_frozen.wren @@ -0,0 +1,2 @@ + +System.print({}.isFrozen) // expect: false diff --git a/test/core/map_entry/is_frozen.wren b/test/core/map_entry/is_frozen.wren new file mode 100644 index 000000000..3ad3baff7 --- /dev/null +++ b/test/core/map_entry/is_frozen.wren @@ -0,0 +1,2 @@ + +System.print(MapEntry.new("key", "value").isFrozen) // expect: true diff --git a/test/core/map_entry/set_frozen.wren b/test/core/map_entry/set_frozen.wren new file mode 100644 index 000000000..e69de29bb diff --git a/test/core/null/is_frozen.wren b/test/core/null/is_frozen.wren new file mode 100644 index 000000000..c60c13603 --- /dev/null +++ b/test/core/null/is_frozen.wren @@ -0,0 +1,2 @@ + +System.print(null.isFrozen) // expect: true diff --git a/test/core/null/set_frozen.wren b/test/core/null/set_frozen.wren new file mode 100644 index 000000000..e5122219d --- /dev/null +++ b/test/core/null/set_frozen.wren @@ -0,0 +1,2 @@ + +System.print(null.setFrozen(false)) // expect: true diff --git a/test/core/number/is_frozen.wren b/test/core/number/is_frozen.wren new file mode 100644 index 000000000..2be0e0759 --- /dev/null +++ b/test/core/number/is_frozen.wren @@ -0,0 +1,5 @@ + +System.print(0.0.isFrozen) // expect: true +System.print(100.isFrozen) // expect: true +System.print(Num.infinity.isFrozen) // expect: true +System.print(Num.nan.isFrozen) // expect: true diff --git a/test/core/number/set_frozen.wren b/test/core/number/set_frozen.wren new file mode 100644 index 000000000..a1a03fdc4 --- /dev/null +++ b/test/core/number/set_frozen.wren @@ -0,0 +1,5 @@ + +System.print(0.0.setFrozen(false)) // expect: true +System.print(100.setFrozen(false)) // expect: true +System.print(Num.infinity.setFrozen(false)) // expect: true +System.print(Num.nan.setFrozen(false)) // expect: true diff --git a/test/core/object/freeze.wren b/test/core/object/freeze.wren new file mode 100644 index 000000000..831819f00 --- /dev/null +++ b/test/core/object/freeze.wren @@ -0,0 +1,14 @@ + +class Foo { + construct new() { } +} + +var foo = Foo.new() + +System.print(foo.isFrozen) // expect: false +System.print(foo.freeze()) // expect: true +System.print(foo.isFrozen) // expect: true + +// Check `freeze` can be repeated without error +System.print(foo.freeze()) // expect: true +System.print(foo.isFrozen) // expect: true diff --git a/test/core/object/frozen_are_immutable.wren b/test/core/object/frozen_are_immutable.wren new file mode 100644 index 000000000..739c556e9 --- /dev/null +++ b/test/core/object/frozen_are_immutable.wren @@ -0,0 +1,7 @@ + +class Foo { + construct new() { freeze() } + mutate() { _value = 42 } // expect runtime error: Object is frozen +} + +System.print(Foo.new().mutate()) diff --git a/test/core/object/set_frozen.wren b/test/core/object/set_frozen.wren new file mode 100644 index 000000000..14b8cd342 --- /dev/null +++ b/test/core/object/set_frozen.wren @@ -0,0 +1,18 @@ + +class Foo { + construct new() { } +} + +var foo = Foo.new() + +System.print(foo.isFrozen) // expect: false +System.print(foo.setFrozen(true)) // expect: true +System.print(foo.isFrozen) // expect: true + +// Check `setFrozen` can be repeated without error +System.print(foo.setFrozen(true)) // expect: true +System.print(foo.isFrozen) // expect: true + +// Check `setFrozen` can be unset +System.print(foo.setFrozen(false)) // expect: false +System.print(foo.isFrozen) // expect: false diff --git a/test/core/object/set_frozen_with_secret.wren b/test/core/object/set_frozen_with_secret.wren new file mode 100644 index 000000000..f835968aa --- /dev/null +++ b/test/core/object/set_frozen_with_secret.wren @@ -0,0 +1,21 @@ + +class Foo { + construct new() { } +} + +class Secret { +} + +var foo = Foo.new() + +System.print(foo.isFrozen) // expect: false +System.print(foo.setFrozen(true, Secret)) // expect: true +System.print(foo.isFrozen) // expect: true + +// Check `setFrozen` can be performed multiple times +System.print(foo.setFrozen(true, Secret)) // expect: true +System.print(foo.isFrozen) // expect: true + +// Check `setFrozen` can be unset +System.print(foo.setFrozen(false, Secret)) // expect: false +System.print(foo.isFrozen) // expect: false diff --git a/test/core/object/unset_frozen_with_wrong_secret.wren b/test/core/object/unset_frozen_with_wrong_secret.wren new file mode 100644 index 000000000..7eaacc731 --- /dev/null +++ b/test/core/object/unset_frozen_with_wrong_secret.wren @@ -0,0 +1,22 @@ + +class Foo { + construct new() { } +} + +class Secret { +} + +class WrongSecret { +} + +var foo = Foo.new() + +System.print(foo.setFrozen(true, Secret)) // expect: true + +// Check `setFrozen` cannot be changed with the wrong secret +System.print(foo.setFrozen(false, WrongSecret)) // expect: true +System.print(foo.isFrozen) // expect: true + +// Check `setFrozen` can be unset with the secret +System.print(foo.setFrozen(false, Secret)) // expect: false +System.print(foo.isFrozen) // expect: false diff --git a/test/core/range/is_frozen.wren b/test/core/range/is_frozen.wren new file mode 100644 index 000000000..86a3fdf20 --- /dev/null +++ b/test/core/range/is_frozen.wren @@ -0,0 +1,2 @@ + +System.print((0..1).isFrozen) // expect: true diff --git a/test/core/range/set_frozen.wren b/test/core/range/set_frozen.wren new file mode 100644 index 000000000..0ac9296bf --- /dev/null +++ b/test/core/range/set_frozen.wren @@ -0,0 +1,2 @@ + +System.print((0..1).setFrozen(false)) // expect: true diff --git a/test/core/string/is_frozen.wren b/test/core/string/is_frozen.wren new file mode 100644 index 000000000..42fbc2d01 --- /dev/null +++ b/test/core/string/is_frozen.wren @@ -0,0 +1,2 @@ + +System.print("".isFrozen) // expect: true diff --git a/test/core/string/set_frozen.wren b/test/core/string/set_frozen.wren new file mode 100644 index 000000000..da7bd2caa --- /dev/null +++ b/test/core/string/set_frozen.wren @@ -0,0 +1,2 @@ + +System.print("".setFrozen(false)) // expect: true