Skip to content

find, findIndex, first and get are type-checked as the element type where they return nil #989

Description

@DRMacIver

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions