Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,8 @@ var Builtins = []*Function{
case reflect.Interface:
return anyType, nil
case reflect.Slice, reflect.Array:
return args[0].Elem(), nil
// Empty collection returns nil; type as any so WarnOnAny catches it.
return anyType, nil
}
return anyType, fmt.Errorf("cannot get first element from %s", args[0])
},
Expand All @@ -634,7 +635,8 @@ var Builtins = []*Function{
case reflect.Interface:
return anyType, nil
case reflect.Slice, reflect.Array:
return args[0].Elem(), nil
// Empty collection returns nil; type as any so WarnOnAny catches it.
return anyType, nil
}
return anyType, fmt.Errorf("cannot get last element from %s", args[0])
},
Expand Down
12 changes: 6 additions & 6 deletions builtin/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,12 @@ func TestBuiltin_types(t *testing.T) {
input string
want reflect.Kind
}{
{`get(ArrayOfString, 0)`, reflect.String},
{`get(ArrayOfInt, 0)`, reflect.Int},
{`first(ArrayOfString)`, reflect.String},
{`first(ArrayOfInt)`, reflect.Int},
{`last(ArrayOfString)`, reflect.String},
{`last(ArrayOfInt)`, reflect.Int},
{`get(ArrayOfString, 0)`, reflect.Interface}, // nilable
{`get(ArrayOfInt, 0)`, reflect.Interface},
{`first(ArrayOfString)`, reflect.Interface},
{`first(ArrayOfInt)`, reflect.Interface},
{`last(ArrayOfString)`, reflect.Interface},
{`last(ArrayOfInt)`, reflect.Interface},
{`get($env, 'str')`, reflect.String},
{`get($env, 'num')`, reflect.Int},
{`get($env, 'ArrayOfString')`, reflect.Slice},
Expand Down
15 changes: 8 additions & 7 deletions checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,10 +842,8 @@ func (v *Checker) builtinNode(node *ast.BuiltinNode) Nature {
if !predicateOut.IsBool() && !predicateOut.IsUnknown(&v.config.NtCache) {
return v.error(node.Arguments[1], "predicate should return boolean (got %s)", predicateOut.String())
}
if collection.IsUnknown(&v.config.NtCache) {
return Nature{}
}
return collection.Elem(&v.config.NtCache)
// May return nil when nothing matches; type as any so WarnOnAny catches it.
return v.config.NtCache.FromType(anyType)
}
return v.error(node.Arguments[1], "predicate should has one input and one output param")

Expand All @@ -868,7 +866,8 @@ func (v *Checker) builtinNode(node *ast.BuiltinNode) Nature {
if !predicateOut.IsBool() && !predicateOut.IsUnknown(&v.config.NtCache) {
return v.error(node.Arguments[1], "predicate should return boolean (got %s)", predicateOut.String())
}
return v.config.NtCache.FromType(intType)
// Returns nil (not -1) when nothing matches; type as any so WarnOnAny catches it.
return v.config.NtCache.FromType(anyType)
}
return v.error(node.Arguments[1], "predicate should has one input and one output param")

Expand Down Expand Up @@ -992,12 +991,14 @@ func (v *Checker) checkBuiltinGet(node *ast.BuiltinNode) Nature {
if !prop.IsInteger && !prop.IsUnknown(&v.config.NtCache) {
return v.error(node.Arguments[1], "non-integer slice index %s", prop.String())
}
return base.Elem(&v.config.NtCache)
// Out-of-range get returns nil; type as any so WarnOnAny catches it.
return v.config.NtCache.FromType(anyType)
case reflect.Map:
if !prop.AssignableTo(base.Key(&v.config.NtCache)) && !prop.IsUnknown(&v.config.NtCache) {
return v.error(node.Arguments[1], "cannot use %s to get an element from %s", prop.String(), base.String())
}
return base.Elem(&v.config.NtCache)
// Missing key returns nil; type as any so WarnOnAny catches it.
return v.config.NtCache.FromType(anyType)
}
return v.error(node.Arguments[0], "type %v does not support indexing", base.String())
}
Expand Down
19 changes: 19 additions & 0 deletions checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1183,3 +1183,22 @@ func TestCheck_types(t *testing.T) {
})
}
}

func TestCheck_NilableBuiltinsWarnOnAny(t *testing.T) {
env := map[string]any{
"ints": []int{1, 2, 3},
"empty": []int{},
}
for _, src := range []string{
"find(ints, # > 5)",
"findIndex(ints, # > 5)",
"first(empty)",
"get(ints, 9)",
} {
t.Run(src, func(t *testing.T) {
_, err := expr.Compile(src, expr.Env(env), expr.AsInt(), expr.WarnOnAny())
require.Error(t, err)
require.Contains(t, err.Error(), "expected int")
})
}
}