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:
commit
af6c304c5a
|
@ -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)
|
||||
|
|
224
runtime.go
224
runtime.go
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue
Block a user