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
12 changes: 7 additions & 5 deletions conf/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,24 @@ func EnvWithCache(c *Cache, env any) Nature {
v := reflect.ValueOf(env)
t := v.Type()

switch deref.Value(v).Kind() {
d := deref.Value(v)

switch d.Kind() {
case reflect.Struct:
n := c.FromType(t)
n.Strict = true
return n

case reflect.Map:
n := c.FromType(v.Type())
n := c.FromType(d.Type())
if n.TypeData == nil {
n.TypeData = new(TypeData)
}
n.Strict = true
n.Fields = make(map[string]Nature, v.Len())
n.Fields = make(map[string]Nature, d.Len())

for _, key := range v.MapKeys() {
elem := v.MapIndex(key)
for _, key := range d.MapKeys() {
elem := d.MapIndex(key)
if !elem.IsValid() || !elem.CanInterface() {
panic(fmt.Sprintf("invalid map value: %s", key))
}
Expand Down
48 changes: 48 additions & 0 deletions test/issues/825/issue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package issue_test

import (
"testing"

"github.com/expr-lang/expr"
"github.com/expr-lang/expr/internal/testify/assert"
"github.com/expr-lang/expr/internal/testify/require"
)

// TestIssue825 verifies that passing a pointer to a map as the environment is
// dereferenced instead of panicking.
//
// conf.EnvWithCache selected the map branch using the dereferenced value's
// kind, but then read the map keys/length from the original (pointer) value,
// panicking with:
//
// reflect: call of reflect.Value.Len on ptr to non-array Value
func TestIssue825(t *testing.T) {
m := map[string]any{"foo": 42}

program, err := expr.Compile("foo + 1", expr.Env(&m))
require.NoError(t, err)

out, err := expr.Run(program, m)
require.NoError(t, err)
assert.Equal(t, 43, out)
}

// TestIssue825_Strict verifies that a pointer-to-map env keeps the strict-mode
// and element-type information of the dereferenced map, i.e. it behaves exactly
// like compiling with the map value itself.
func TestIssue825_Strict(t *testing.T) {
m := map[string]int{"a": 1}

// Unknown names are rejected (strict), just like a plain map env.
_, err := expr.Compile("unknown + 1", expr.Env(&m))
require.Error(t, err)
require.Contains(t, err.Error(), "unknown name unknown")

// The element type (int) is inferred from the dereferenced map.
program, err := expr.Compile("a + 1", expr.Env(&m))
require.NoError(t, err)

out, err := expr.Run(program, m)
require.NoError(t, err)
assert.Equal(t, 2, out)
}