From 8e87249da6928cc6e3c6b4eab8d06c57264ef302 Mon Sep 17 00:00:00 2001 From: Tommie Gannert Date: Wed, 13 Oct 2021 12:33:26 +0200 Subject: [PATCH] Add Symbols. This allows e.g. setting the @@iterator property on an object, turning it into an Iterable. --- object.go | 31 ++++++++++ object_template_test.go | 17 ++++++ object_test.go | 29 +++++++++- symbol.go | 60 +++++++++++++++++++ symbol_test.go | 43 ++++++++++++++ template.go | 44 ++++++++++++++ v8go.cc | 124 ++++++++++++++++++++++++++++++++++++++++ v8go.h | 37 ++++++++++-- value.go | 9 +++ value_test.go | 26 +++++++++ 10 files changed, 413 insertions(+), 7 deletions(-) create mode 100644 symbol.go create mode 100644 symbol_test.go diff --git a/object.go b/object.go index 6c56887b7..23f953459 100644 --- a/object.go +++ b/object.go @@ -69,6 +69,20 @@ func (o *Object) Set(key string, val interface{}) error { return nil } +// SetSymbol will set a property on the Object to a given value. +// Supports all value types, eg: Object, Array, Date, Set, Map etc +// If the value passed is a Go supported primitive (string, int32, uint32, int64, uint64, float64, big.Int) +// then a *Value will be created and set as the value property. +func (o *Object) SetSymbol(key *Symbol, val interface{}) error { + value, err := coerceValue(o.ctx.iso, val) + if err != nil { + return err + } + + C.ObjectSetAnyKey(o.ptr, key.ptr, value.ptr) + return nil +} + // Set will set a given index on the Object to a given value. // Supports all value types, eg: Object, Array, Date, Set, Map etc // If the value passed is a Go supported primitive (string, int32, uint32, int64, uint64, float64, big.Int) @@ -92,6 +106,12 @@ func (o *Object) Get(key string) (*Value, error) { return valueResult(o.ctx, rtn) } +// GetSymbol tries to get a Value for a given Object property key. +func (o *Object) GetSymbol(key *Symbol) (*Value, error) { + rtn := C.ObjectGetAnyKey(o.ptr, key.ptr) + return valueResult(o.ctx, rtn) +} + // GetIdx tries to get a Value at a give Object index. func (o *Object) GetIdx(idx uint32) (*Value, error) { rtn := C.ObjectGetIdx(o.ptr, C.uint32_t(idx)) @@ -106,6 +126,12 @@ func (o *Object) Has(key string) bool { return C.ObjectHas(o.ptr, ckey) != 0 } +// HasSymbol calls the abstract operation HasProperty(O, P) described in ECMA-262, 7.3.10. +// Returns true, if the object has the property, either own or on the prototype chain. +func (o *Object) HasSymbol(key *Symbol) bool { + return C.ObjectHasAnyKey(o.ptr, key.ptr) != 0 +} + // HasIdx returns true if the object has a value at the given index. func (o *Object) HasIdx(idx uint32) bool { return C.ObjectHasIdx(o.ptr, C.uint32_t(idx)) != 0 @@ -118,6 +144,11 @@ func (o *Object) Delete(key string) bool { return C.ObjectDelete(o.ptr, ckey) != 0 } +// DeleteSymbol returns true if successful in deleting a named property on the object. +func (o *Object) DeleteSymbol(key *Symbol) bool { + return C.ObjectDeleteAnyKey(o.ptr, key.ptr) != 0 +} + // DeleteIdx returns true if successful in deleting a value at a given index of the object. func (o *Object) DeleteIdx(idx uint32) bool { return C.ObjectDeleteIdx(o.ptr, C.uint32_t(idx)) != 0 diff --git a/object_template_test.go b/object_template_test.go index 46189afa8..633c6fb24 100644 --- a/object_template_test.go +++ b/object_template_test.go @@ -55,6 +55,23 @@ func TestObjectTemplate(t *testing.T) { } } +func TestObjectTemplateSetSymbol(t *testing.T) { + t.Parallel() + iso := v8.NewIsolate() + defer iso.Dispose() + obj := v8.NewObjectTemplate(iso) + + val, _ := v8.NewValue(iso, "bar") + objVal := v8.NewObjectTemplate(iso) + + if err := obj.SetSymbol(v8.SymbolIterator(iso), val); err != nil { + t.Errorf("failed to set property: %v", err) + } + if err := obj.SetSymbol(v8.SymbolIterator(iso), objVal); err != nil { + t.Errorf("failed to set template property: %v", err) + } +} + func TestObjectTemplate_panic_on_nil_isolate(t *testing.T) { t.Parallel() diff --git a/object_test.go b/object_test.go index ca515e731..4841a38c6 100644 --- a/object_test.go +++ b/object_test.go @@ -49,6 +49,8 @@ func TestObjectSet(t *testing.T) { ctx := v8.NewContext() defer ctx.Isolate().Dispose() defer ctx.Close() + + symIter := v8.SymbolIterator(ctx.Isolate()) val, _ := ctx.RunScript("const foo = {}; foo", "") obj, _ := val.AsObject() obj.Set("bar", "baz") @@ -62,6 +64,9 @@ func TestObjectSet(t *testing.T) { if err := obj.Set("a", 0); err == nil { t.Error("expected error but got ") } + if err := obj.SetSymbol(symIter, "sym"); err != nil { + t.Errorf("unexpected error: %v", err) + } if err := obj.SetIdx(10, "ten"); err != nil { t.Errorf("unexpected error: %v", err) } @@ -79,7 +84,11 @@ func TestObjectGet(t *testing.T) { ctx := v8.NewContext() defer ctx.Isolate().Dispose() defer ctx.Close() - val, _ := ctx.RunScript("const foo = { bar: 'baz'}; foo", "") + symIter := v8.SymbolIterator(ctx.Isolate()) + val, err := ctx.RunScript("const foo = { bar: 'baz', [Symbol.iterator]: 'gee'}; foo", "") + if err != nil { + t.Fatalf("RunScript failed: %v", err) + } obj, _ := val.AsObject() if bar, _ := obj.Get("bar"); bar.String() != "baz" { t.Errorf("unexpected value: %q", bar) @@ -87,6 +96,9 @@ func TestObjectGet(t *testing.T) { if baz, _ := obj.Get("baz"); !baz.IsUndefined() { t.Errorf("unexpected value: %q", baz) } + if got, _ := obj.GetSymbol(symIter); got.String() != "gee" { + t.Errorf("unexpected value: %q", got) + } ctx.RunScript("foo[5] = 5", "") if five, _ := obj.GetIdx(5); five.Integer() != 5 { t.Errorf("unexpected value: %q", five) @@ -102,7 +114,8 @@ func TestObjectHas(t *testing.T) { ctx := v8.NewContext() defer ctx.Isolate().Dispose() defer ctx.Close() - val, _ := ctx.RunScript("const foo = {a: 1, '2': 2}; foo", "") + symIter := v8.SymbolIterator(ctx.Isolate()) + val, _ := ctx.RunScript("const foo = {a: 1, '2': 2, [Symbol.iterator]: 3}; foo", "") obj, _ := val.AsObject() if !obj.Has("a") { t.Error("expected true, got false") @@ -110,6 +123,9 @@ func TestObjectHas(t *testing.T) { if obj.Has("c") { t.Error("expected false, got true") } + if !obj.HasSymbol(symIter) { + t.Error("expected true, got false") + } if !obj.HasIdx(2) { t.Error("expected true, got false") } @@ -124,7 +140,8 @@ func TestObjectDelete(t *testing.T) { ctx := v8.NewContext() defer ctx.Isolate().Dispose() defer ctx.Close() - val, _ := ctx.RunScript("const foo = { bar: 'baz', '2': 2}; foo", "") + symIter := v8.SymbolIterator(ctx.Isolate()) + val, _ := ctx.RunScript("const foo = { bar: 'baz', '2': 2, [Symbol.iterator]: 3}; foo", "") obj, _ := val.AsObject() if !obj.Has("bar") { t.Error("expected property to exist") @@ -135,6 +152,12 @@ func TestObjectDelete(t *testing.T) { if obj.Has("bar") { t.Error("expected property to be deleted") } + if !obj.DeleteSymbol(symIter) { + t.Error("expected delete to return true, got false") + } + if obj.HasSymbol(symIter) { + t.Error("expected property to be deleted") + } if !obj.DeleteIdx(2) { t.Error("expected delete to return true, got false") } diff --git a/symbol.go b/symbol.go new file mode 100644 index 000000000..825d0b81c --- /dev/null +++ b/symbol.go @@ -0,0 +1,60 @@ +// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package v8go + +import ( + "fmt" + "unsafe" + + // #include + // #include "v8go.h" + "C" +) + +// A Symbol represents a JavaScript symbol (ECMA-262 edition 6). +type Symbol struct { + *Value +} + +func SymbolAsyncIterator(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_ASYNC_ITERATOR) } +func SymbolHasInstance(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_HAS_INSTANCE) } +func SymbolIsConcatSpreadable(iso *Isolate) *Symbol { + return symbolByIndex(iso, C.SYMBOL_IS_CONCAT_SPREADABLE) +} +func SymbolIterator(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_ITERATOR) } +func SymbolMatch(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_MATCH) } +func SymbolReplace(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_REPLACE) } +func SymbolSearch(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_SEARCH) } +func SymbolSplit(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_SPLIT) } +func SymbolToPrimitive(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_TO_PRIMITIVE) } +func SymbolToStringTag(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_TO_STRING_TAG) } +func SymbolUnscopables(iso *Isolate) *Symbol { return symbolByIndex(iso, C.SYMBOL_UNSCOPABLES) } + +// symbolByIndex is a Go-to-C helper for obtaining builtin symbols. +func symbolByIndex(iso *Isolate, idx C.SymbolIndex) *Symbol { + val := C.BuiltinSymbol(iso.ptr, idx) + if val == nil { + panic(fmt.Errorf("unknown symbol index: %d", idx)) + } + return &Symbol{&Value{val, nil}} +} + +// Description returns the string representation of the symbol, +// e.g. "Symbol.asyncIterator". +func (sym *Symbol) Description() string { + s := C.SymbolDescription(sym.Value.ptr) + defer C.free(unsafe.Pointer(s)) + return C.GoString(s) +} + +// String returns Description(). +func (sym *Symbol) String() string { + return sym.Description() +} + +// value implements Valuer. +func (sym *Symbol) value() *Value { + return sym.Value +} diff --git a/symbol_test.go b/symbol_test.go new file mode 100644 index 000000000..5146ff3bd --- /dev/null +++ b/symbol_test.go @@ -0,0 +1,43 @@ +// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package v8go_test + +import ( + "testing" + + v8 "rogchap.com/v8go" +) + +func TestBuiltinSymbol(t *testing.T) { + t.Parallel() + + iso := v8.NewIsolate() + defer iso.Dispose() + + tsts := []struct { + Func func(*v8.Isolate) *v8.Symbol + WantDescription string + }{ + {v8.SymbolAsyncIterator, "Symbol.asyncIterator"}, + {v8.SymbolHasInstance, "Symbol.hasInstance"}, + {v8.SymbolIsConcatSpreadable, "Symbol.isConcatSpreadable"}, + {v8.SymbolIterator, "Symbol.iterator"}, + {v8.SymbolMatch, "Symbol.match"}, + {v8.SymbolReplace, "Symbol.replace"}, + {v8.SymbolSearch, "Symbol.search"}, + {v8.SymbolSplit, "Symbol.split"}, + {v8.SymbolToPrimitive, "Symbol.toPrimitive"}, + {v8.SymbolToStringTag, "Symbol.toStringTag"}, + {v8.SymbolUnscopables, "Symbol.unscopables"}, + } + for _, tst := range tsts { + t.Run(tst.WantDescription, func(t *testing.T) { + iter := tst.Func(iso) + if iter.Description() != tst.WantDescription { + t.Errorf("Description: got %q, want %q", iter.Description(), tst.WantDescription) + } + }) + } +} diff --git a/template.go b/template.go index 5a12a7ccd..9d4a5cb6a 100644 --- a/template.go +++ b/template.go @@ -59,6 +59,50 @@ func (t *template) Set(name string, val interface{}, attributes ...PropertyAttri return nil } +// SetSymbol adds a property to each instance created by this template. +// The property must be defined either as a primitive value, or a template. +// If the value passed is a Go supported primitive (string, int32, uint32, int64, uint64, float64, big.Int) +// then a value will be created and set as the value property. +func (t *template) SetSymbol(key *Symbol, val interface{}, attributes ...PropertyAttribute) error { + var attrs PropertyAttribute + for _, a := range attributes { + attrs |= a + } + + switch v := val.(type) { + case string, int32, uint32, int64, uint64, float64, bool, *big.Int: + newVal, err := NewValue(t.iso, v) + if err != nil { + return fmt.Errorf("v8go: unable to create new value: %v", err) + } + if C.TemplateSetAnyValue(t.ptr, key.ptr, newVal.ptr, C.int(attrs)) == 0 { + return fmt.Errorf("v8go: unable to set property for symbol %v", key) + } + case *ObjectTemplate: + if C.TemplateSetAnyTemplate(t.ptr, key.ptr, v.ptr, C.int(attrs)) == 0 { + return fmt.Errorf("v8go: unable to set property for symbol %v", key) + } + runtime.KeepAlive(v) + case *FunctionTemplate: + if C.TemplateSetAnyTemplate(t.ptr, key.ptr, v.ptr, C.int(attrs)) == 0 { + return fmt.Errorf("v8go: unable to set property for symbol %v", key) + } + runtime.KeepAlive(v) + case *Value: + if v.IsObject() || v.IsExternal() { + return errors.New("v8go: unsupported property: value type must be a primitive or use a template") + } + if C.TemplateSetAnyValue(t.ptr, key.ptr, v.ptr, C.int(attrs)) == 0 { + return fmt.Errorf("v8go: unable to set property for symbol %v", key) + } + default: + return fmt.Errorf("v8go: unsupported property type `%T`, must be one of string, int32, uint32, int64, uint64, float64, *big.Int, *v8go.Value, *v8go.ObjectTemplate or *v8go.FunctionTemplate", v) + } + runtime.KeepAlive(t) + + return nil +} + func (t *template) finalizer() { // Using v8::PersistentBase::Reset() wouldn't be thread-safe to do from // this finalizer goroutine so just free the wrapper and let the template diff --git a/v8go.cc b/v8go.cc index 144470fc2..e55778168 100644 --- a/v8go.cc +++ b/v8go.cc @@ -230,6 +230,20 @@ void TemplateSetValue(TemplatePtr ptr, tmpl->Set(prop_name, val->ptr.Get(iso), (PropertyAttribute)attributes); } +int TemplateSetAnyValue(TemplatePtr ptr, + ValuePtr key, + ValuePtr val, + int attributes) { + LOCAL_TEMPLATE(ptr); + + Local local_key = key->ptr.Get(iso); + if (!local_key->IsName()) { + return false; + } + tmpl->Set(local_key.As(), val->ptr.Get(iso), (PropertyAttribute)attributes); + return true; +} + void TemplateSetTemplate(TemplatePtr ptr, const char* name, TemplatePtr obj, @@ -241,6 +255,20 @@ void TemplateSetTemplate(TemplatePtr ptr, tmpl->Set(prop_name, obj->ptr.Get(iso), (PropertyAttribute)attributes); } +int TemplateSetAnyTemplate(TemplatePtr ptr, + ValuePtr key, + TemplatePtr obj, + int attributes) { + LOCAL_TEMPLATE(ptr); + + Local local_key = key->ptr.Get(iso); + if (!local_key->IsName()) { + return false; + } + tmpl->Set(Local::Cast(local_key), obj->ptr.Get(iso), (PropertyAttribute)attributes); + return true; +} + /********** ObjectTemplate **********/ TemplatePtr NewObjectTemplate(IsolatePtr iso) { @@ -1043,6 +1071,12 @@ void ObjectSet(ValuePtr ptr, const char* key, ValuePtr prop_val) { obj->Set(local_ctx, key_val, prop_val->ptr.Get(iso)).Check(); } +void ObjectSetAnyKey(ValuePtr ptr, ValuePtr key, ValuePtr prop_val) { + LOCAL_OBJECT(ptr); + Local local_key = key->ptr.Get(iso); + obj->Set(local_ctx, local_key, prop_val->ptr.Get(iso)).Check(); +} + void ObjectSetIdx(ValuePtr ptr, uint32_t idx, ValuePtr prop_val) { LOCAL_OBJECT(ptr); obj->Set(local_ctx, idx, prop_val->ptr.Get(iso)).Check(); @@ -1073,6 +1107,26 @@ RtnValue ObjectGet(ValuePtr ptr, const char* key) { return rtn; } +RtnValue ObjectGetAnyKey(ValuePtr ptr, ValuePtr key) { + LOCAL_OBJECT(ptr); + RtnValue rtn = {nullptr, nullptr}; + + Local local_key = key->ptr.Get(iso); + Local result; + if (!obj->Get(local_ctx, local_key).ToLocal(&result)) { + rtn.error = ExceptionError(try_catch, iso, local_ctx); + return rtn; + } + m_value* new_val = new m_value; + new_val->iso = iso; + new_val->ctx = ctx; + new_val->ptr = + Persistent>(iso, result); + + rtn.value = tracked_value(ctx, new_val); + return rtn; +} + RtnValue ObjectGetIdx(ValuePtr ptr, uint32_t idx) { LOCAL_OBJECT(ptr); RtnValue rtn = {nullptr, nullptr}; @@ -1099,6 +1153,12 @@ int ObjectHas(ValuePtr ptr, const char* key) { return obj->Has(local_ctx, key_val).ToChecked(); } +int ObjectHasAnyKey(ValuePtr ptr, ValuePtr key) { + LOCAL_OBJECT(ptr); + Local local_key = key->ptr.Get(iso); + return obj->Has(local_ctx, local_key).ToChecked(); +} + int ObjectHasIdx(ValuePtr ptr, uint32_t idx) { LOCAL_OBJECT(ptr); return obj->Has(local_ctx, idx).ToChecked(); @@ -1111,11 +1171,75 @@ int ObjectDelete(ValuePtr ptr, const char* key) { return obj->Delete(local_ctx, key_val).ToChecked(); } +int ObjectDeleteAnyKey(ValuePtr ptr, ValuePtr key) { + LOCAL_OBJECT(ptr); + Local local_key = key->ptr.Get(iso); + return obj->Delete(local_ctx, local_key).ToChecked(); +} + int ObjectDeleteIdx(ValuePtr ptr, uint32_t idx) { LOCAL_OBJECT(ptr); return obj->Delete(local_ctx, idx).ToChecked(); } +/********** Symbol **********/ + +ValuePtr BuiltinSymbol(IsolatePtr iso, SymbolIndex idx) { + ISOLATE_SCOPE_INTERNAL_CONTEXT(iso); + Local sym; + switch (idx) { + case SYMBOL_ASYNC_ITERATOR: + sym = Symbol::GetAsyncIterator(iso); + break; + case SYMBOL_HAS_INSTANCE: + sym = Symbol::GetHasInstance(iso); + break; + case SYMBOL_IS_CONCAT_SPREADABLE: + sym = Symbol::GetIsConcatSpreadable(iso); + break; + case SYMBOL_ITERATOR: + sym = Symbol::GetIterator(iso); + break; + case SYMBOL_MATCH: + sym = Symbol::GetMatch(iso); + break; + case SYMBOL_REPLACE: + sym = Symbol::GetReplace(iso); + break; + case SYMBOL_SEARCH: + sym = Symbol::GetSearch(iso); + break; + case SYMBOL_SPLIT: + sym = Symbol::GetSplit(iso); + break; + case SYMBOL_TO_PRIMITIVE: + sym = Symbol::GetToPrimitive(iso); + break; + case SYMBOL_TO_STRING_TAG: + sym = Symbol::GetToStringTag(iso); + break; + case SYMBOL_UNSCOPABLES: + sym = Symbol::GetUnscopables(iso); + break; + default: + return nullptr; + } + m_value* val = new m_value; + val->iso = iso; + val->ctx = ctx; + val->ptr = Persistent>(iso, sym); + return tracked_value(ctx, val); +} + +const char* SymbolDescription(ValuePtr ptr) { + LOCAL_VALUE(ptr); + Local sym = value.As(); + Local descr = sym->Description(); + String::Utf8Value utf8(iso, descr); + return CopyString(utf8); + +} + /********** Promise **********/ RtnValue NewPromiseResolver(ContextPtr ctx) { diff --git a/v8go.h b/v8go.h index 339f424b2..704796cda 100644 --- a/v8go.h +++ b/v8go.h @@ -30,6 +30,20 @@ typedef m_ctx* ContextPtr; typedef m_value* ValuePtr; typedef m_template* TemplatePtr; +typedef enum { + SYMBOL_ASYNC_ITERATOR = 1, + SYMBOL_HAS_INSTANCE, + SYMBOL_IS_CONCAT_SPREADABLE, + SYMBOL_ITERATOR, + SYMBOL_MATCH, + SYMBOL_REPLACE, + SYMBOL_SEARCH, + SYMBOL_SPLIT, + SYMBOL_TO_PRIMITIVE, + SYMBOL_TO_STRING_TAG, + SYMBOL_UNSCOPABLES, +} SymbolIndex; + typedef struct { const char* msg; const char* location; @@ -90,10 +104,18 @@ extern void TemplateSetValue(TemplatePtr ptr, const char* name, ValuePtr val_ptr, int attributes); +extern int TemplateSetAnyValue(TemplatePtr ptr, + ValuePtr key, + ValuePtr val_ptr, + int attributes); extern void TemplateSetTemplate(TemplatePtr ptr, const char* name, TemplatePtr obj_ptr, int attributes); +extern int TemplateSetAnyTemplate(TemplatePtr ptr, + ValuePtr key, + TemplatePtr obj_ptr, + int attributes); extern TemplatePtr NewObjectTemplate(IsolatePtr iso_ptr); extern RtnValue ObjectTemplateNewInstance(TemplatePtr ptr, ContextPtr ctx_ptr); @@ -181,15 +203,22 @@ int ValueIsProxy(ValuePtr ptr); int ValueIsWasmModuleObject(ValuePtr ptr); int ValueIsModuleNamespaceObject(ValuePtr ptr); -extern void ObjectSet(ValuePtr ptr, const char* key, ValuePtr val_ptr); -extern void ObjectSetIdx(ValuePtr ptr, uint32_t idx, ValuePtr val_ptr); -extern RtnValue ObjectGet(ValuePtr ptr, const char* key); -extern RtnValue ObjectGetIdx(ValuePtr ptr, uint32_t idx); +void ObjectSet(ValuePtr ptr, const char* key, ValuePtr val_ptr); +void ObjectSetAnyKey(ValuePtr ptr, ValuePtr key, ValuePtr val_ptr); +void ObjectSetIdx(ValuePtr ptr, uint32_t idx, ValuePtr val_ptr); +RtnValue ObjectGet(ValuePtr ptr, const char* key); +RtnValue ObjectGetAnyKey(ValuePtr ptr, ValuePtr key); +RtnValue ObjectGetIdx(ValuePtr ptr, uint32_t idx); int ObjectHas(ValuePtr ptr, const char* key); +int ObjectHasAnyKey(ValuePtr ptr, ValuePtr key); int ObjectHasIdx(ValuePtr ptr, uint32_t idx); int ObjectDelete(ValuePtr ptr, const char* key); +int ObjectDeleteAnyKey(ValuePtr ptr, ValuePtr key); int ObjectDeleteIdx(ValuePtr ptr, uint32_t idx); +ValuePtr BuiltinSymbol(IsolatePtr iso_ptr, SymbolIndex idx); +const char* SymbolDescription(ValuePtr ptr); + extern RtnValue NewPromiseResolver(ContextPtr ctx_ptr); extern ValuePtr PromiseResolverGetPromise(ValuePtr ptr); int PromiseResolverResolve(ValuePtr ptr, ValuePtr val_ptr); diff --git a/value.go b/value.go index dcfc0ab11..f05d7297a 100644 --- a/value.go +++ b/value.go @@ -555,6 +555,15 @@ func (v *Value) AsObject() (*Object, error) { return &Object{v}, nil } +// AsSymbol will cast the value to the Symbol type. If the value is not a Symbol +// then an error is returned. +func (v *Value) AsSymbol() (*Symbol, error) { + if !v.IsSymbol() { + return nil, errors.New("v8go: value is not a Symbol") + } + return &Symbol{v}, nil +} + func (v *Value) AsPromise() (*Promise, error) { if !v.IsPromise() { return nil, errors.New("v8go: value is not a Promise") diff --git a/value_test.go b/value_test.go index 4b2c019c6..b67e04ae7 100644 --- a/value_test.go +++ b/value_test.go @@ -435,6 +435,32 @@ func TestValueObject(t *testing.T) { } } +func TestValueAsSymbol(t *testing.T) { + t.Parallel() + + ctx := v8.NewContext() + defer ctx.Isolate().Dispose() + defer ctx.Close() + + t.Run("valid", func(t *testing.T) { + val, _ := ctx.RunScript("Symbol.iterator", "") + got, err := val.AsSymbol() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if want := "Symbol.iterator"; got.Description() != want { + t.Errorf("Description: expected %q, but got %q", want, got.Description()) + } + }) + + t.Run("invalid", func(t *testing.T) { + val, _ := ctx.RunScript("1", "") + if _, err := val.AsSymbol(); err == nil { + t.Error("Expected error but got ") + } + }) +} + func TestValuePromise(t *testing.T) { t.Parallel()