1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-05 19:19:10 +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)
}
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 {
Ghi string
}
@ -234,6 +244,10 @@ func Test_reflectStruct(t *testing.T) {
abc.Func1Int(1);
`, 2)
test(`
abc.Func1Int(0x01 & 0x01);
`, 2)
test(`raise:
abc.Func1Int(1.1);
`, "converting float64 to int would cause loss of precision")
@ -294,6 +308,22 @@ func Test_reflectStruct(t *testing.T) {
test(`
abc.Func2IntVariadic("test", 1, 2);
`, "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)
// ...
}
// []int32
{
abc := make([]int32, 4)

View File

@ -2,6 +2,8 @@ package otto
import (
"errors"
"fmt"
"math"
"reflect"
"sync"
@ -191,65 +193,146 @@ func (self *_runtime) safeToValue(value interface{}) (Value, error) {
return result, err
}
// safeNumericConvert converts numeric parameter val from js to the type that the function fn expects if its safe to do so.
// This allows literals (int64) and the general js numeric form (float64) to be passed as parameters to go functions easily.
func safeNumericConvert(fn reflect.Type, i int, val interface{}) reflect.Value {
switch val.(type) {
default:
// 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)
}
// 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), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily.
func convertNumeric(val reflect.Value, t reflect.Type) reflect.Value {
if val.Kind() == t.Kind() {
return val
}
if f64, ok := val.(float64); ok {
switch ptype.Kind() {
case reflect.Float64:
return reflect.ValueOf(val)
case reflect.Float32:
if reflect.Zero(ptype).OverflowFloat(f64) {
// Not safe to convert
return reflect.ValueOf(val)
}
if val.Kind() == reflect.Interface {
val = reflect.ValueOf(val.Interface())
}
return reflect.ValueOf(val).Convert(ptype)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i64 := int64(f64)
if float64(i64) != f64 {
// Not safe to convert
return reflect.ValueOf(val)
}
// The float represents an integer
val = i64
default:
// Not a supported conversion
return reflect.ValueOf(val)
switch val.Kind() {
case reflect.Float32, reflect.Float64:
f64 := val.Float()
switch t.Kind() {
case reflect.Float64:
return reflect.ValueOf(f64)
case reflect.Float32:
if reflect.Zero(t).OverflowFloat(f64) {
panic("converting float64 to float32 would overflow")
}
}
i64 := val.(int64)
switch ptype.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
if !reflect.Zero(ptype).OverflowInt(i64) {
return reflect.ValueOf(val).Convert(ptype)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if i64 > 0 && !reflect.Zero(ptype).OverflowUint(uint64(i64)) {
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:
i64 := int64(f64)
if float64(i64) != f64 {
panic(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t))
}
// The float represents an integer
val = reflect.ValueOf(i64)
default:
panic(fmt.Sprintf("cannot convert %v to %v", val.Type(), t))
}
}
// Not a supported conversion
return reflect.ValueOf(val)
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i64 := val.Int()
switch t.Kind() {
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:
if i64 < 0 {
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)
}
}
panic(fmt.Sprintf("unsupported type %v for numeric conversion", val.Type()))
}
// 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 {
@ -278,13 +361,46 @@ func (self *_runtime) toValue(value interface{}) Value {
case reflect.Func:
// TODO Maybe cache this?
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()
callSlice := false
paramsCount := t.NumIn()
lastParam := paramsCount - 1
lastArg := argsCount - 1
isVariadic := t.IsVariadic()
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)
switch l {
case 0: