1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-12 20:27:30 +08:00

Merge pull request #107 from multiplay/call-params

Improve method call parameter processing
This commit is contained in:
Steven Hartland 2015-12-01 13:14:24 +00:00
commit af6c304c5a
2 changed files with 200 additions and 55 deletions

View File

@ -116,6 +116,16 @@ func (abs _abcStruct) Func2IntVariadic(s string, a ...int) string {
return fmt.Sprintf("%v:%v", s, t) return fmt.Sprintf("%v:%v", s, t)
} }
func (abs _abcStruct) Func2IntArrayVariadic(s string, a ...[]int) string {
t := 0
for _, i := range a {
for _, j := range i {
t += j
}
}
return fmt.Sprintf("%v:%v", s, t)
}
type _mnoStruct struct { type _mnoStruct struct {
Ghi string Ghi string
} }
@ -234,6 +244,10 @@ func Test_reflectStruct(t *testing.T) {
abc.Func1Int(1); abc.Func1Int(1);
`, 2) `, 2)
test(`
abc.Func1Int(0x01 & 0x01);
`, 2)
test(`raise: test(`raise:
abc.Func1Int(1.1); abc.Func1Int(1.1);
`, "converting float64 to int would cause loss of precision") `, "converting float64 to int would cause loss of precision")
@ -294,6 +308,22 @@ func Test_reflectStruct(t *testing.T) {
test(` test(`
abc.Func2IntVariadic("test", 1, 2); abc.Func2IntVariadic("test", 1, 2);
`, "test:3") `, "test:3")
test(`
abc.Func2IntVariadic("test", [1, 2]);
`, "test:3")
test(`
abc.Func2IntArrayVariadic("test", [1, 2]);
`, "test:3")
test(`
abc.Func2IntArrayVariadic("test", [1, 2], [3, 4]);
`, "test:10")
test(`
abc.Func2IntArrayVariadic("test", [[1, 2], [3, 4]]);
`, "test:10")
} }
}) })
} }
@ -535,7 +565,6 @@ func Test_reflectArray(t *testing.T) {
is(abc[len(abc)-1], false) is(abc[len(abc)-1], false)
// ... // ...
} }
// []int32 // []int32
{ {
abc := make([]int32, 4) abc := make([]int32, 4)

View File

@ -2,6 +2,8 @@ package otto
import ( import (
"errors" "errors"
"fmt"
"math"
"reflect" "reflect"
"sync" "sync"
@ -191,65 +193,146 @@ func (self *_runtime) safeToValue(value interface{}) (Value, error) {
return result, err return result, err
} }
// safeNumericConvert converts numeric parameter val from js to the type that the function fn expects if its safe to do so. // convertNumeric converts numeric parameter val from js to that of type t if it is safe to do so, otherwise it panics.
// This allows literals (int64) and the general js numeric form (float64) to be passed as parameters to go functions easily. // This allows literals (int64), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily.
func safeNumericConvert(fn reflect.Type, i int, val interface{}) reflect.Value { func convertNumeric(val reflect.Value, t reflect.Type) reflect.Value {
switch val.(type) { if val.Kind() == t.Kind() {
default: return val
// Not a supported conversion
return reflect.ValueOf(val)
case float64, int64:
// What type is the func expecting?
var ptype reflect.Type
switch {
case fn.IsVariadic() && fn.NumIn() <= i+1:
// This argument is variadic so use the variadics element type.
ptype = fn.In(fn.NumIn() - 1).Elem()
case fn.NumIn() > i:
ptype = fn.In(i)
} }
if f64, ok := val.(float64); ok { if val.Kind() == reflect.Interface {
switch ptype.Kind() { val = reflect.ValueOf(val.Interface())
}
switch val.Kind() {
case reflect.Float32, reflect.Float64:
f64 := val.Float()
switch t.Kind() {
case reflect.Float64: case reflect.Float64:
return reflect.ValueOf(val) return reflect.ValueOf(f64)
case reflect.Float32: case reflect.Float32:
if reflect.Zero(ptype).OverflowFloat(f64) { if reflect.Zero(t).OverflowFloat(f64) {
// Not safe to convert panic("converting float64 to float32 would overflow")
return reflect.ValueOf(val)
} }
return reflect.ValueOf(val).Convert(ptype) return val.Convert(t)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i64 := int64(f64) i64 := int64(f64)
if float64(i64) != f64 { if float64(i64) != f64 {
// Not safe to convert panic(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t))
return reflect.ValueOf(val)
} }
// The float represents an integer // The float represents an integer
val = i64 val = reflect.ValueOf(i64)
default: default:
// Not a supported conversion panic(fmt.Sprintf("cannot convert %v to %v", val.Type(), t))
return reflect.ValueOf(val)
} }
} }
i64 := val.(int64) switch val.Kind() {
switch ptype.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: i64 := val.Int()
if !reflect.Zero(ptype).OverflowInt(i64) { switch t.Kind() {
return reflect.ValueOf(val).Convert(ptype) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if reflect.Zero(t).OverflowInt(i64) {
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
} }
return val.Convert(t)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if i64 > 0 && !reflect.Zero(ptype).OverflowUint(uint64(i64)) { if i64 < 0 {
return reflect.ValueOf(val).Convert(ptype) panic(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t))
} }
if reflect.Zero(t).OverflowUint(uint64(i64)) {
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
}
return val.Convert(t)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u64 := val.Uint()
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if u64 > math.MaxInt64 || reflect.Zero(t).OverflowInt(int64(u64)) {
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
}
return val.Convert(t)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if reflect.Zero(t).OverflowUint(u64) {
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
}
return val.Convert(t)
} }
} }
// Not a supported conversion panic(fmt.Sprintf("unsupported type %v for numeric conversion", val.Type()))
return reflect.ValueOf(val) }
// callParamConvert converts request val to type t if possible.
// If the conversion fails due to overflow or type miss-match then it panics.
// If no conversion is known then the original value is returned.
func callParamConvert(val reflect.Value, t reflect.Type) reflect.Value {
if val.Kind() == reflect.Interface {
val = reflect.ValueOf(val.Interface())
}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
if val.Kind() == t.Kind() {
// Types already match
return val
}
return convertNumeric(val, t)
case reflect.Slice:
if val.Kind() != reflect.Slice {
// Conversion from none slice type to slice not possible
panic(fmt.Sprintf("cannot use %v as type %v", val, t))
}
default:
// No supported conversion
return val
}
elemType := t.Elem()
switch elemType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Slice:
// Attempt to convert to slice of the type t
s := reflect.MakeSlice(reflect.SliceOf(elemType), val.Len(), val.Len())
for i := 0; i < val.Len(); i++ {
s.Index(i).Set(callParamConvert(val.Index(i), elemType))
}
return s
}
// Not a slice type we can convert
return val
}
// callSliceRequired returns true if CallSlice is required instead of Call.
func callSliceRequired(param reflect.Type, val reflect.Value) bool {
vt := val.Type()
for param.Kind() == reflect.Slice {
if val.Kind() == reflect.Interface {
val = reflect.ValueOf(val.Interface())
vt = val.Type()
}
if vt.Kind() != reflect.Slice {
return false
}
vt = vt.Elem()
if val.Kind() != reflect.Invalid {
if val.Len() > 0 {
val = val.Index(0)
} else {
val = reflect.Value{}
}
}
param = param.Elem()
}
return true
} }
func (self *_runtime) toValue(value interface{}) Value { func (self *_runtime) toValue(value interface{}) Value {
@ -278,13 +361,46 @@ func (self *_runtime) toValue(value interface{}) Value {
case reflect.Func: case reflect.Func:
// TODO Maybe cache this? // TODO Maybe cache this?
return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value { return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value {
in := make([]reflect.Value, len(call.ArgumentList)) argsCount := len(call.ArgumentList)
in := make([]reflect.Value, argsCount)
t := value.Type() t := value.Type()
callSlice := false
paramsCount := t.NumIn()
lastParam := paramsCount - 1
lastArg := argsCount - 1
isVariadic := t.IsVariadic()
for i, value := range call.ArgumentList { for i, value := range call.ArgumentList {
in[i] = safeNumericConvert(t, i, value.export()) var paramType reflect.Type
if isVariadic && i == lastArg && argsCount == paramsCount {
// Variadic functions last parameter and parameter numbers match incoming args
paramType = t.In(lastArg)
val := reflect.ValueOf(value.export())
callSlice = callSliceRequired(paramType, val)
if callSlice {
in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType)
continue
}
}
if i >= lastParam {
if isVariadic {
paramType = t.In(lastParam).Elem()
} else {
paramType = t.In(lastParam)
}
} else {
paramType = t.In(i)
}
in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType)
}
var out []reflect.Value
if callSlice {
out = value.CallSlice(in)
} else {
out = value.Call(in)
} }
out := value.Call(in)
l := len(out) l := len(out)
switch l { switch l {
case 0: case 0: