From 2a3b14b554b83d49e0028385832d88eb76a4cbc3 Mon Sep 17 00:00:00 2001 From: AdamMagued Date: Fri, 2 Oct 2026 01:01:06 +0300 Subject: [PATCH] Add type checking for variadic function arguments In functionEntry.resolveArgs, variadic functions only validated that len(arguments) >= len(e.arguments) without performing type checking on either fixed or variadic arguments. When passing arguments of unexpected types (such as nil, strings, or numbers) to functions like merge(), downstream type assertions in handlers (e.g. jpfMerge) panicked at runtime instead of returning an evaluation error. Iterate over arguments in resolveArgs for variadic functions, checking each argument against its corresponding argSpec or the trailing variadic argSpec. Fixes #72 --- functions.go | 11 +++++++++++ interpreter_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/functions.go b/functions.go index e9770e8..4dad64a 100644 --- a/functions.go +++ b/functions.go @@ -342,6 +342,17 @@ func (e *functionEntry) resolveArgs(arguments []interface{}) ([]interface{}, err if len(arguments) < len(e.arguments) { return nil, errors.New("invalid arity") } + for i, userArg := range arguments { + var spec argSpec + if i < len(e.arguments)-1 { + spec = e.arguments[i] + } else { + spec = e.arguments[len(e.arguments)-1] + } + if err := spec.typeCheck(userArg); err != nil { + return nil, err + } + } return arguments, nil } diff --git a/interpreter_test.go b/interpreter_test.go index 76e0c5d..e0e3bf2 100644 --- a/interpreter_test.go +++ b/interpreter_test.go @@ -181,6 +181,40 @@ func TestCanSupportSliceOfStructsWithFunctions(t *testing.T) { assert.Equal(result.(float64), 2.0) } +func TestVariadicFunctionTypeChecking(t *testing.T) { + assert := assert.New(t) + + // Issue #72: merge should return error instead of panicking when an argument is not an object. + data := map[string]interface{}{"message": "foo"} + result, err := Search("merge({message: message}, contextMap)", data) + assert.Nil(result) + assert.NotNil(err) + + invalidExpressions := []string{ + "merge(`\"foo\"`)", + "merge(`123`)", + "merge(`[1, 2]`)", + "merge(`true`)", + "merge(`{\"a\": 1}`, `\"b\"`)", + "merge(`{\"a\": 1}`, `null`)", + } + for _, expr := range invalidExpressions { + res, err := Search(expr, nil) + assert.Nil(res) + assert.NotNil(err) + } + + // merge with valid objects should still succeed + res, err := Search("merge(`{\"a\": 1}`, `{\"b\": 2}`)", nil) + assert.Nil(err) + assert.Equal(map[string]interface{}{"a": float64(1), "b": float64(2)}, res) + + // not_null with variadic arguments should still succeed + res, err = Search("not_null(foo, bar, `\"default\"`)", map[string]interface{}{}) + assert.Nil(err) + assert.Equal("default", res) +} + func BenchmarkInterpretSingleFieldStruct(b *testing.B) { assert := assert.New(b) intr := newInterpreter()