diff --git a/checker/checker_test.go b/checker/checker_test.go index 0639f1f4..e5691b76 100644 --- a/checker/checker_test.go +++ b/checker/checker_test.go @@ -1074,7 +1074,7 @@ func TestCheck_types(t *testing.T) { env := types.Map{ "foo": types.StrictMap{ "bar": types.Map{ - "baz": "", + "baz": types.String, }, }, } diff --git a/checker/nature/of.go b/checker/nature/of.go deleted file mode 100644 index 93235f9c..00000000 --- a/checker/nature/of.go +++ /dev/null @@ -1,47 +0,0 @@ -package nature - -import ( - "fmt" - "reflect" - - "github.com/expr-lang/expr/types" -) - -func Of(value any) Nature { - if value == nil { - return Nature{Nil: true} - } - - v := reflect.ValueOf(value) - - switch v.Kind() { - case reflect.Map: - _, strict := value.(types.StrictMap) - fields := make(map[string]Nature, v.Len()) - for _, key := range v.MapKeys() { - elem := v.MapIndex(key) - if !elem.IsValid() || !elem.CanInterface() { - panic(fmt.Sprintf("invalid map value: %s", key)) - } - face := elem.Interface() - switch face.(type) { - case types.Map, types.StrictMap: - fields[key.String()] = Of(face) - default: - if face == nil { - fields[key.String()] = Nature{Nil: true} - continue - } - fields[key.String()] = Nature{Type: reflect.TypeOf(face)} - - } - } - return Nature{ - Type: v.Type(), - Fields: fields, - Strict: strict, - } - } - - return Nature{Type: v.Type()} -} diff --git a/conf/config.go b/conf/config.go index 77bb2a67..8a6ee70e 100644 --- a/conf/config.go +++ b/conf/config.go @@ -7,7 +7,6 @@ import ( "github.com/expr-lang/expr/ast" "github.com/expr-lang/expr/builtin" "github.com/expr-lang/expr/checker/nature" - "github.com/expr-lang/expr/types" "github.com/expr-lang/expr/vm/runtime" ) @@ -53,11 +52,7 @@ func New(env any) *Config { func (c *Config) WithEnv(env any) { c.Strict = true c.EnvObject = env - c.Env = nature.Of(env) - c.Env.Strict = true // To keep backward compatibility with expr.AllowUndefinedVariables() - if _, ok := env.(types.Map); ok { - c.Env.Strict = false - } + c.Env = Env(env) } func (c *Config) ConstExpr(name string) { diff --git a/conf/env.go b/conf/env.go new file mode 100644 index 00000000..82e6a93d --- /dev/null +++ b/conf/env.go @@ -0,0 +1,73 @@ +package conf + +import ( + "fmt" + "reflect" + + . "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/internal/deref" + "github.com/expr-lang/expr/types" +) + +func Env(env any) Nature { + if env == nil { + return Nature{ + Type: reflect.TypeOf(map[string]any{}), + Strict: true, + } + } + + switch env := env.(type) { + case types.Map: + return env.Nature() + + case types.StrictMap: + return env.Nature() + } + + v := reflect.ValueOf(env) + d := deref.Value(v) + + switch d.Kind() { + case reflect.Struct: + return Nature{ + Type: v.Type(), + Strict: true, + } + + case reflect.Map: + n := Nature{ + Type: v.Type(), + Fields: make(map[string]Nature, v.Len()), + } + + for _, key := range v.MapKeys() { + elem := v.MapIndex(key) + if !elem.IsValid() || !elem.CanInterface() { + panic(fmt.Sprintf("invalid map value: %s", key)) + } + + face := elem.Interface() + + switch face.(type) { + case types.Map: + n.Fields[key.String()] = face.(types.Map).Nature() + + case types.StrictMap: + n.Fields[key.String()] = face.(types.StrictMap).Nature() + + default: + if face == nil { + n.Fields[key.String()] = Nature{Nil: true} + continue + } + n.Fields[key.String()] = Nature{Type: reflect.TypeOf(face)} + } + + } + + return n + } + + panic(fmt.Sprintf("unknown type %T", env)) +} diff --git a/docgen/docgen.go b/docgen/docgen.go index d9abcf0e..1844f23b 100644 --- a/docgen/docgen.go +++ b/docgen/docgen.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/conf" "github.com/expr-lang/expr/internal/deref" ) @@ -84,7 +85,7 @@ func CreateDoc(i any) *Context { PkgPath: deref.Type(reflect.TypeOf(i)).PkgPath(), } - for name, t := range nature.Of(i).All() { + for name, t := range conf.Env(i).All() { if _, ok := c.Variables[Identifier(name)]; ok { continue } diff --git a/expr_test.go b/expr_test.go index e71393f3..4f2f829d 100644 --- a/expr_test.go +++ b/expr_test.go @@ -2705,7 +2705,7 @@ func TestExpr_nil_op_str(t *testing.T) { func TestExpr_env_types_map(t *testing.T) { envTypes := types.Map{ "foo": types.StrictMap{ - "bar": "value", + "bar": types.String, }, } @@ -2726,7 +2726,7 @@ func TestExpr_env_types_map(t *testing.T) { func TestExpr_env_types_map_error(t *testing.T) { envTypes := types.Map{ "foo": types.StrictMap{ - "bar": "value", + "bar": types.String, }, } diff --git a/types/types.go b/types/types.go index 9057d14a..e707e18c 100644 --- a/types/types.go +++ b/types/types.go @@ -1,5 +1,74 @@ package types -type Map map[string]any +import ( + "reflect" -type StrictMap map[string]any + . "github.com/expr-lang/expr/checker/nature" +) + +func TypeOf(v any) Type { + return rtype{t: reflect.TypeOf(v)} +} + +var ( + Int = TypeOf(0) + Int8 = TypeOf(int8(0)) + Int16 = TypeOf(int16(0)) + Int32 = TypeOf(int32(0)) + Int64 = TypeOf(int64(0)) + Uint = TypeOf(uint(0)) + Uint8 = TypeOf(uint8(0)) + Uint16 = TypeOf(uint16(0)) + Uint32 = TypeOf(uint32(0)) + Uint64 = TypeOf(uint64(0)) + Float = TypeOf(float32(0)) + Float64 = TypeOf(float64(0)) + String = TypeOf("") + Bool = TypeOf(true) + Nil = nilType{} +) + +type Type interface { + Nature() Nature +} + +type nilType struct{} + +func (nilType) Nature() Nature { + return Nature{Nil: true} +} + +type rtype struct { + t reflect.Type +} + +func (r rtype) Nature() Nature { + return Nature{Type: r.t} +} + +type Map map[string]Type + +func (m Map) Nature() Nature { + nt := Nature{ + Type: reflect.TypeOf(map[string]any{}), + Fields: make(map[string]Nature, len(m)), + } + for k, v := range m { + nt.Fields[k] = v.Nature() + } + return nt +} + +type StrictMap map[string]Type + +func (m StrictMap) Nature() Nature { + nt := Nature{ + Type: reflect.TypeOf(map[string]any{}), + Fields: make(map[string]Nature, len(m)), + Strict: true, + } + for k, v := range m { + nt.Fields[k] = v.Nature() + } + return nt +}