-
Notifications
You must be signed in to change notification settings - Fork 191
/
func.go
317 lines (274 loc) · 7.9 KB
/
func.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
package reflects
import (
"errors"
"fmt"
"reflect"
"github.com/gookit/goutil/basefn"
)
// FuncX wrap a go func. represent a function
type FuncX struct {
CallOpt
// Name of func. eg: "MyFunc"
Name string
// rv is the `reflect.Value` of func
rv reflect.Value
rt reflect.Type
}
// NewFunc instance. param fn support func and reflect.Value
func NewFunc(fn any) *FuncX {
var ok bool
var rv reflect.Value
if rv, ok = fn.(reflect.Value); !ok {
rv = reflect.ValueOf(fn)
}
rv = indirectInterface(rv)
if !rv.IsValid() {
panic("input func is nil")
}
typ := rv.Type()
if typ.Kind() != reflect.Func {
basefn.Panicf("non-function of type: %s", typ)
}
return &FuncX{rv: rv, rt: typ}
}
// NumIn get the number of func input args
func (f *FuncX) NumIn() int {
return f.rt.NumIn()
}
// NumOut get the number of func output args
func (f *FuncX) NumOut() int {
return f.rt.NumOut()
}
// Call the function with given arguments.
//
// Usage:
//
// func main() {
// fn := func(a, b int) int {
// return a + b
// }
//
// fx := NewFunc(fn)
// ret, err := fx.Call(1, 2)
// fmt.Println(ret[0], err) // Output: 3 <nil>
// }
func (f *FuncX) Call(args ...any) ([]any, error) {
// convert args to []reflect.Value
argRvs := make([]reflect.Value, len(args))
for i, arg := range args {
argRvs[i] = reflect.ValueOf(arg)
}
ret, err := f.CallRV(argRvs)
if err != nil {
return nil, err
}
// convert ret to []any
rets := make([]any, len(ret))
for i, r := range ret {
rets[i] = r.Interface()
}
return rets, nil
}
// Call2 returns the result of evaluating the first argument as a function.
// The function must return 1 result, or 2 results, the second of which is an error.
//
// - Only support func with 1 or 2 return values: (val) OR (val, err)
// - Will check args and try convert input args to func args type.
func (f *FuncX) Call2(args ...any) (any, error) {
// convert args to []reflect.Value
argRvs := make([]reflect.Value, len(args))
for i, arg := range args {
argRvs[i] = reflect.ValueOf(arg)
}
if f.TypeChecker == nil {
f.TypeChecker = OneOrTwoOutChecker
}
// do call func
ret, err := Call(f.rv, argRvs, &f.CallOpt)
if err != nil {
return emptyValue, err
}
// func return like: (val, err)
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0].Interface(), ret[1].Interface().(error)
}
return ret[0].Interface(), nil
}
// CallRV call the function with given reflect.Value arguments.
func (f *FuncX) CallRV(args []reflect.Value) ([]reflect.Value, error) {
return Call(f.rv, args, &f.CallOpt)
}
// WithTypeChecker set type checker
func (f *FuncX) WithTypeChecker(checker TypeCheckerFn) *FuncX {
f.TypeChecker = checker
return f
}
// WithEnhanceConv set enhance convert
func (f *FuncX) WithEnhanceConv() *FuncX {
f.EnhanceConv = true
return f
}
// String of func
func (f *FuncX) String() string {
return f.rt.String()
}
// TypeCheckerFn type checker func
type TypeCheckerFn func(typ reflect.Type) error
// CallOpt call options
type CallOpt struct {
// TypeChecker check func type before call func. eg: check return values
TypeChecker TypeCheckerFn
// EnhanceConv try to enhance auto convert args to func args type
// - support more type: string, int, uint, float, bool
EnhanceConv bool
}
// OneOrTwoOutChecker check func type. only allow 1 or 2 return values
//
// Allow func returns:
// - 1 return: (value)
// - 2 return: (value, error)
var OneOrTwoOutChecker = func(typ reflect.Type) error {
if !good1or2outFunc(typ) {
return errors.New("func allow with 1 result or 2 results where the second is an error")
}
return nil
}
//
// TIP:
// flow func refer from text/template package.
//
//
// reports whether the function or method has the right result signature.
func good1or2outFunc(typ reflect.Type) bool {
// We allow functions with 1 result or 2 results where the second is an error.
switch {
case typ.NumOut() == 1:
return true
case typ.NumOut() == 2 && typ.Out(1) == errorType:
return true
}
return false
}
// Call2 returns the result of evaluating the first argument as a function.
// The function must return 1 result, or 2 results, the second of which is an error.
//
// - Only support func with 1 or 2 return values: (val) OR (val, err)
// - Will check args and try convert input args to func args type.
func Call2(fn reflect.Value, args []reflect.Value) (reflect.Value, error) {
ret, err := Call(fn, args, &CallOpt{
TypeChecker: OneOrTwoOutChecker,
})
if err != nil {
return emptyValue, err
}
// func return like: (val, err)
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0], ret[1].Interface().(error)
}
return ret[0], nil
}
// Call returns the result of evaluating the first argument as a function.
//
// - Will check args and try convert input args to func args type.
//
// from text/template/funcs.go#call
func Call(fn reflect.Value, args []reflect.Value, opt *CallOpt) ([]reflect.Value, error) {
fn = indirectInterface(fn)
if !fn.IsValid() {
return nil, fmt.Errorf("call of nil")
}
typ := fn.Type()
if typ.Kind() != reflect.Func {
return nil, fmt.Errorf("non-function of type %s", typ)
}
if opt == nil {
opt = &CallOpt{}
}
if opt.TypeChecker != nil {
if err := opt.TypeChecker(typ); err != nil {
return nil, err
}
}
numIn := typ.NumIn()
var dddType reflect.Type
if typ.IsVariadic() {
if len(args) < numIn-1 {
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
}
dddType = typ.In(numIn - 1).Elem()
} else {
if len(args) != numIn {
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
}
}
// Convert each arg to the type of the function's arg.
argv := make([]reflect.Value, len(args))
for i, arg := range args {
arg = indirectInterface(arg)
// Compute the expected type. Clumsy because of variadic.
argType := dddType
if !typ.IsVariadic() || i < numIn-1 {
argType = typ.In(i)
}
var err error
if argv[i], err = prepareArg(arg, argType, opt.EnhanceConv); err != nil {
return nil, fmt.Errorf("arg %d: %w", i, err)
}
}
return SafeCall(fn, argv)
}
// SafeCall2 runs fun.Call(args), and returns the resulting value and error, if
// any. If the call panics, the panic value is returned as an error.
//
// NOTE: Only support func with 1 or 2 return values: (val) OR (val, err)
//
// from text/template/funcs.go#safeCall
func SafeCall2(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) {
ret, err := SafeCall(fun, args)
if err != nil {
return reflect.Value{}, err
}
// func return like: (val, err)
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0], ret[1].Interface().(error)
}
return ret[0], nil
}
// SafeCall runs fun.Call(args), and returns the resulting values, or an error.
// If the call panics, the panic value is returned as an error.
func SafeCall(fun reflect.Value, args []reflect.Value) (ret []reflect.Value, err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
ret = fun.Call(args)
return
}
// prepareArg checks if value can be used as an argument of type argType, and
// converts an invalid value to appropriate zero if possible.
func prepareArg(value reflect.Value, argType reflect.Type, enhanced bool) (reflect.Value, error) {
if !value.IsValid() {
if !CanBeNil(argType) {
return emptyValue, fmt.Errorf("value is nil; should be of type %s", argType)
}
value = reflect.Zero(argType)
}
if value.Type().AssignableTo(argType) {
return value, nil
}
// If the argument is an int-like type, and the value is an int-like type, auto-convert.
if IsIntLike(value.Kind()) && IsIntLike(argType.Kind()) && value.Type().ConvertibleTo(argType) {
value = value.Convert(argType)
return value, nil
}
// enhance convert value to argType, support more type: string, int, uint, float, bool
if enhanced {
return ValueByType(value.Interface(), argType)
}
return emptyValue, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
}