Преглед изворни кода

Refactored conversion to primitive. Always use `String()` method if defined. Closes #423.

Dmitry Panov пре 3 година
родитељ
комит
1444e6b945
7 измењених фајлова са 309 додато и 73 уклоњено
  1. 0 10
      object_goarray_reflect.go
  2. 29 0
      object_goarray_reflect_test.go
  3. 79 48
      object_goreflect.go
  4. 188 0
      object_goreflect_test.go
  5. 0 14
      object_goslice.go
  6. 13 0
      object_goslice_test.go
  7. 0 1
      string.go

+ 0 - 10
object_goarray_reflect.go

@@ -256,16 +256,6 @@ func (o *objectGoArrayReflect) defineOwnPropertyStr(name unistring.String, descr
 	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()
 }

+ 29 - 0
object_goarray_reflect_test.go

@@ -273,3 +273,32 @@ func TestCopyOnChangeSort(t *testing.T) {
 		t.Fatal(a)
 	}
 }
+
+type testStringerArray [8]byte
+
+func (a testStringerArray) String() string {
+	return "X"
+}
+
+func TestReflectArrayToString(t *testing.T) {
+	vm := New()
+	var a testStringerArray
+	vm.Set("a", &a)
+	res, err := vm.RunString("`${a}`")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if exp := res.Export(); exp != "X" {
+		t.Fatal(exp)
+	}
+
+	var a1 [2]byte
+	vm.Set("a", &a1)
+	res, err = vm.RunString("`${a}`")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if exp := res.Export(); exp != "0,0" {
+		t.Fatal(exp)
+	}
+}

+ 79 - 48
object_goreflect.go

@@ -105,6 +105,8 @@ type objectGoReflect struct {
 
 	valueCache map[string]reflectValueWrapper
 
+	toString, valueOf func() Value
+
 	toJson func() interface{}
 }
 
@@ -114,15 +116,24 @@ func (o *objectGoReflect) init() {
 	case reflect.Bool:
 		o.class = classBoolean
 		o.prototype = o.val.runtime.global.BooleanPrototype
+		o.toString = o._toStringBool
+		o.valueOf = o._valueOfBool
 	case reflect.String:
 		o.class = classString
 		o.prototype = o.val.runtime.global.StringPrototype
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
-		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
-		reflect.Float32, reflect.Float64:
-
+		o.toString = o._toStringString
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		o.class = classNumber
+		o.prototype = o.val.runtime.global.NumberPrototype
+		o.valueOf = o._valueOfInt
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 		o.class = classNumber
 		o.prototype = o.val.runtime.global.NumberPrototype
+		o.valueOf = o._valueOfUint
+	case reflect.Float32, reflect.Float64:
+		o.class = classNumber
+		o.prototype = o.val.runtime.global.NumberPrototype
+		o.valueOf = o._valueOfFloat
 	default:
 		o.class = classObject
 		o.prototype = o.val.runtime.global.ObjectPrototype
@@ -135,8 +146,17 @@ func (o *objectGoReflect) init() {
 	}
 	o.extensible = true
 
-	o.baseObject._putProp("toString", o.val.runtime.newNativeFunc(o.toStringFunc, nil, "toString", nil, 0), true, false, true)
-	o.baseObject._putProp("valueOf", o.val.runtime.newNativeFunc(o.valueOfFunc, nil, "valueOf", nil, 0), true, false, true)
+	switch o.origValue.Interface().(type) {
+	case fmt.Stringer:
+		o.toString = o._toStringStringer
+	case error:
+		o.toString = o._toStringError
+	}
+
+	if o.toString != nil || o.valueOf != nil {
+		o.baseObject._putProp("toString", o.val.runtime.newNativeFunc(o.toStringFunc, nil, "toString", nil, 0), true, false, true)
+		o.baseObject._putProp("valueOf", o.val.runtime.newNativeFunc(o.valueOfFunc, nil, "valueOf", nil, 0), true, false, true)
+	}
 
 	o.valueTypeInfo = o.val.runtime.typeInfo(o.value.Type())
 	o.origValueTypeInfo = o.val.runtime.typeInfo(o.origValue.Type())
@@ -151,7 +171,7 @@ func (o *objectGoReflect) toStringFunc(FunctionCall) Value {
 }
 
 func (o *objectGoReflect) valueOfFunc(FunctionCall) Value {
-	return o.toPrimitive()
+	return o.toPrimitiveNumber()
 }
 
 func (o *objectGoReflect) getStr(name unistring.String, receiver Value) Value {
@@ -354,64 +374,75 @@ func (o *objectGoReflect) hasOwnPropertyStr(name unistring.String) bool {
 	return o._has(name.String())
 }
 
-func (o *objectGoReflect) _toNumber() Value {
-	switch o.value.Kind() {
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return intToValue(o.value.Int())
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		return intToValue(int64(o.value.Uint()))
-	case reflect.Bool:
-		if o.value.Bool() {
-			return intToValue(1)
-		} else {
-			return intToValue(0)
-		}
-	case reflect.Float32, reflect.Float64:
-		return floatToValue(o.value.Float())
-	}
-	return nil
+func (o *objectGoReflect) _valueOfInt() Value {
+	return intToValue(o.value.Int())
 }
 
-func (o *objectGoReflect) _toString() Value {
-	switch o.value.Kind() {
-	case reflect.String:
-		return newStringValue(o.value.String())
-	case reflect.Bool:
-		if o.value.Interface().(bool) {
-			return stringTrue
-		} else {
-			return stringFalse
-		}
+func (o *objectGoReflect) _valueOfUint() Value {
+	return intToValue(int64(o.value.Uint()))
+}
+
+func (o *objectGoReflect) _valueOfBool() Value {
+	if o.value.Bool() {
+		return valueTrue
+	} else {
+		return valueFalse
 	}
-	switch v := o.origValue.Interface().(type) {
-	case fmt.Stringer:
-		return newStringValue(v.String())
-	case error:
-		return newStringValue(v.Error())
+}
+
+func (o *objectGoReflect) _valueOfFloat() Value {
+	return floatToValue(o.value.Float())
+}
+
+func (o *objectGoReflect) _toStringStringer() Value {
+	return newStringValue(o.origValue.Interface().(fmt.Stringer).String())
+}
+
+func (o *objectGoReflect) _toStringString() Value {
+	return newStringValue(o.value.String())
+}
+
+func (o *objectGoReflect) _toStringBool() Value {
+	if o.value.Bool() {
+		return stringTrue
+	} else {
+		return stringFalse
 	}
+}
 
-	return stringObjectObject
+func (o *objectGoReflect) _toStringError() Value {
+	return newStringValue(o.origValue.Interface().(error).Error())
 }
 
 func (o *objectGoReflect) toPrimitiveNumber() Value {
-	if v := o._toNumber(); v != nil {
-		return v
+	if o.valueOf != nil {
+		return o.valueOf()
 	}
-	return o._toString()
+	if o.toString != nil {
+		return o.toString()
+	}
+	return o.baseObject.toPrimitiveNumber()
 }
 
 func (o *objectGoReflect) toPrimitiveString() Value {
-	if v := o._toNumber(); v != nil {
-		return v.toString()
+	if o.toString != nil {
+		return o.toString()
+	}
+	if o.valueOf != nil {
+		return o.valueOf().toString()
 	}
-	return o._toString()
+	return o.baseObject.toPrimitiveString()
 }
 
 func (o *objectGoReflect) toPrimitive() Value {
-	if o.prototype == o.val.runtime.global.NumberPrototype {
-		return o.toPrimitiveNumber()
+	if o.valueOf != nil {
+		return o.valueOf()
 	}
-	return o.toPrimitiveString()
+	if o.toString != nil {
+		return o.toString()
+	}
+
+	return o.baseObject.toPrimitive()
 }
 
 func (o *objectGoReflect) deleteStr(name unistring.String, throw bool) bool {

+ 188 - 0
object_goreflect_test.go

@@ -1265,3 +1265,191 @@ func TestReflectOverwriteReflectMap(t *testing.T) {
 		t.Fatal(s)
 	}
 }
+
+type testBoolS bool
+
+func (testBoolS) String() string {
+	return "B"
+}
+
+type testIntS int
+
+func (testIntS) String() string {
+	return "I"
+}
+
+type testStringS string
+
+func (testStringS) String() string {
+	return "S"
+}
+
+func TestGoReflectToPrimitive(t *testing.T) {
+	vm := New()
+
+	f := func(expr string, expected Value, t *testing.T) {
+		v, err := vm.RunString(expr)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if IsNaN(expected) {
+			if IsNaN(v) {
+				return
+			}
+		} else {
+			if v.StrictEquals(expected) {
+				return
+			}
+		}
+		t.Fatalf("%s: expected: %v, actual: %v", expr, expected, v)
+	}
+
+	t.Run("Not Stringers", func(t *testing.T) {
+		type Bool bool
+		var b Bool = true
+
+		t.Run("Bool", func(t *testing.T) {
+			vm.Set("b", b)
+			f("+b", intToValue(1), t)
+			f("`${b}`", asciiString("true"), t)
+			f("b.toString()", asciiString("true"), t)
+			f("b.valueOf()", valueTrue, t)
+		})
+
+		t.Run("*Bool", func(t *testing.T) {
+			vm.Set("b", &b)
+			f("+b", intToValue(1), t)
+			f("`${b}`", asciiString("true"), t)
+			f("b.toString()", asciiString("true"), t)
+			f("b.valueOf()", valueTrue, t)
+		})
+
+		type Int int
+		var i Int = 1
+
+		t.Run("Int", func(t *testing.T) {
+			vm.Set("i", i)
+			f("+i", intToValue(1), t)
+			f("`${i}`", asciiString("1"), t)
+			f("i.toString()", asciiString("1"), t)
+			f("i.valueOf()", intToValue(1), t)
+		})
+
+		t.Run("*Int", func(t *testing.T) {
+			vm.Set("i", &i)
+			f("+i", intToValue(1), t)
+			f("`${i}`", asciiString("1"), t)
+			f("i.toString()", asciiString("1"), t)
+			f("i.valueOf()", intToValue(1), t)
+		})
+
+		type Uint uint
+		var ui Uint = 1
+
+		t.Run("Uint", func(t *testing.T) {
+			vm.Set("ui", ui)
+			f("+ui", intToValue(1), t)
+			f("`${ui}`", asciiString("1"), t)
+			f("ui.toString()", asciiString("1"), t)
+			f("ui.valueOf()", intToValue(1), t)
+		})
+
+		t.Run("*Uint", func(t *testing.T) {
+			vm.Set("ui", &i)
+			f("+ui", intToValue(1), t)
+			f("`${ui}`", asciiString("1"), t)
+			f("ui.toString()", asciiString("1"), t)
+			f("ui.valueOf()", intToValue(1), t)
+		})
+
+		type Float float64
+		var fl Float = 1.1
+
+		t.Run("Float", func(t *testing.T) {
+			vm.Set("fl", fl)
+			f("+fl", floatToValue(1.1), t)
+			f("`${fl}`", asciiString("1.1"), t)
+			f("fl.toString()", asciiString("1.1"), t)
+			f("fl.valueOf()", floatToValue(1.1), t)
+		})
+
+		t.Run("*Float", func(t *testing.T) {
+			vm.Set("fl", &fl)
+			f("+fl", floatToValue(1.1), t)
+			f("`${fl}`", asciiString("1.1"), t)
+			f("fl.toString()", asciiString("1.1"), t)
+			f("fl.valueOf()", floatToValue(1.1), t)
+		})
+
+		fl = Float(math.Inf(1))
+		t.Run("FloatInf", func(t *testing.T) {
+			vm.Set("fl", fl)
+			f("+fl", _positiveInf, t)
+			f("fl.toString()", asciiString("Infinity"), t)
+		})
+
+		type Empty struct{}
+
+		var e Empty
+		t.Run("Empty", func(t *testing.T) {
+			vm.Set("e", &e)
+			f("+e", _NaN, t)
+			f("`${e}`", asciiString("[object Object]"), t)
+			f("e.toString()", asciiString("[object Object]"), t)
+			f("e.valueOf()", vm.ToValue(&e), t)
+		})
+	})
+
+	t.Run("Stringers", func(t *testing.T) {
+		var b testBoolS = true
+		t.Run("Bool", func(t *testing.T) {
+			vm.Set("b", b)
+			f("`${b}`", asciiString("B"), t)
+			f("b.toString()", asciiString("B"), t)
+			f("b.valueOf()", valueTrue, t)
+			f("+b", intToValue(1), t)
+		})
+
+		t.Run("*Bool", func(t *testing.T) {
+			vm.Set("b", &b)
+			f("`${b}`", asciiString("B"), t)
+			f("b.toString()", asciiString("B"), t)
+			f("b.valueOf()", valueTrue, t)
+			f("+b", intToValue(1), t)
+		})
+
+		var i testIntS = 1
+		t.Run("Int", func(t *testing.T) {
+			vm.Set("i", i)
+			f("`${i}`", asciiString("I"), t)
+			f("i.toString()", asciiString("I"), t)
+			f("i.valueOf()", intToValue(1), t)
+			f("+i", intToValue(1), t)
+		})
+
+		t.Run("*Int", func(t *testing.T) {
+			vm.Set("i", &i)
+			f("`${i}`", asciiString("I"), t)
+			f("i.toString()", asciiString("I"), t)
+			f("i.valueOf()", intToValue(1), t)
+			f("+i", intToValue(1), t)
+		})
+
+		var s testStringS
+		t.Run("String", func(t *testing.T) {
+			vm.Set("s", s)
+			f("`${s}`", asciiString("S"), t)
+			f("s.toString()", asciiString("S"), t)
+			f("s.valueOf()", asciiString("S"), t)
+			f("+s", _NaN, t)
+		})
+
+		t.Run("*String", func(t *testing.T) {
+			vm.Set("s", &s)
+			f("`${s}`", asciiString("S"), t)
+			f("s.toString()", asciiString("S"), t)
+			f("s.valueOf()", asciiString("S"), t)
+			f("+s", _NaN, t)
+		})
+	})
+}

+ 0 - 14
object_goslice.go

@@ -244,20 +244,6 @@ func (o *objectGoSlice) defineOwnPropertyStr(name unistring.String, descr Proper
 	return false
 }
 
-func (o *objectGoSlice) toPrimitiveNumber() Value {
-	return o.toPrimitiveString()
-}
-
-func (o *objectGoSlice) toPrimitiveString() Value {
-	return o.val.runtime.arrayproto_join(FunctionCall{
-		This: o.val,
-	})
-}
-
-func (o *objectGoSlice) toPrimitive() Value {
-	return o.toPrimitiveString()
-}
-
 func (o *objectGoSlice) _deleteIdx(idx int64) {
 	if idx < int64(len(*o.data)) {
 		(*o.data)[idx] = nil

+ 13 - 0
object_goslice_test.go

@@ -258,3 +258,16 @@ func TestGoSliceSort(t *testing.T) {
 		t.Fatalf("val: %v", s)
 	}
 }
+
+func TestGoSliceToString(t *testing.T) {
+	vm := New()
+	s := []interface{}{4, 2, 3}
+	vm.Set("s", &s)
+	res, err := vm.RunString("`${s}`")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if exp := res.Export(); exp != "4,2,3" {
+		t.Fatal(exp)
+	}
+}

+ 0 - 1
string.go

@@ -42,7 +42,6 @@ var (
 	stringGoError        valueString = asciiString("GoError")
 
 	stringObjectNull      valueString = asciiString("[object Null]")
-	stringObjectObject    valueString = asciiString("[object Object]")
 	stringObjectUndefined valueString = asciiString("[object Undefined]")
 	stringInvalidDate     valueString = asciiString("Invalid Date")
 )