Переглянути джерело

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 3 роки тому
батько
коміт
dd567e70ae

+ 8 - 4
array.go

@@ -507,12 +507,16 @@ func (a *arrayObject) exportType() reflect.Type {
 	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
 	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
 		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())
 		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 a.baseObject.exportToSlice(dst, typ, ctx)
+	return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
 }
 
 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
 }
 
-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
 	if iter := a.getSym(SymIterator, nil); iter == r.global.arrayValues || iter == nil {
 		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())
 		for _, item := range a.items {
@@ -483,5 +487,5 @@ func (a *sparseArrayObject) exportToSlice(dst reflect.Value, typ reflect.Type, c
 		}
 		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]]
 }
 
+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) {
 	var key1 Value = asciiString("a")
 	var key2 Value = asciiString("b")

+ 11 - 4
builtin_set.go

@@ -1,6 +1,9 @@
 package goja
 
-import "reflect"
+import (
+	"fmt"
+	"reflect"
+)
 
 var setExportType = reflectTypeArray
 
@@ -60,10 +63,14 @@ func (so *setObject) export(ctx *objectExportCtx) interface{} {
 	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
-	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())
 	iter := so.m.newIter()

+ 19 - 0
builtin_set_test.go

@@ -2,6 +2,7 @@ package goja
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 )
 
@@ -81,3 +82,21 @@ func TestSetExportToSliceCircular(t *testing.T) {
 		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)
 }
 
-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 {

+ 16 - 8
object.go

@@ -194,7 +194,7 @@ type objectImpl interface {
 	export(ctx *objectExportCtx) interface{}
 	exportType() reflect.Type
 	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
 
 	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)
 }
 
-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
 
 	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 {
 			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())
 		for i, val := range values {
@@ -1041,8 +1045,12 @@ func genericExportToSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *o
 	} else {
 		// array-like
 		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())
 		for i := 0; i < l; i++ {
@@ -1057,8 +1065,8 @@ func genericExportToSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *o
 	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

+ 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)
 }
 
-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 {

+ 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:
 		o.class = classObject
 		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
 

+ 25 - 29
object_goreflect_test.go

@@ -636,25 +636,17 @@ func TestStructNonAddressable(t *testing.T) {
 	const SCRIPT = `
 	"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) {
-		throw new Error("Field is non-writable");
+		throw new Error("s1.Field is non-writable");
 	}
 
 	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
@@ -665,8 +657,13 @@ func TestStructNonAddressable(t *testing.T) {
 	if err != nil {
 		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 {
 		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);
 	}
 	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{
 		B: B{
@@ -900,10 +888,18 @@ func TestNestedStructSet(t *testing.T) {
 	vm := New()
 	vm.Set("a", &a)
 	vm.Set("a1", a)
-	_, err := vm.RunString(SCRIPT)
+	v, err := vm.RunString(SCRIPT)
 	if err != nil {
 		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 {
 		t.Fatalf("Unexpected a.B.Field: %d", v)

+ 9 - 264
object_goslice_reflect.go

@@ -4,113 +4,25 @@ import (
 	"math"
 	"math/bits"
 	"reflect"
-	"strconv"
 
 	"github.com/dop251/goja/unistring"
 )
 
 type objectGoSliceReflect struct {
-	objectGoReflect
-	lengthProp valueProperty
+	objectGoArrayReflect
 }
 
 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.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() {
 		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) {
@@ -154,167 +66,18 @@ func (o *objectGoSliceReflect) putLength(v uint32, throw bool) bool {
 	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 {
-	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 {
-	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" {
 		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 {
@@ -323,21 +86,3 @@ func (o *objectGoSliceReflect) equal(other objectImpl) bool {
 	}
 	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) {
 	const SCRIPT = `
 	a.join(",")
@@ -370,3 +418,33 @@ func TestGoSliceReflectMethods(t *testing.T) {
 		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)
 }
 
-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)
 	o.val.self = obj
-	return obj.exportToSlice(s, typ, ctx)
+	return obj.exportToArrayOrSlice(s, typ, ctx)
 }
 
 func (o *lazyObject) equal(other objectImpl) bool {

+ 56 - 0
object_test.go

@@ -3,6 +3,7 @@ package goja
 import (
 	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -390,6 +391,61 @@ func ExampleRuntime_ExportTo_arrayLikeToSlice() {
 	// 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) {
 	const SCRIPT = `
 	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"
 
+Non-addressable structs, slices and arrays get copied (as if they were passed as a function parameter, by value).
+
 Notes on individual 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
 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
 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
 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
 to a Number, String, Boolean or Object.
 
@@ -1767,9 +1773,9 @@ func (r *Runtime) ToValue(i interface{}) Value {
 				return obj
 			}
 		}
-	case reflect.Slice:
+	case reflect.Array:
 		obj := &Object{runtime: r}
-		a := &objectGoSliceReflect{
+		a := &objectGoArrayReflect{
 			objectGoReflect: objectGoReflect{
 				baseObject: baseObject{
 					val: obj,
@@ -1781,6 +1787,22 @@ func (r *Runtime) ToValue(i interface{}) Value {
 		a.init()
 		obj.self = a
 		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:
 		name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name())
 		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:
 		dst.Set(reflect.ValueOf(toFloat32(v)).Convert(typ))
 		return nil
-	case reflect.Slice:
+	case reflect.Slice, reflect.Array:
 		if o, ok := v.(*Object); ok {
 			if v, exists := ctx.getTyped(o, typ); exists {
 				dst.Set(reflect.ValueOf(v))
 				return nil
 			}
-			return o.self.exportToSlice(dst, typ, ctx)
+			return o.self.exportToArrayOrSlice(dst, typ, ctx)
 		}
 	case reflect.Map:
 		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.
 //
+// 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 objects are treated the same way as if they were accessed from ES code in regard to their properties