expr icon indicating copy to clipboard operation
expr copied to clipboard

expr.WithContext requires expr.Env?

Open swdunlop opened this issue 1 year ago • 2 comments

When using the expr.WithContext option, the named variable must also be provided at compilation time using expr.Env, otherwise the patch seems to fail to correctly update calls to functions that require a context. (I'm not exactly sure why this happens, but if it's necessary, perhaps we should document it?)

Reproduction:

env := map[string]any{
	`cancelled`: func(ctx context.Context) bool {
		return ctx.Err() != nil
	},
	`ctx`: context.Background(),
}
program, err := expr.Compile(`cancelled()`,
	expr.WithContext(`ctx`),
	// expr.Env(env),
)
if err != nil {
	panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
	panic(err)
}
fmt.Println(output)

With the expr.Env line commented out, this will fail (see playground), and with it uncommented, it succeeds (see playground). The error produced at runtime is:

reflect: Call with too few input arguments (1:1)
	 | cancelled()
	 | ^

swdunlop avatar Feb 08 '25 01:02 swdunlop

Since this error also happens if cancelled is not bound at compilation but is bound at run, playground, I assume the problem is that Patch cannot figure out the argument of the function at compilation time.

swdunlop avatar Feb 08 '25 02:02 swdunlop

Yes, exactly. If patcher is not able to figure out the types, it will not add context arg.

But there is a way without a patcher: https://go.dev/play/p/zKG4Ro4jlo4

package main

import (
	"context"
	"fmt"

	"github.com/expr-lang/expr"
)

type Env struct {
	ctx context.Context
	Var string
}

func (e Env) Fn(s string) (string, error) {
	if e.ctx.Err() != nil {
		return "", e.ctx.Err()
	}
	return s, nil
}

func main() {
	code := `Fn(Var)`

	program, err := expr.Compile(code, expr.Env(Env{}))
	if err != nil {
		panic(err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	env := Env{
		ctx: ctx,
		Var: "foo",
	}
	output, err := expr.Run(program, env)
	if err != nil {
		panic(err)
	}
	fmt.Println(output)

	cancel()
	_, err = expr.Run(program, env)
	if err != nil {
		panic(err)
	}
}

antonmedv avatar Mar 10 '25 14:03 antonmedv