Skip to content

Commit ab801d4

Browse files
Allow passing nil to functions as nil interface{} (#87)
* Update vm.go golang gotcha: reflect calls with nil params fail without this trick * Update vm.go * Problem with passing "nil" when operators are overloaded with interface{} type parameters fixed e.g. a != nil was not working when "!=" is overloaded with func notEqual(a, b interface{}) interface{} {...} * Compile fix Forgot to close ")" * Added test that illustrates what did not work when using overloaded operators and passing nil * Updated test for overloaded eaqal with using nil a == b replaced with a == nil to illustrated the problem when using nil directly in expression * Fixed the problem for untyped nil We use the trick only for untyped nil: https://play.golang.org/p/MyAztoZaqhx * Refactor call procedure Co-authored-by: wintermute-cds <[email protected]>
1 parent daa3bfc commit ab801d4

File tree

4 files changed

+40
-5
lines changed

4 files changed

+40
-5
lines changed

checker/checker.go

+4
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,10 @@ func (v *visitor) checkFunc(fn reflect.Type, method bool, node ast.Node, name st
409409
setTypeForIntegers(arg, t)
410410
}
411411

412+
if t == nil {
413+
continue
414+
}
415+
412416
if !t.AssignableTo(in) {
413417
panic(v.error(arg, "cannot use %v as argument (type %v) to call %v ", t, in, name))
414418
}

expr_test.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -873,10 +873,29 @@ func TestExpr_map_default_values_compile_check(t *testing.T) {
873873
}
874874
}
875875

876+
func TestExpr_calls_with_nil(t *testing.T) {
877+
env := map[string]interface{}{
878+
"equals": func(a, b interface{}) interface{} {
879+
return a == b
880+
},
881+
}
882+
883+
p, err := expr.Compile(
884+
"a == nil && equals(b, nil)",
885+
expr.Env(env),
886+
expr.Operator("==", "equals"),
887+
expr.AllowUndefinedVariables(),
888+
)
889+
require.NoError(t, err)
890+
891+
out, err := expr.Run(p, env)
892+
require.NoError(t, err)
893+
require.Equal(t, true, out)
894+
}
895+
876896
//
877897
// Mock types
878898
//
879-
880899
type mockEnv struct {
881900
Any interface{}
882901
Int, One, Two, Three int

internal/conf/operators_table.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ func FindSuitableOperatorOverload(fns []string, types TypesTable, l, r reflect.T
1616
firstArgType := fnType.Type.In(firstInIndex)
1717
secondArgType := fnType.Type.In(firstInIndex + 1)
1818

19-
firstArgumentFit := l == firstArgType || (firstArgType.Kind() == reflect.Interface && l.Implements(firstArgType))
20-
secondArgumentFit := r == secondArgType || (secondArgType.Kind() == reflect.Interface && r.Implements(secondArgType))
19+
firstArgumentFit := l == firstArgType || (firstArgType.Kind() == reflect.Interface && (l == nil || l.Implements(firstArgType)))
20+
secondArgumentFit := r == secondArgType || (secondArgType.Kind() == reflect.Interface && (r == nil || r.Implements(secondArgType)))
2121
if firstArgumentFit && secondArgumentFit {
2222
return fnType.Type.Out(0), fn, true
2323
}

vm/vm.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,13 @@ func (vm *VM) Run(program *Program, env interface{}) interface{} {
250250
call := vm.constant().(Call)
251251
in := make([]reflect.Value, call.Size)
252252
for i := call.Size - 1; i >= 0; i-- {
253-
in[i] = reflect.ValueOf(vm.pop())
253+
param := vm.pop()
254+
if param == nil {
255+
// In case of nil interface{} (nil type) use this hack,
256+
// otherwise reflect.Call will panic on zero value.
257+
param = reflect.ValueOf(&in).Elem()
258+
}
259+
in[i] = reflect.ValueOf(param)
254260
}
255261
out := fetchFn(env, call.Name).Call(in)
256262
vm.push(out[0].Interface())
@@ -268,7 +274,13 @@ func (vm *VM) Run(program *Program, env interface{}) interface{} {
268274
call := vm.constants[vm.arg()].(Call)
269275
in := make([]reflect.Value, call.Size)
270276
for i := call.Size - 1; i >= 0; i-- {
271-
in[i] = reflect.ValueOf(vm.pop())
277+
param := vm.pop()
278+
if param == nil {
279+
// In case of nil interface{} (nil type) use this hack,
280+
// otherwise reflect.Call will panic on zero value.
281+
param = reflect.ValueOf(&in).Elem()
282+
}
283+
in[i] = reflect.ValueOf(param)
272284
}
273285
out := fetchFn(vm.pop(), call.Name).Call(in)
274286
vm.push(out[0].Interface())

0 commit comments

Comments
 (0)