Explorar el Código

Added support for go arrays (both to ToValue and ExportTo). Nom-addressable structs and arrays are copied in ToValue() so they remain writable in ES. Subsequent Export() returns the value including any changes made.

Dmitry Panov hace 3 años
padre
commit
dd567e70ae

+ 8 - 4
array.go

@@ -507,12 +507,16 @@ func (a *arrayObject) exportType() reflect.Type {
 	return reflectTypeArray
 	return reflectTypeArray
 }
 }
 
 
-func (a *arrayObject) exportToSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
+func (a *arrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 	r := a.val.runtime
 	r := a.val.runtime
 	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
 	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
 		l := toIntStrict(int64(a.length))
 		l := toIntStrict(int64(a.length))
-		if dst.IsNil() || dst.Len() != l {
-			dst.Set(reflect.MakeSlice(typ, l, l))
+		if dst.Len() != l {
+			if typ.Kind() == reflect.Array {
+				return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
+			} else {
+				dst.Set(reflect.MakeSlice(typ, l, l))
+			}
 		}
 		}
 		ctx.putTyped(a.val, typ, dst.Interface())
 		ctx.putTyped(a.val, typ, dst.Interface())
 		for i := 0; i < l; i++ {
 		for i := 0; i < l; i++ {
@@ -530,7 +534,7 @@ func (a *arrayObject) exportToSlice(dst reflect.Value, typ reflect.Type, ctx *ob
 		}
 		}
 		return nil
 		return nil
 	}
 	}
-	return a.baseObject.exportToSlice(dst, typ, ctx)
+	return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
 }
 }
 
 
 func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem, newMaxIdx int) {
 func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem, newMaxIdx int) {

+ 8 - 4
array_sparse.go

@@ -459,12 +459,16 @@ func (a *sparseArrayObject) exportType() reflect.Type {
 	return reflectTypeArray
 	return reflectTypeArray
 }
 }
 
 
-func (a *sparseArrayObject) exportToSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
+func (a *sparseArrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 	r := a.val.runtime
 	r := a.val.runtime
 	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
 	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
 		l := toIntStrict(int64(a.length))
 		l := toIntStrict(int64(a.length))
-		if dst.IsNil() || dst.Len() != l {
-			dst.Set(reflect.MakeSlice(typ, l, l))
+		if dst.Len() != l {
+			if typ.Kind() == reflect.Array {
+				return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
+			} else {
+				dst.Set(reflect.MakeSlice(typ, l, l))
+			}
 		}
 		}
 		ctx.putTyped(a.val, typ, dst.Interface())
 		ctx.putTyped(a.val, typ, dst.Interface())
 		for _, item := range a.items {
 		for _, item := range a.items {
@@ -483,5 +487,5 @@ func (a *sparseArrayObject) exportToSlice(dst reflect.Value, typ reflect.Type, c
 		}
 		}
 		return nil
 		return nil
 	}
 	}
-	return a.baseObject.exportToSlice(dst, typ, ctx)
+	return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
 }
 }

+ 17 - 0
builtin_map_test.go

@@ -109,6 +109,23 @@ func ExampleRuntime_ExportTo_mapToSlice() {
 	// Output: [[1 true] [2 false]]
 	// Output: [[1 true] [2 false]]
 }
 }
 
 
+func ExampleRuntime_ExportTo_mapToTypedSlice() {
+	vm := New()
+	m, err := vm.RunString(`
+	new Map([[1, true], [2, false]]);
+	`)
+	if err != nil {
+		panic(err)
+	}
+	exp := make([][2]interface{}, 0)
+	err = vm.ExportTo(m, &exp)
+	if err != nil {
+		panic(err)
+	}
+	fmt.Println(exp)
+	// Output: [[1 true] [2 false]]
+}
+
 func BenchmarkMapDelete(b *testing.B) {
 func BenchmarkMapDelete(b *testing.B) {
 	var key1 Value = asciiString("a")
 	var key1 Value = asciiString("a")
 	var key2 Value = asciiString("b")
 	var key2 Value = asciiString("b")

+ 11 - 4
builtin_set.go

@@ -1,6 +1,9 @@
 package goja
 package goja
 
 
-import "reflect"
+import (
+	"fmt"
+	"reflect"
+)
 
 
 var setExportType = reflectTypeArray
 var setExportType = reflectTypeArray
 
 
@@ -60,10 +63,14 @@ func (so *setObject) export(ctx *objectExportCtx) interface{} {
 	return a
 	return a
 }
 }
 
 
-func (so *setObject) exportToSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
+func (so *setObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 	l := so.m.size
 	l := so.m.size
-	if dst.IsNil() || dst.Len() != l {
-		dst.Set(reflect.MakeSlice(typ, l, l))
+	if dst.Len() != l {
+		if typ.Kind() == reflect.Array {
+			return fmt.Errorf("cannot convert a Set into an array, lengths mismatch: have %d, need %d)", l, dst.Len())
+		} else {
+			dst.Set(reflect.MakeSlice(typ, l, l))
+		}
 	}
 	}
 	ctx.putTyped(so.val, typ, dst.Interface())
 	ctx.putTyped(so.val, typ, dst.Interface())
 	iter := so.m.newIter()
 	iter := so.m.newIter()

+ 19 - 0
builtin_set_test.go

@@ -2,6 +2,7 @@ package goja
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"strings"
 	"testing"
 	"testing"
 )
 )
 
 
@@ -81,3 +82,21 @@ func TestSetExportToSliceCircular(t *testing.T) {
 		t.Fatalf("a: %v", a)
 		t.Fatalf("a: %v", a)
 	}
 	}
 }
 }
+
+func TestSetExportToArrayMismatchedLengths(t *testing.T) {
+	vm := New()
+	s, err := vm.RunString(`
+	new Set([1, 2])
+	`)
+	if err != nil {
+		panic(err)
+	}
+	var s1 [3]int
+	err = vm.ExportTo(s, &s1)
+	if err == nil {
+		t.Fatal("expected error")
+	}
+	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}

+ 2 - 2
destruct.go

@@ -245,8 +245,8 @@ func (d *destructKeyedSource) exportToMap(dst reflect.Value, typ reflect.Type, c
 	return d.w().exportToMap(dst, typ, ctx)
 	return d.w().exportToMap(dst, typ, ctx)
 }
 }
 
 
-func (d *destructKeyedSource) exportToSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
-	return d.w().exportToSlice(dst, typ, ctx)
+func (d *destructKeyedSource) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
+	return d.w().exportToArrayOrSlice(dst, typ, ctx)
 }
 }
 
 
 func (d *destructKeyedSource) equal(impl objectImpl) bool {
 func (d *destructKeyedSource) equal(impl objectImpl) bool {

+ 16 - 8
object.go

@@ -194,7 +194,7 @@ type objectImpl interface {
 	export(ctx *objectExportCtx) interface{}
 	export(ctx *objectExportCtx) interface{}
 	exportType() reflect.Type
 	exportType() reflect.Type
 	exportToMap(m reflect.Value, typ reflect.Type, ctx *objectExportCtx) error
 	exportToMap(m reflect.Value, typ reflect.Type, ctx *objectExportCtx) error
-	exportToSlice(s reflect.Value, typ reflect.Type, ctx *objectExportCtx) error
+	exportToArrayOrSlice(s reflect.Value, typ reflect.Type, ctx *objectExportCtx) error
 	equal(objectImpl) bool
 	equal(objectImpl) bool
 
 
 	iterateStringKeys() iterNextFunc
 	iterateStringKeys() iterNextFunc
@@ -1014,7 +1014,7 @@ func (o *baseObject) exportToMap(m reflect.Value, typ reflect.Type, ctx *objectE
 	return genericExportToMap(o.val, m, typ, ctx)
 	return genericExportToMap(o.val, m, typ, ctx)
 }
 }
 
 
-func genericExportToSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) (err error) {
+func genericExportToArrayOrSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) (err error) {
 	r := o.runtime
 	r := o.runtime
 
 
 	if method := toMethod(r.getV(o, SymIterator)); method != nil {
 	if method := toMethod(r.getV(o, SymIterator)); method != nil {
@@ -1028,8 +1028,12 @@ func genericExportToSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *o
 		if ex != nil {
 		if ex != nil {
 			return ex
 			return ex
 		}
 		}
-		if dst.IsNil() || dst.Len() != len(values) {
-			dst.Set(reflect.MakeSlice(typ, len(values), len(values)))
+		if dst.Len() != len(values) {
+			if typ.Kind() == reflect.Array {
+				return fmt.Errorf("cannot convert an iterable into an array, lengths mismatch (have %d, need %d)", len(values), dst.Len())
+			} else {
+				dst.Set(reflect.MakeSlice(typ, len(values), len(values)))
+			}
 		}
 		}
 		ctx.putTyped(o, typ, dst.Interface())
 		ctx.putTyped(o, typ, dst.Interface())
 		for i, val := range values {
 		for i, val := range values {
@@ -1041,8 +1045,12 @@ func genericExportToSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *o
 	} else {
 	} else {
 		// array-like
 		// array-like
 		l := toIntStrict(toLength(o.self.getStr("length", nil)))
 		l := toIntStrict(toLength(o.self.getStr("length", nil)))
-		if dst.IsNil() || dst.Len() != l {
-			dst.Set(reflect.MakeSlice(typ, l, l))
+		if dst.Len() != l {
+			if typ.Kind() == reflect.Array {
+				return fmt.Errorf("cannot convert an array-like object into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
+			} else {
+				dst.Set(reflect.MakeSlice(typ, l, l))
+			}
 		}
 		}
 		ctx.putTyped(o, typ, dst.Interface())
 		ctx.putTyped(o, typ, dst.Interface())
 		for i := 0; i < l; i++ {
 		for i := 0; i < l; i++ {
@@ -1057,8 +1065,8 @@ func genericExportToSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *o
 	return
 	return
 }
 }
 
 
-func (o *baseObject) exportToSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
-	return genericExportToSlice(o.val, dst, typ, ctx)
+func (o *baseObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
+	return genericExportToArrayOrSlice(o.val, dst, typ, ctx)
 }
 }
 
 
 type enumerableFlag int
 type enumerableFlag int

+ 2 - 2
object_dynamic.go

@@ -487,8 +487,8 @@ func (o *baseDynamicObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx
 	return genericExportToMap(o.val, dst, typ, ctx)
 	return genericExportToMap(o.val, dst, typ, ctx)
 }
 }
 
 
-func (o *baseDynamicObject) exportToSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
-	return genericExportToSlice(o.val, dst, typ, ctx)
+func (o *baseDynamicObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
+	return genericExportToArrayOrSlice(o.val, dst, typ, ctx)
 }
 }
 
 
 func (o *dynamicObject) equal(impl objectImpl) bool {
 func (o *dynamicObject) equal(impl objectImpl) bool {

+ 288 - 0
object_goarray_reflect.go

@@ -0,0 +1,288 @@
+package goja
+
+import (
+	"reflect"
+	"strconv"
+
+	"github.com/dop251/goja/unistring"
+)
+
+type objectGoArrayReflect struct {
+	objectGoReflect
+	lengthProp valueProperty
+
+	putIdx func(idx int, v Value, throw bool) bool
+}
+
+func (o *objectGoArrayReflect) _init() {
+	o.objectGoReflect.init()
+	o.class = classArray
+	o.prototype = o.val.runtime.global.ArrayPrototype
+	o.updateLen()
+	o.baseObject._put("length", &o.lengthProp)
+}
+
+func (o *objectGoArrayReflect) init() {
+	o._init()
+	o.putIdx = o._putIdx
+}
+
+func (o *objectGoArrayReflect) updateLen() {
+	o.lengthProp.value = intToValue(int64(o.value.Len()))
+}
+
+func (o *objectGoArrayReflect) _hasIdx(idx valueInt) bool {
+	if idx := int64(idx); idx >= 0 && idx < int64(o.value.Len()) {
+		return true
+	}
+	return false
+}
+
+func (o *objectGoArrayReflect) _hasStr(name unistring.String) bool {
+	if idx := strToIdx64(name); idx >= 0 && idx < int64(o.value.Len()) {
+		return true
+	}
+	return false
+}
+
+func (o *objectGoArrayReflect) _getIdx(idx int) Value {
+	v := o.value.Index(idx)
+	if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
+		return _null
+	}
+	return o.val.runtime.ToValue(v.Interface())
+}
+
+func (o *objectGoArrayReflect) getIdx(idx valueInt, receiver Value) Value {
+	if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.value.Len() {
+		return o._getIdx(idx)
+	}
+	return o.objectGoReflect.getStr(idx.string(), receiver)
+}
+
+func (o *objectGoArrayReflect) getStr(name unistring.String, receiver Value) Value {
+	var ownProp Value
+	if idx := strToGoIdx(name); idx >= 0 && idx < o.value.Len() {
+		ownProp = o._getIdx(idx)
+	} else if name == "length" {
+		ownProp = &o.lengthProp
+	} else {
+		ownProp = o.objectGoReflect.getOwnPropStr(name)
+	}
+	return o.getStrWithOwnProp(ownProp, name, receiver)
+}
+
+func (o *objectGoArrayReflect) getOwnPropStr(name unistring.String) Value {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if idx < o.value.Len() {
+			return &valueProperty{
+				value:      o._getIdx(idx),
+				writable:   true,
+				enumerable: true,
+			}
+		}
+		return nil
+	}
+	if name == "length" {
+		return &o.lengthProp
+	}
+	return o.objectGoReflect.getOwnPropStr(name)
+}
+
+func (o *objectGoArrayReflect) getOwnPropIdx(idx valueInt) Value {
+	if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.value.Len() {
+		return &valueProperty{
+			value:      o._getIdx(idx),
+			writable:   true,
+			enumerable: true,
+		}
+	}
+	return nil
+}
+
+func (o *objectGoArrayReflect) _putIdx(idx int, v Value, throw bool) bool {
+	err := o.val.runtime.toReflectValue(v, o.value.Index(idx), &objectExportCtx{})
+	if err != nil {
+		o.val.runtime.typeErrorResult(throw, "Go type conversion error: %v", err)
+		return false
+	}
+	return true
+}
+
+func (o *objectGoArrayReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
+	if i := toIntStrict(int64(idx)); i >= 0 {
+		if i >= o.value.Len() {
+			if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
+				return res
+			}
+		}
+		return o.putIdx(i, val, throw)
+	} else {
+		name := idx.string()
+		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
+			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
+			return false
+		} else {
+			return res
+		}
+	}
+}
+
+func (o *objectGoArrayReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if idx >= o.value.Len() {
+			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
+				return res
+			}
+		}
+		return o.putIdx(idx, val, throw)
+	} else {
+		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
+			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
+			return false
+		} else {
+			return res
+		}
+	}
+}
+
+func (o *objectGoArrayReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw)
+}
+
+func (o *objectGoArrayReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
+}
+
+func (o *objectGoArrayReflect) hasOwnPropertyIdx(idx valueInt) bool {
+	return o._hasIdx(idx)
+}
+
+func (o *objectGoArrayReflect) hasOwnPropertyStr(name unistring.String) bool {
+	if o._hasStr(name) || name == "length" {
+		return true
+	}
+	return o.objectGoReflect._has(name.String())
+}
+
+func (o *objectGoArrayReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	if i := toIntStrict(int64(idx)); i >= 0 {
+		if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
+			return false
+		}
+		val := descr.Value
+		if val == nil {
+			val = _undefined
+		}
+		return o.putIdx(i, val, throw)
+	}
+	o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
+	return false
+}
+
+func (o *objectGoArrayReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
+			return false
+		}
+		val := descr.Value
+		if val == nil {
+			val = _undefined
+		}
+		return o.putIdx(idx, val, throw)
+	}
+	o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
+	return false
+}
+
+func (o *objectGoArrayReflect) toPrimitiveNumber() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoArrayReflect) toPrimitiveString() Value {
+	return o.val.runtime.arrayproto_join(FunctionCall{
+		This: o.val,
+	})
+}
+
+func (o *objectGoArrayReflect) toPrimitive() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoArrayReflect) _deleteIdx(idx int) {
+	if idx < o.value.Len() {
+		o.value.Index(idx).Set(reflect.Zero(o.value.Type().Elem()))
+	}
+}
+
+func (o *objectGoArrayReflect) deleteStr(name unistring.String, throw bool) bool {
+	if idx := strToGoIdx(name); idx >= 0 {
+		o._deleteIdx(idx)
+		return true
+	}
+
+	return o.objectGoReflect.deleteStr(name, throw)
+}
+
+func (o *objectGoArrayReflect) deleteIdx(i valueInt, throw bool) bool {
+	idx := toIntStrict(int64(i))
+	if idx >= 0 {
+		o._deleteIdx(idx)
+	}
+	return true
+}
+
+type goArrayReflectPropIter struct {
+	o          *objectGoArrayReflect
+	idx, limit int
+}
+
+func (i *goArrayReflectPropIter) next() (propIterItem, iterNextFunc) {
+	if i.idx < i.limit && i.idx < i.o.value.Len() {
+		name := strconv.Itoa(i.idx)
+		i.idx++
+		return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
+	}
+
+	return i.o.objectGoReflect.iterateStringKeys()()
+}
+
+func (o *objectGoArrayReflect) stringKeys(all bool, accum []Value) []Value {
+	for i := 0; i < o.value.Len(); i++ {
+		accum = append(accum, asciiString(strconv.Itoa(i)))
+	}
+
+	return o.objectGoReflect.stringKeys(all, accum)
+}
+
+func (o *objectGoArrayReflect) iterateStringKeys() iterNextFunc {
+	return (&goArrayReflectPropIter{
+		o:     o,
+		limit: o.value.Len(),
+	}).next
+}
+
+func (o *objectGoArrayReflect) equal(other objectImpl) bool {
+	if other, ok := other.(*objectGoArrayReflect); ok {
+		return o.value.Interface() == other.value.Interface()
+	}
+	return false
+}
+
+func (o *objectGoArrayReflect) sortLen() int64 {
+	return int64(o.value.Len())
+}
+
+func (o *objectGoArrayReflect) sortGet(i int64) Value {
+	return o.getIdx(valueInt(i), nil)
+}
+
+func (o *objectGoArrayReflect) swap(i, j int64) {
+	ii := toIntStrict(i)
+	jj := toIntStrict(j)
+	x := o._getIdx(ii)
+	y := o._getIdx(jj)
+
+	o._putIdx(ii, y, false)
+	o._putIdx(jj, x, false)
+}

+ 49 - 0
object_goarray_reflect_test.go

@@ -0,0 +1,49 @@
+package goja
+
+import "testing"
+
+func TestGoReflectArray(t *testing.T) {
+	vm := New()
+	vm.Set("a", [...]int{1, 2, 3})
+	_, err := vm.RunString(`
+	if (!Array.isArray(a)) {
+		throw new Error("isArray() returned false");
+	}
+	if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
+		throw new Error("Array contents is incorrect");
+	}
+	if (!a.hasOwnProperty("length")) {
+		throw new Error("hasOwnProperty() returned false");
+	}
+	let desc = Object.getOwnPropertyDescriptor(a, "length");
+	if (desc.value !== 3 || desc.writable || desc.enumerable || desc.configurable) {
+		throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
+	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoReflectArraySort(t *testing.T) {
+	vm := New()
+	vm.Set("a", [...]int{3, 1, 2})
+	v, err := vm.RunString(`
+		a.sort();
+		if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
+			throw new Error(a.toString());
+		}
+		a;
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	res := v.Export()
+	if a, ok := res.([3]int); ok {
+		if a[0] != 1 || a[1] != 2 || a[2] != 3 {
+			t.Fatal(a)
+		}
+	} else {
+		t.Fatalf("Wrong type: %T", res)
+	}
+}

+ 6 - 0
object_goreflect.go

@@ -103,6 +103,12 @@ func (o *objectGoReflect) init() {
 	default:
 	default:
 		o.class = classObject
 		o.class = classObject
 		o.prototype = o.val.runtime.global.ObjectPrototype
 		o.prototype = o.val.runtime.global.ObjectPrototype
+		if !o.value.CanAddr() {
+			value := reflect.Indirect(reflect.New(o.value.Type()))
+			value.Set(o.value)
+			o.origValue = value
+			o.value = value
+		}
 	}
 	}
 	o.extensible = true
 	o.extensible = true
 
 

+ 25 - 29
object_goreflect_test.go

@@ -636,25 +636,17 @@ func TestStructNonAddressable(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
 	"use strict";
 	"use strict";
 	
 	
-	if (Object.getOwnPropertyDescriptor(s, "Field").writable) {
-		throw new Error("Field is writable");
+	if (!Object.getOwnPropertyDescriptor(s, "Field").writable) {
+		throw new Error("s.Field is non-writable");
 	}
 	}
 
 
 	if (!Object.getOwnPropertyDescriptor(s1, "Field").writable) {
 	if (!Object.getOwnPropertyDescriptor(s1, "Field").writable) {
-		throw new Error("Field is non-writable");
+		throw new Error("s1.Field is non-writable");
 	}
 	}
 
 
 	s1.Field = 42;
 	s1.Field = 42;
-
-	var result;
-	try {
-		s.Field = 42;
-		result = false;
-	} catch (e) {
-		result = e instanceof TypeError;
-	}
-	
-	result;
+	s.Field = 43;
+	s;
 `
 `
 
 
 	var s S
 	var s S
@@ -665,8 +657,13 @@ func TestStructNonAddressable(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	if !v.StrictEquals(valueTrue) {
-		t.Fatalf("Unexpected result: %v", v)
+	exp := v.Export()
+	if s1, ok := exp.(S); ok {
+		if s1.Field != 43 {
+			t.Fatal(s1)
+		}
+	} else {
+		t.Fatalf("Wrong type: %T", exp)
 	}
 	}
 	if s.Field != 42 {
 	if s.Field != 42 {
 		t.Fatalf("Unexpected s.Field value: %d", s.Field)
 		t.Fatalf("Unexpected s.Field value: %d", s.Field)
@@ -877,20 +874,11 @@ func TestNestedStructSet(t *testing.T) {
 		throw new Error("a1.B.Field = " + a1.B.Field);
 		throw new Error("a1.B.Field = " + a1.B.Field);
 	}
 	}
 	var d = Object.getOwnPropertyDescriptor(a1.B, "Field");
 	var d = Object.getOwnPropertyDescriptor(a1.B, "Field");
-	if (d.writable) {
-		throw new Error("a1.B is writable");
-	}
-	var thrown = false;
-	try {
-		a1.B.Field = 42;
-	} catch (e) {
-		if (e instanceof TypeError) {
-			thrown = true;
-		}
-	}
-	if (!thrown) {
-		throw new Error("TypeError was not thrown");
+	if (!d.writable) {
+		throw new Error("a1.B is not writable");
 	}
 	}
+	a1.B.Field = 42;
+	a1;
 	`
 	`
 	a := A{
 	a := A{
 		B: B{
 		B: B{
@@ -900,10 +888,18 @@ func TestNestedStructSet(t *testing.T) {
 	vm := New()
 	vm := New()
 	vm.Set("a", &a)
 	vm.Set("a", &a)
 	vm.Set("a1", a)
 	vm.Set("a1", a)
-	_, err := vm.RunString(SCRIPT)
+	v, err := vm.RunString(SCRIPT)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
+	exp := v.Export()
+	if v, ok := exp.(A); ok {
+		if v.B.Field != 42 {
+			t.Fatal(v)
+		}
+	} else {
+		t.Fatalf("Wrong type: %T", exp)
+	}
 
 
 	if v := a.B.Field; v != 2 {
 	if v := a.B.Field; v != 2 {
 		t.Fatalf("Unexpected a.B.Field: %d", v)
 		t.Fatalf("Unexpected a.B.Field: %d", v)

+ 9 - 264
object_goslice_reflect.go

@@ -4,113 +4,25 @@ import (
 	"math"
 	"math"
 	"math/bits"
 	"math/bits"
 	"reflect"
 	"reflect"
-	"strconv"
 
 
 	"github.com/dop251/goja/unistring"
 	"github.com/dop251/goja/unistring"
 )
 )
 
 
 type objectGoSliceReflect struct {
 type objectGoSliceReflect struct {
-	objectGoReflect
-	lengthProp valueProperty
+	objectGoArrayReflect
 }
 }
 
 
 func (o *objectGoSliceReflect) init() {
 func (o *objectGoSliceReflect) init() {
-	o.objectGoReflect.init()
-	o.class = classArray
-	o.prototype = o.val.runtime.global.ArrayPrototype
-	if !o.value.CanSet() {
-		value := reflect.Indirect(reflect.New(o.value.Type()))
-		value.Set(o.value)
-		o.value = value
-	}
+	o.objectGoArrayReflect._init()
 	o.lengthProp.writable = true
 	o.lengthProp.writable = true
-	o.updateLen()
-	o.baseObject._put("length", &o.lengthProp)
-}
-
-func (o *objectGoSliceReflect) updateLen() {
-	o.lengthProp.value = intToValue(int64(o.value.Len()))
-}
-
-func (o *objectGoSliceReflect) _hasIdx(idx valueInt) bool {
-	if idx := int64(idx); idx >= 0 && idx < int64(o.value.Len()) {
-		return true
-	}
-	return false
-}
-
-func (o *objectGoSliceReflect) _hasStr(name unistring.String) bool {
-	if idx := strToIdx64(name); idx >= 0 && idx < int64(o.value.Len()) {
-		return true
-	}
-	return false
-}
-
-func (o *objectGoSliceReflect) _getIdx(idx int) Value {
-	v := o.value.Index(idx)
-	if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
-		return _null
-	}
-	return o.val.runtime.ToValue(v.Interface())
+	o.putIdx = o._putIdx
 }
 }
 
 
-func (o *objectGoSliceReflect) getIdx(idx valueInt, receiver Value) Value {
-	if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.value.Len() {
-		return o._getIdx(idx)
-	}
-	return o.objectGoReflect.getStr(idx.string(), receiver)
-}
-
-func (o *objectGoSliceReflect) getStr(name unistring.String, receiver Value) Value {
-	var ownProp Value
-	if idx := strToGoIdx(name); idx >= 0 && idx < o.value.Len() {
-		ownProp = o._getIdx(idx)
-	} else if name == "length" {
-		ownProp = &o.lengthProp
-	} else {
-		ownProp = o.objectGoReflect.getOwnPropStr(name)
-	}
-	return o.getStrWithOwnProp(ownProp, name, receiver)
-}
-
-func (o *objectGoSliceReflect) getOwnPropStr(name unistring.String) Value {
-	if idx := strToGoIdx(name); idx >= 0 {
-		if idx < o.value.Len() {
-			return &valueProperty{
-				value:      o._getIdx(idx),
-				writable:   true,
-				enumerable: true,
-			}
-		}
-		return nil
-	}
-	if name == "length" {
-		return &o.lengthProp
-	}
-	return o.objectGoReflect.getOwnPropStr(name)
-}
-
-func (o *objectGoSliceReflect) getOwnPropIdx(idx valueInt) Value {
-	if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.value.Len() {
-		return &valueProperty{
-			value:      o._getIdx(idx),
-			writable:   true,
-			enumerable: true,
-		}
-	}
-	return nil
-}
-
-func (o *objectGoSliceReflect) putIdx(idx int, v Value, throw bool) bool {
+func (o *objectGoSliceReflect) _putIdx(idx int, v Value, throw bool) bool {
 	if idx >= o.value.Len() {
 	if idx >= o.value.Len() {
 		o.grow(idx + 1)
 		o.grow(idx + 1)
 	}
 	}
-	err := o.val.runtime.toReflectValue(v, o.value.Index(idx), &objectExportCtx{})
-	if err != nil {
-		o.val.runtime.typeErrorResult(throw, "Go type conversion error: %v", err)
-		return false
-	}
-	return true
+	return o.objectGoArrayReflect._putIdx(idx, v, throw)
 }
 }
 
 
 func (o *objectGoSliceReflect) grow(size int) {
 func (o *objectGoSliceReflect) grow(size int) {
@@ -154,167 +66,18 @@ func (o *objectGoSliceReflect) putLength(v uint32, throw bool) bool {
 	return true
 	return true
 }
 }
 
 
-func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
-	if i := toIntStrict(int64(idx)); i >= 0 {
-		if i >= o.value.Len() {
-			if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
-				return res
-			}
-		}
-		o.putIdx(i, val, throw)
-	} else {
-		name := idx.string()
-		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
-			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
-			return false
-		} else {
-			return res
-		}
-	}
-	return true
-}
-
 func (o *objectGoSliceReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
 func (o *objectGoSliceReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
-	if idx := strToGoIdx(name); idx >= 0 {
-		if idx >= o.value.Len() {
-			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
-				return res
-			}
-		}
-		o.putIdx(idx, val, throw)
-	} else {
-		if name == "length" {
-			return o.putLength(o.val.runtime.toLengthUint32(val), throw)
-		}
-		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
-			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
-			return false
-		} else {
-			return res
-		}
-	}
-	return true
-}
-
-func (o *objectGoSliceReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
-	return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw)
-}
-
-func (o *objectGoSliceReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
-	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
-}
-
-func (o *objectGoSliceReflect) hasOwnPropertyIdx(idx valueInt) bool {
-	return o._hasIdx(idx)
-}
-
-func (o *objectGoSliceReflect) hasOwnPropertyStr(name unistring.String) bool {
-	if o._hasStr(name) || name == "length" {
-		return true
-	}
-	return o.objectGoReflect._has(name.String())
-}
-
-func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
-	if i := toIntStrict(int64(idx)); i >= 0 {
-		if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
-			return false
-		}
-		val := descr.Value
-		if val == nil {
-			val = _undefined
-		}
-		o.putIdx(i, val, throw)
-		return true
+	if name == "length" {
+		return o.putLength(o.val.runtime.toLengthUint32(val), throw)
 	}
 	}
-	o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
-	return false
+	return o.objectGoArrayReflect.setOwnStr(name, val, throw)
 }
 }
 
 
 func (o *objectGoSliceReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 func (o *objectGoSliceReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
-	if idx := strToGoIdx(name); idx >= 0 {
-		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
-			return false
-		}
-		val := descr.Value
-		if val == nil {
-			val = _undefined
-		}
-		o.putIdx(idx, val, throw)
-		return true
-	}
 	if name == "length" {
 	if name == "length" {
 		return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw)
 		return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw)
 	}
 	}
-	o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
-	return false
-}
-
-func (o *objectGoSliceReflect) toPrimitiveNumber() Value {
-	return o.toPrimitiveString()
-}
-
-func (o *objectGoSliceReflect) toPrimitiveString() Value {
-	return o.val.runtime.arrayproto_join(FunctionCall{
-		This: o.val,
-	})
-}
-
-func (o *objectGoSliceReflect) toPrimitive() Value {
-	return o.toPrimitiveString()
-}
-
-func (o *objectGoSliceReflect) _deleteIdx(idx int) {
-	if idx < o.value.Len() {
-		o.value.Index(idx).Set(reflect.Zero(o.value.Type().Elem()))
-	}
-}
-
-func (o *objectGoSliceReflect) deleteStr(name unistring.String, throw bool) bool {
-	if idx := strToGoIdx(name); idx >= 0 {
-		o._deleteIdx(idx)
-		return true
-	}
-
-	return o.objectGoReflect.deleteStr(name, throw)
-}
-
-func (o *objectGoSliceReflect) deleteIdx(i valueInt, throw bool) bool {
-	idx := toIntStrict(int64(i))
-	if idx >= 0 {
-		o._deleteIdx(idx)
-	}
-	return true
-}
-
-type gosliceReflectPropIter struct {
-	o          *objectGoSliceReflect
-	idx, limit int
-}
-
-func (i *gosliceReflectPropIter) next() (propIterItem, iterNextFunc) {
-	if i.idx < i.limit && i.idx < i.o.value.Len() {
-		name := strconv.Itoa(i.idx)
-		i.idx++
-		return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
-	}
-
-	return i.o.objectGoReflect.iterateStringKeys()()
-}
-
-func (o *objectGoSliceReflect) stringKeys(all bool, accum []Value) []Value {
-	for i := 0; i < o.value.Len(); i++ {
-		accum = append(accum, asciiString(strconv.Itoa(i)))
-	}
-
-	return o.objectGoReflect.stringKeys(all, accum)
-}
-
-func (o *objectGoSliceReflect) iterateStringKeys() iterNextFunc {
-	return (&gosliceReflectPropIter{
-		o:     o,
-		limit: o.value.Len(),
-	}).next
+	return o.objectGoArrayReflect.defineOwnPropertyStr(name, descr, throw)
 }
 }
 
 
 func (o *objectGoSliceReflect) equal(other objectImpl) bool {
 func (o *objectGoSliceReflect) equal(other objectImpl) bool {
@@ -323,21 +86,3 @@ func (o *objectGoSliceReflect) equal(other objectImpl) bool {
 	}
 	}
 	return false
 	return false
 }
 }
-
-func (o *objectGoSliceReflect) sortLen() int64 {
-	return int64(o.value.Len())
-}
-
-func (o *objectGoSliceReflect) sortGet(i int64) Value {
-	return o.getIdx(valueInt(i), nil)
-}
-
-func (o *objectGoSliceReflect) swap(i, j int64) {
-	ii := valueInt(i)
-	jj := valueInt(j)
-	x := o.getIdx(ii, nil)
-	y := o.getIdx(jj, nil)
-
-	o.setOwnIdx(ii, y, false)
-	o.setOwnIdx(jj, x, false)
-}

+ 78 - 0
object_goslice_reflect_test.go

@@ -111,6 +111,54 @@ func TestGoSliceReflectPush(t *testing.T) {
 
 
 }
 }
 
 
+func TestGoSliceReflectStructField(t *testing.T) {
+	vm := New()
+	var s struct {
+		A []int
+		B *[]int
+	}
+	vm.Set("s", &s)
+	_, err := vm.RunString(`
+		'use strict';
+		s.A.push(1);
+		if (s.B !== null) {
+			throw new Error("s.B is not null: " + s.B);
+		}
+		s.B = [2];
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(s.A) != 1 || s.A[0] != 1 {
+		t.Fatalf("s.A: %v", s.A)
+	}
+	if len(*s.B) != 1 || (*s.B)[0] != 2 {
+		t.Fatalf("s.B: %v", *s.B)
+	}
+}
+
+func TestGoSliceReflectExportToStructField(t *testing.T) {
+	vm := New()
+	v, err := vm.RunString(`({A: [1], B: [2]})`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var s struct {
+		A []int
+		B *[]int
+	}
+	err = vm.ExportTo(v, &s)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(s.A) != 1 || s.A[0] != 1 {
+		t.Fatalf("s.A: %v", s.A)
+	}
+	if len(*s.B) != 1 || (*s.B)[0] != 2 {
+		t.Fatalf("s.B: %v", *s.B)
+	}
+}
+
 func TestGoSliceReflectProtoMethod(t *testing.T) {
 func TestGoSliceReflectProtoMethod(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
 	a.join(",")
 	a.join(",")
@@ -370,3 +418,33 @@ func TestGoSliceReflectMethods(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 }
 }
+
+func TestGoSliceReflectExportAfterGrow(t *testing.T) {
+	vm := New()
+	vm.Set("a", []int{1})
+	v, err := vm.RunString(`
+		a.push(2);
+		a;
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	exp := v.Export()
+	if a, ok := exp.([]int); ok {
+		if len(a) != 2 || a[0] != 1 || a[1] != 2 {
+			t.Fatal(a)
+		}
+	} else {
+		t.Fatalf("Wrong type: %T", exp)
+	}
+}
+
+func BenchmarkGoSliceReflectSet(b *testing.B) {
+	vm := New()
+	a := vm.ToValue([]int{1}).(*Object)
+	b.ResetTimer()
+	v := intToValue(0)
+	for i := 0; i < b.N; i++ {
+		a.Set("0", v)
+	}
+}

+ 2 - 2
object_lazy.go

@@ -259,10 +259,10 @@ func (o *lazyObject) exportToMap(m reflect.Value, typ reflect.Type, ctx *objectE
 	return obj.exportToMap(m, typ, ctx)
 	return obj.exportToMap(m, typ, ctx)
 }
 }
 
 
-func (o *lazyObject) exportToSlice(s reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
+func (o *lazyObject) exportToArrayOrSlice(s reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 	obj := o.create(o.val)
 	obj := o.create(o.val)
 	o.val.self = obj
 	o.val.self = obj
-	return obj.exportToSlice(s, typ, ctx)
+	return obj.exportToArrayOrSlice(s, typ, ctx)
 }
 }
 
 
 func (o *lazyObject) equal(other objectImpl) bool {
 func (o *lazyObject) equal(other objectImpl) bool {

+ 56 - 0
object_test.go

@@ -3,6 +3,7 @@ package goja
 import (
 import (
 	"fmt"
 	"fmt"
 	"reflect"
 	"reflect"
+	"strings"
 	"testing"
 	"testing"
 )
 )
 
 
@@ -390,6 +391,61 @@ func ExampleRuntime_ExportTo_arrayLikeToSlice() {
 	// Output: [1 2 3]
 	// Output: [1 2 3]
 }
 }
 
 
+func TestExportArrayToArrayMismatchedLengths(t *testing.T) {
+	vm := New()
+	a := vm.NewArray(1, 2)
+	var a1 [3]int
+	err := vm.ExportTo(a, &a1)
+	if err == nil {
+		t.Fatal("expected error")
+	}
+	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestExportIterableToArrayMismatchedLengths(t *testing.T) {
+	vm := New()
+	a, err := vm.RunString(`
+		new Map([[1, true], [2, true]]);
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var a1 [3]interface{}
+	err = vm.ExportTo(a, &a1)
+	if err == nil {
+		t.Fatal("expected error")
+	}
+	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) {
+	vm := New()
+	a, err := vm.RunString(`
+		({
+			length: 2,
+			0: true,
+			1: true
+		});
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var a1 [3]interface{}
+	err = vm.ExportTo(a, &a1)
+	if err == nil {
+		t.Fatal("expected error")
+	}
+	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
 func TestSetForeignReturnValue(t *testing.T) {
 func TestSetForeignReturnValue(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
 	var array = [1, 2, 3];
 	var array = [1, 2, 3];

+ 33 - 6
runtime.go

@@ -1463,6 +1463,8 @@ Export()'ed and therefore copied. This may result in an unexpected behaviour in
  `)
  `)
  fmt.Println(m["obj"].(map[string]interface{})["test"]) // prints "false"
  fmt.Println(m["obj"].(map[string]interface{})["test"]) // prints "false"
 
 
+Non-addressable structs, slices and arrays get copied (as if they were passed as a function parameter, by value).
+
 Notes on individual types:
 Notes on individual types:
 
 
 Primitive types
 Primitive types
@@ -1539,8 +1541,7 @@ Structs
 Structs are converted to Object-like values. Fields and methods are available as properties, their values are
 Structs are converted to Object-like values. Fields and methods are available as properties, their values are
 results of this method (ToValue()) applied to the corresponding Go value.
 results of this method (ToValue()) applied to the corresponding Go value.
 
 
-Field properties are writable (if the struct is addressable) and non-configurable.
-Method properties are non-writable and non-configurable.
+Field properties are writable and non-configurable. Method properties are non-writable and non-configurable.
 
 
 Attempt to define a new property or delete an existing property will fail (throw in strict mode) unless it's a Symbol
 Attempt to define a new property or delete an existing property will fail (throw in strict mode) unless it's a Symbol
 property. Symbol properties only exist in the wrapper and do not affect the underlying Go value.
 property. Symbol properties only exist in the wrapper and do not affect the underlying Go value.
@@ -1611,6 +1612,11 @@ an index < length will set it to a zero value (but the property will remain). Ni
 `null`. Accessing an element beyond `length` returns `undefined`. Also see the warning above about passing slices as
 `null`. Accessing an element beyond `length` returns `undefined`. Also see the warning above about passing slices as
 values (as opposed to pointers).
 values (as opposed to pointers).
 
 
+Arrays
+
+Arrays are converted similarly to slices, except the resulting Arrays are not resizable (and therefore the 'length'
+property is non-writable).
+
 Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar
 Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar
 to a Number, String, Boolean or Object.
 to a Number, String, Boolean or Object.
 
 
@@ -1767,9 +1773,9 @@ func (r *Runtime) ToValue(i interface{}) Value {
 				return obj
 				return obj
 			}
 			}
 		}
 		}
-	case reflect.Slice:
+	case reflect.Array:
 		obj := &Object{runtime: r}
 		obj := &Object{runtime: r}
-		a := &objectGoSliceReflect{
+		a := &objectGoArrayReflect{
 			objectGoReflect: objectGoReflect{
 			objectGoReflect: objectGoReflect{
 				baseObject: baseObject{
 				baseObject: baseObject{
 					val: obj,
 					val: obj,
@@ -1781,6 +1787,22 @@ func (r *Runtime) ToValue(i interface{}) Value {
 		a.init()
 		a.init()
 		obj.self = a
 		obj.self = a
 		return obj
 		return obj
+	case reflect.Slice:
+		obj := &Object{runtime: r}
+		a := &objectGoSliceReflect{
+			objectGoArrayReflect: objectGoArrayReflect{
+				objectGoReflect: objectGoReflect{
+					baseObject: baseObject{
+						val: obj,
+					},
+					origValue: origValue,
+					value:     value,
+				},
+			},
+		}
+		a.init()
+		obj.self = a
+		return obj
 	case reflect.Func:
 	case reflect.Func:
 		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		return r.newNativeFunc(r.wrapReflectFunc(value), nil, name, nil, value.Type().NumIn())
 		return r.newNativeFunc(r.wrapReflectFunc(value), nil, name, nil, value.Type().NumIn())
@@ -2012,13 +2034,13 @@ func (r *Runtime) toReflectValue(v Value, dst reflect.Value, ctx *objectExportCt
 	case reflect.Float32:
 	case reflect.Float32:
 		dst.Set(reflect.ValueOf(toFloat32(v)).Convert(typ))
 		dst.Set(reflect.ValueOf(toFloat32(v)).Convert(typ))
 		return nil
 		return nil
-	case reflect.Slice:
+	case reflect.Slice, reflect.Array:
 		if o, ok := v.(*Object); ok {
 		if o, ok := v.(*Object); ok {
 			if v, exists := ctx.getTyped(o, typ); exists {
 			if v, exists := ctx.getTyped(o, typ); exists {
 				dst.Set(reflect.ValueOf(v))
 				dst.Set(reflect.ValueOf(v))
 				return nil
 				return nil
 			}
 			}
-			return o.self.exportToSlice(dst, typ, ctx)
+			return o.self.exportToArrayOrSlice(dst, typ, ctx)
 		}
 		}
 	case reflect.Map:
 	case reflect.Map:
 		if o, ok := v.(*Object); ok {
 		if o, ok := v.(*Object); ok {
@@ -2175,6 +2197,11 @@ func (r *Runtime) wrapJSFunc(fn Callable, typ reflect.Type) func(args []reflect.
 //
 //
 // Any other Object is treated as an array-like object with zero length and results in an empty slice.
 // Any other Object is treated as an array-like object with zero length and results in an empty slice.
 //
 //
+// Array types
+//
+// Anything that can be exported to a slice type can also be exported to an array type, as long as the lengths
+// match. If they do not, an error is returned.
+//
 // Proxy
 // Proxy
 //
 //
 // Proxy objects are treated the same way as if they were accessed from ES code in regard to their properties
 // Proxy objects are treated the same way as if they were accessed from ES code in regard to their properties