first of an empty []int, get with an out-of-range index and find/findIndex with no match are all type-checked as int, so AsInt() with WarnOnAny() accepts them at compile time and Run then fails with invalid operation: int(<nil>); the same find over a []any array is refused at compile time:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]any{
"ints": []int{1, 2, 3},
"anys": []any{1, 2, 3},
"empty": []int{},
}
for _, src := range []string{
"find(anys, # > 5)",
"find(ints, # > 5)",
"findIndex(ints, # > 5)",
"first(empty)",
"get(ints, 9)",
} {
program, err := expr.Compile(src, expr.Env(env), expr.AsInt(), expr.WarnOnAny())
if err != nil {
fmt.Printf("%-23s compile error: %v\n", src, err)
continue
}
_, err = expr.Run(program, env)
fmt.Printf("%-23s compiled as %v; Run: %v\n", src, program.Node().Type(), err)
}
}
Output:
find(anys, # > 5) compile error: expected int, but got interface {}
find(ints, # > 5) compiled as int; Run: invalid operation: int(<nil>) (1:1)
| find(ints, # > 5)
| ^
findIndex(ints, # > 5) compiled as int; Run: invalid operation: int(<nil>) (1:1)
| findIndex(ints, # > 5)
| ^
first(empty) compiled as int; Run: invalid operation: int(<nil>) (1:1)
| first(empty)
| ^
get(ints, 9) compiled as int; Run: invalid operation: int(<nil>) (1:1)
| get(ints, 9)
| ^
first and get are documented to return nil for an empty array and an out-of-range index (language-definition.md), and WarnOnAny() is documented to make the type checker return an error when the return type is any (configuration.md). With a []int array the static type of these calls is int, so the check passes and the nil only shows up when the program runs. I would expect these to be caught at compile time the way the []any case is, or to have a static type that admits nil.
Tested on expr v1.17.8 and on current master (4b31df3), Go 1.27.1.
BTW, this was found by an automated program that writes property-based tests for various open source projects using hegel (but it has been reviewed by hand before reporting). We've also potentially found (but not yet hand validated) 3 other bugs in expr. You can see the tests at https://github.com/hegeldev/hegel-zoo/tree/main/targets/go/expr. Let us know if you would like us to file the other bugs found and/or contribute the tests. NB the tests are currently LLM generated and probably not yet suitable for inclusion as is, but we're happy to help get them into a better state if you want them.
firstof an empty[]int,getwith an out-of-range index andfind/findIndexwith no match are all type-checked asint, soAsInt()withWarnOnAny()accepts them at compile time andRunthen fails withinvalid operation: int(<nil>); the samefindover a[]anyarray is refused at compile time:Output:
firstandgetare documented to returnnilfor an empty array and an out-of-range index (language-definition.md), andWarnOnAny()is documented to make the type checker return an error when the return type isany(configuration.md). With a[]intarray the static type of these calls isint, so the check passes and the nil only shows up when the program runs. I would expect these to be caught at compile time the way the[]anycase is, or to have a static type that admits nil.Tested on expr v1.17.8 and on current master (4b31df3), Go 1.27.1.
BTW, this was found by an automated program that writes property-based tests for various open source projects using hegel (but it has been reviewed by hand before reporting). We've also potentially found (but not yet hand validated) 3 other bugs in expr. You can see the tests at https://github.com/hegeldev/hegel-zoo/tree/main/targets/go/expr. Let us know if you would like us to file the other bugs found and/or contribute the tests. NB the tests are currently LLM generated and probably not yet suitable for inclusion as is, but we're happy to help get them into a better state if you want them.