diff --git a/checker/checker.go b/checker/checker.go index 7db986926..76a221e3b 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -409,6 +409,10 @@ func (v *visitor) checkFunc(fn reflect.Type, method bool, node ast.Node, name st setTypeForIntegers(arg, t) } + if t == nil { + continue + } + if !t.AssignableTo(in) { panic(v.error(arg, "cannot use %v as argument (type %v) to call %v ", t, in, name)) } diff --git a/expr_test.go b/expr_test.go index ab9002ddb..df929245e 100644 --- a/expr_test.go +++ b/expr_test.go @@ -873,10 +873,29 @@ func TestExpr_map_default_values_compile_check(t *testing.T) { } } +func TestExpr_calls_with_nil(t *testing.T) { + env := map[string]interface{}{ + "equals": func(a, b interface{}) interface{} { + return a == b + }, + } + + p, err := expr.Compile( + "a == nil && equals(b, nil)", + expr.Env(env), + expr.Operator("==", "equals"), + expr.AllowUndefinedVariables(), + ) + require.NoError(t, err) + + out, err := expr.Run(p, env) + require.NoError(t, err) + require.Equal(t, true, out) +} + // // Mock types // - type mockEnv struct { Any interface{} Int, One, Two, Three int diff --git a/internal/conf/operators_table.go b/internal/conf/operators_table.go index 5368366db..0ceb84400 100644 --- a/internal/conf/operators_table.go +++ b/internal/conf/operators_table.go @@ -16,8 +16,8 @@ func FindSuitableOperatorOverload(fns []string, types TypesTable, l, r reflect.T firstArgType := fnType.Type.In(firstInIndex) secondArgType := fnType.Type.In(firstInIndex + 1) - firstArgumentFit := l == firstArgType || (firstArgType.Kind() == reflect.Interface && l.Implements(firstArgType)) - secondArgumentFit := r == secondArgType || (secondArgType.Kind() == reflect.Interface && r.Implements(secondArgType)) + firstArgumentFit := l == firstArgType || (firstArgType.Kind() == reflect.Interface && (l == nil || l.Implements(firstArgType))) + secondArgumentFit := r == secondArgType || (secondArgType.Kind() == reflect.Interface && (r == nil || r.Implements(secondArgType))) if firstArgumentFit && secondArgumentFit { return fnType.Type.Out(0), fn, true } diff --git a/vm/vm.go b/vm/vm.go index 5f6396ea0..a230da04e 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -250,7 +250,13 @@ func (vm *VM) Run(program *Program, env interface{}) interface{} { call := vm.constant().(Call) in := make([]reflect.Value, call.Size) for i := call.Size - 1; i >= 0; i-- { - in[i] = reflect.ValueOf(vm.pop()) + param := vm.pop() + if param == nil { + // In case of nil interface{} (nil type) use this hack, + // otherwise reflect.Call will panic on zero value. + param = reflect.ValueOf(&in).Elem() + } + in[i] = reflect.ValueOf(param) } out := fetchFn(env, call.Name).Call(in) vm.push(out[0].Interface()) @@ -268,7 +274,13 @@ func (vm *VM) Run(program *Program, env interface{}) interface{} { call := vm.constants[vm.arg()].(Call) in := make([]reflect.Value, call.Size) for i := call.Size - 1; i >= 0; i-- { - in[i] = reflect.ValueOf(vm.pop()) + param := vm.pop() + if param == nil { + // In case of nil interface{} (nil type) use this hack, + // otherwise reflect.Call will panic on zero value. + param = reflect.ValueOf(&in).Elem() + } + in[i] = reflect.ValueOf(param) } out := fetchFn(vm.pop(), call.Name).Call(in) vm.push(out[0].Interface())