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