Make sure get() returns nil for map[string]string on missing keys

Fixes #723
This commit is contained in:
Anton Medvedev
2025-03-08 17:28:39 +01:00
parent 41da963d55
commit 80eb95bf5f
5 changed files with 92 additions and 70 deletions
+1 -8
View File
@@ -627,14 +627,7 @@ var Builtins = []*Function{
},
{
Name: "get",
Func: func(args ...any) (out any, err error) {
defer func() {
if r := recover(); r != nil {
return
}
}()
return runtime.Fetch(args[0], args[1]), nil
},
Func: get,
},
{
Name: "take",
+67
View File
@@ -8,6 +8,7 @@ import (
"unicode/utf8"
"github.com/expr-lang/expr/internal/deref"
"github.com/expr-lang/expr/vm/runtime"
)
func Len(x any) any {
@@ -367,3 +368,69 @@ func flatten(arg reflect.Value) []any {
}
return ret
}
func get(params ...any) (out any, err error) {
from := params[0]
i := params[1]
v := reflect.ValueOf(from)
if v.Kind() == reflect.Invalid {
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))
}
// Methods can be defined on any type.
if v.NumMethod() > 0 {
if methodName, ok := i.(string); ok {
method := v.MethodByName(methodName)
if method.IsValid() {
return method.Interface(), nil
}
}
}
v = deref.Value(v)
i = deref.Deref(i)
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
index := runtime.ToInt(i)
l := v.Len()
if index < 0 {
index = l + index
}
if 0 <= index && index < l {
value := v.Index(index)
if value.IsValid() {
return value.Interface(), nil
}
}
case reflect.Map:
var value reflect.Value
if i == nil {
value = v.MapIndex(reflect.Zero(v.Type().Key()))
} else {
value = v.MapIndex(reflect.ValueOf(i))
}
if value.IsValid() {
return value.Interface(), nil
}
case reflect.Struct:
fieldName := i.(string)
value := v.FieldByNameFunc(func(name string) bool {
field, _ := v.Type().FieldByName(name)
if field.Tag.Get("expr") == fieldName {
return true
}
return name == fieldName
})
if value.IsValid() {
return value.Interface(), nil
}
}
// Main difference from runtime.Fetch
// is that we return `nil` instead of panic.
return nil, nil
}
-31
View File
@@ -7088,58 +7088,33 @@ get(false ? i32 : list, i64)
get(false ? i64 : true, f64)
get(false ? score : ok, trimSuffix("bar", "bar"))
get(filter(list, true), i)
get(groupBy(array, "bar"), add)
get(groupBy(array, "bar"), i)
get(groupBy(array, "bar"), ok)
get(groupBy(array, "foo"), half)
get(groupBy(array, #), add)
get(groupBy(array, #), array)
get(groupBy(array, #), f32)
get(groupBy(array, #), f64)
get(groupBy(array, #), foo)
get(groupBy(array, #), greet)
get(groupBy(array, #), half)
get(groupBy(array, #), i)
get(groupBy(array, #), i64 * 0.5)
get(groupBy(array, #), i64)
get(groupBy(array, #)?.String, i64)
get(groupBy(array, 0.5), add)
get(groupBy(array, 0.5), div)
get(groupBy(array, 0.5), ok)
get(groupBy(array, 1), add)
get(groupBy(array, 1), f32)
get(groupBy(array, f64), i64)
get(groupBy(array, false), findIndex(array, ok))
get(groupBy(array, i), ok)
get(groupBy(array, i), score)
get(groupBy(array, i64), add)
get(groupBy(array, ok), add)
get(groupBy(array, true), i32)
get(groupBy(list, "bar"), i32)
get(groupBy(list, "foo"), greet)
get(groupBy(list, #), div)
get(groupBy(list, #), f32 <= f64)
get(groupBy(list, #), f32)
get(groupBy(list, #), foo)
get(groupBy(list, #), greet)
get(groupBy(list, #), half)
get(groupBy(list, #), i)
get(groupBy(list, #), i32)
get(groupBy(list, #), i64)
get(groupBy(list, #), int(f32))
get(groupBy(list, #), list)
get(groupBy(list, #), reduce(array, foo))
get(groupBy(list, #), score(1))
get(groupBy(list, #), score)
get(groupBy(list, 0.5), array)
get(groupBy(list, 0.5), div)
get(groupBy(list, 0.5), ok)
get(groupBy(list, 1), i32)
get(groupBy(list, f32), greet)
get(groupBy(list, foo), add)
get(groupBy(list, foo), i32)
get(groupBy(list, ok), greet)
get(groupBy(list, ok), list)
get(i .. 1, 1 * 1)
get(i .. i32, i)
get(i32 .. i, i64)
@@ -7200,12 +7175,10 @@ get(list, max(i32))
get(list, min(i32))
get(list, min(i64))
get(list, ok ? 0.5 : "foo")
get(list, ok ? array : "foo")
get(list, reduce(list, i64))
get(list, score(1))
get(list, score(i))
get(list, true ? i : i32)
get(list, {"foo": i32}?.i)
get(list[i64:1], i)
get(map(array, #), i)
get(map(array, #), i32)
@@ -7221,18 +7194,15 @@ get(map(list, 1), i32)
get(map(list, array), i)
get(map(list, i32), i64)
get(map(list, i64), i64)
get(ok ? "bar" : false, array)
get(ok ? 1 : "bar", f32)
get(ok ? add : f32, array)
get(ok ? f64 : div, i64)
get(ok ? false : list, f32 > i)
get(ok ? foo : 0.5, div)
get(ok ? half : i32, i)
get(ok ? half : ok, f64)
get(ok ? i : 0.5, half)
get(ok ? i32 : half, f64)
get(ok ? i64 : foo, f32)
get(ok ? list : "foo", add)
get(ok ? list : i32, f32)
get(ok ? ok : div, greet)
get(ok ? score : f64, i)
@@ -7241,7 +7211,6 @@ get(reduce(list, array), i32)
get(sort(array), i32)
get(take(list, i), i64)
get(true ? "bar" : ok, score(i))
get(true ? "foo" : half, list)
get(true ? 0.5 : i32, array)
get(true ? f32 : 0.5, ok)
get(true ? false : foo, i64 > 0.5)
+24
View File
@@ -0,0 +1,24 @@
package issue_test
import (
"testing"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/internal/testify/require"
)
func TestIssue723(t *testing.T) {
emptyMap := make(map[string]string)
env := map[string]interface{}{
"empty_map": emptyMap,
}
code := `get(empty_map, "non_existing_key")`
program, err := expr.Compile(code, expr.Env(env))
require.NoError(t, err)
output, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, nil, output)
}
-31
View File
@@ -7088,58 +7088,33 @@ get(false ? i32 : list, i64)
get(false ? i64 : true, f64)
get(false ? score : ok, trimSuffix("bar", "bar"))
get(filter(list, true), i)
get(groupBy(array, "bar"), add)
get(groupBy(array, "bar"), i)
get(groupBy(array, "bar"), ok)
get(groupBy(array, "foo"), half)
get(groupBy(array, #), add)
get(groupBy(array, #), array)
get(groupBy(array, #), f32)
get(groupBy(array, #), f64)
get(groupBy(array, #), foo)
get(groupBy(array, #), greet)
get(groupBy(array, #), half)
get(groupBy(array, #), i)
get(groupBy(array, #), i64 * 0.5)
get(groupBy(array, #), i64)
get(groupBy(array, #)?.String, i64)
get(groupBy(array, 0.5), add)
get(groupBy(array, 0.5), div)
get(groupBy(array, 0.5), ok)
get(groupBy(array, 1), add)
get(groupBy(array, 1), f32)
get(groupBy(array, f64), i64)
get(groupBy(array, false), findIndex(array, ok))
get(groupBy(array, i), ok)
get(groupBy(array, i), score)
get(groupBy(array, i64), add)
get(groupBy(array, ok), add)
get(groupBy(array, true), i32)
get(groupBy(list, "bar"), i32)
get(groupBy(list, "foo"), greet)
get(groupBy(list, #), div)
get(groupBy(list, #), f32 <= f64)
get(groupBy(list, #), f32)
get(groupBy(list, #), foo)
get(groupBy(list, #), greet)
get(groupBy(list, #), half)
get(groupBy(list, #), i)
get(groupBy(list, #), i32)
get(groupBy(list, #), i64)
get(groupBy(list, #), int(f32))
get(groupBy(list, #), list)
get(groupBy(list, #), reduce(array, foo))
get(groupBy(list, #), score(1))
get(groupBy(list, #), score)
get(groupBy(list, 0.5), array)
get(groupBy(list, 0.5), div)
get(groupBy(list, 0.5), ok)
get(groupBy(list, 1), i32)
get(groupBy(list, f32), greet)
get(groupBy(list, foo), add)
get(groupBy(list, foo), i32)
get(groupBy(list, ok), greet)
get(groupBy(list, ok), list)
get(i .. 1, 1 * 1)
get(i .. i32, i)
get(i32 .. i, i64)
@@ -7200,12 +7175,10 @@ get(list, max(i32))
get(list, min(i32))
get(list, min(i64))
get(list, ok ? 0.5 : "foo")
get(list, ok ? array : "foo")
get(list, reduce(list, i64))
get(list, score(1))
get(list, score(i))
get(list, true ? i : i32)
get(list, {"foo": i32}?.i)
get(list[i64:1], i)
get(map(array, #), i)
get(map(array, #), i32)
@@ -7221,18 +7194,15 @@ get(map(list, 1), i32)
get(map(list, array), i)
get(map(list, i32), i64)
get(map(list, i64), i64)
get(ok ? "bar" : false, array)
get(ok ? 1 : "bar", f32)
get(ok ? add : f32, array)
get(ok ? f64 : div, i64)
get(ok ? false : list, f32 > i)
get(ok ? foo : 0.5, div)
get(ok ? half : i32, i)
get(ok ? half : ok, f64)
get(ok ? i : 0.5, half)
get(ok ? i32 : half, f64)
get(ok ? i64 : foo, f32)
get(ok ? list : "foo", add)
get(ok ? list : i32, f32)
get(ok ? ok : div, greet)
get(ok ? score : f64, i)
@@ -7241,7 +7211,6 @@ get(reduce(list, array), i32)
get(sort(array), i32)
get(take(list, i), i64)
get(true ? "bar" : ok, score(i))
get(true ? "foo" : half, list)
get(true ? 0.5 : i32, array)
get(true ? f32 : 0.5, ok)
get(true ? false : foo, i64 > 0.5)