mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Improve type checker for named types
This commit is contained in:
+1
-1
@@ -164,7 +164,7 @@ var Builtins = []*Function{
|
||||
if len(args) != 1 {
|
||||
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
|
||||
}
|
||||
switch kind(args[0]) {
|
||||
switch kind(deref.Type(args[0])) {
|
||||
case reflect.Interface:
|
||||
return integerType, nil
|
||||
case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
|
||||
@@ -638,3 +638,16 @@ func Test_int_unwraps_underlying_value(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, out)
|
||||
}
|
||||
|
||||
func TestBuiltin_int_with_deref(t *testing.T) {
|
||||
x := 42
|
||||
env := map[string]any{
|
||||
"x": &x,
|
||||
}
|
||||
program, err := expr.Compile(`int(x)`, expr.Env(env))
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err := expr.Run(program, env)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 42, out)
|
||||
}
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ func Round(x any) any {
|
||||
}
|
||||
|
||||
func Int(x any) any {
|
||||
switch x := x.(type) {
|
||||
switch x := deref.Deref(x).(type) {
|
||||
case float32:
|
||||
return int(x)
|
||||
case float64:
|
||||
|
||||
+20
-2
@@ -319,7 +319,10 @@ func (v *checker) BinaryNode(node *ast.BinaryNode) Nature {
|
||||
if isTime(l) && isTime(r) {
|
||||
return boolNature
|
||||
}
|
||||
if or(l, r, isNumber, isString, isTime) {
|
||||
if isDuration(l) && isDuration(r) {
|
||||
return boolNature
|
||||
}
|
||||
if or(l, r, isNumber, isString, isTime, isDuration) {
|
||||
return boolNature
|
||||
}
|
||||
|
||||
@@ -333,6 +336,9 @@ func (v *checker) BinaryNode(node *ast.BinaryNode) Nature {
|
||||
if isTime(l) && isDuration(r) {
|
||||
return timeNature
|
||||
}
|
||||
if isDuration(l) && isDuration(r) {
|
||||
return durationNature
|
||||
}
|
||||
if or(l, r, isNumber, isTime, isDuration) {
|
||||
return unknown
|
||||
}
|
||||
@@ -341,7 +347,16 @@ func (v *checker) BinaryNode(node *ast.BinaryNode) Nature {
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return combined(l, r)
|
||||
}
|
||||
if or(l, r, isNumber) {
|
||||
if isNumber(l) && isDuration(r) {
|
||||
return durationNature
|
||||
}
|
||||
if isDuration(l) && isNumber(r) {
|
||||
return durationNature
|
||||
}
|
||||
if isDuration(l) && isDuration(r) {
|
||||
return durationNature
|
||||
}
|
||||
if or(l, r, isNumber, isDuration) {
|
||||
return unknown
|
||||
}
|
||||
|
||||
@@ -382,6 +397,9 @@ func (v *checker) BinaryNode(node *ast.BinaryNode) Nature {
|
||||
if isDuration(l) && isTime(r) {
|
||||
return timeNature
|
||||
}
|
||||
if isDuration(l) && isDuration(r) {
|
||||
return durationNature
|
||||
}
|
||||
if or(l, r, isNumber, isString, isTime, isDuration) {
|
||||
return unknown
|
||||
}
|
||||
|
||||
@@ -161,6 +161,13 @@ func (n Nature) FieldByName(name string) (Nature, bool) {
|
||||
return Nature{Type: field.Type, FieldIndex: field.Index}, ok
|
||||
}
|
||||
|
||||
func (n Nature) PkgPath() string {
|
||||
if n.Type == nil {
|
||||
return ""
|
||||
}
|
||||
return n.Type.PkgPath()
|
||||
}
|
||||
|
||||
func (n Nature) IsFastMap() bool {
|
||||
if n.Type == nil {
|
||||
return false
|
||||
|
||||
+19
-11
@@ -89,7 +89,7 @@ func isInteger(nt Nature) bool {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fallthrough
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return true
|
||||
return nt.PkgPath() == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func isInteger(nt Nature) bool {
|
||||
func isFloat(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
return nt.PkgPath() == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -178,15 +178,23 @@ func kind(t reflect.Type) reflect.Kind {
|
||||
}
|
||||
|
||||
func isComparable(l, r Nature) bool {
|
||||
switch {
|
||||
case l.Kind() == r.Kind():
|
||||
return true
|
||||
case isNumber(l) && isNumber(r):
|
||||
return true
|
||||
case isNil(l) || isNil(r):
|
||||
return true
|
||||
case isUnknown(l) || isUnknown(r):
|
||||
if isUnknown(l) || isUnknown(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
if isNil(l) || isNil(r) {
|
||||
return true
|
||||
}
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return true
|
||||
}
|
||||
if isDuration(l) && isDuration(r) {
|
||||
return true
|
||||
}
|
||||
if isTime(l) && isTime(r) {
|
||||
return true
|
||||
}
|
||||
if isArray(l) && isArray(r) {
|
||||
return true
|
||||
}
|
||||
return l.AssignableTo(r)
|
||||
}
|
||||
|
||||
+4
-80
@@ -1119,6 +1119,10 @@ func TestExpr(t *testing.T) {
|
||||
`duration("1h") + duration("1m")`,
|
||||
time.Hour + time.Minute,
|
||||
},
|
||||
{
|
||||
`duration("1h") - duration("1m")`,
|
||||
time.Hour - time.Minute,
|
||||
},
|
||||
{
|
||||
`7 * duration("1h")`,
|
||||
7 * time.Hour,
|
||||
@@ -2328,86 +2332,6 @@ func TestIssue432(t *testing.T) {
|
||||
assert.Equal(t, float64(10), out)
|
||||
}
|
||||
|
||||
func TestIssue461(t *testing.T) {
|
||||
type EnvStr string
|
||||
type EnvField struct {
|
||||
S EnvStr
|
||||
Str string
|
||||
}
|
||||
type Env struct {
|
||||
S EnvStr
|
||||
Str string
|
||||
EnvField EnvField
|
||||
}
|
||||
var tests = []struct {
|
||||
input string
|
||||
env Env
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
input: "Str == S",
|
||||
env: Env{S: "string", Str: "string"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
input: "Str == Str",
|
||||
env: Env{Str: "string"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: "S == S",
|
||||
env: Env{Str: "string"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `Str == "string"`,
|
||||
env: Env{Str: "string"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `S == "string"`,
|
||||
env: Env{Str: "string"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
input: "EnvField.Str == EnvField.S",
|
||||
env: Env{EnvField: EnvField{S: "string", Str: "string"}},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
input: "EnvField.Str == EnvField.Str",
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: "EnvField.S == EnvField.S",
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `EnvField.Str == "string"`,
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `EnvField.S == "string"`,
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
program, err := expr.Compile(tt.input, expr.Env(tt.env), expr.AsBool())
|
||||
|
||||
out, err := expr.Run(program, tt.env)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tt.want, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue462(t *testing.T) {
|
||||
env := map[string]any{
|
||||
"foo": func() (string, error) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package issue_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/internal/testify/require"
|
||||
)
|
||||
|
||||
func TestIssue461(t *testing.T) {
|
||||
type EnvStr string
|
||||
type EnvField struct {
|
||||
S EnvStr
|
||||
Str string
|
||||
}
|
||||
type Env struct {
|
||||
S EnvStr
|
||||
Str string
|
||||
EnvField EnvField
|
||||
}
|
||||
var tests = []struct {
|
||||
input string
|
||||
env Env
|
||||
want bool
|
||||
err string
|
||||
}{
|
||||
{
|
||||
input: "Str == S",
|
||||
env: Env{S: "string", Str: "string"},
|
||||
err: "invalid operation: == (mismatched types string and issue_test.EnvStr)",
|
||||
},
|
||||
{
|
||||
input: "Str == Str",
|
||||
env: Env{Str: "string"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: "S == S",
|
||||
env: Env{Str: "string"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `Str == "string"`,
|
||||
env: Env{Str: "string"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `S == "string"`,
|
||||
env: Env{Str: "string"},
|
||||
err: "invalid operation: == (mismatched types issue_test.EnvStr and string)",
|
||||
},
|
||||
{
|
||||
input: "EnvField.Str == EnvField.S",
|
||||
env: Env{EnvField: EnvField{S: "string", Str: "string"}},
|
||||
err: "invalid operation: == (mismatched types string and issue_test.EnvStr)",
|
||||
},
|
||||
{
|
||||
input: "EnvField.Str == EnvField.Str",
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: "EnvField.S == EnvField.S",
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `EnvField.Str == "string"`,
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
input: `EnvField.S == "string"`,
|
||||
env: Env{EnvField: EnvField{Str: "string"}},
|
||||
err: "invalid operation: == (mismatched types issue_test.EnvStr and string)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
program, err := expr.Compile(tt.input, expr.Env(tt.env), expr.AsBool())
|
||||
|
||||
if tt.err != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.err)
|
||||
} else {
|
||||
out, err := expr.Run(program, tt.env)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package issue_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/internal/testify/require"
|
||||
)
|
||||
|
||||
type ModeEnum int
|
||||
|
||||
const (
|
||||
ModeEnumA ModeEnum = 1
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
Mode *ModeEnum
|
||||
}
|
||||
|
||||
func TestIssue730(t *testing.T) {
|
||||
code := `int(Mode) == 1`
|
||||
|
||||
tmp := ModeEnumA
|
||||
|
||||
env := map[string]any{
|
||||
"Mode": &tmp,
|
||||
}
|
||||
|
||||
program, err := expr.Compile(code, expr.Env(env))
|
||||
require.NoError(t, err)
|
||||
|
||||
output, err := expr.Run(program, env)
|
||||
require.NoError(t, err)
|
||||
require.True(t, output.(bool))
|
||||
}
|
||||
|
||||
func TestIssue730_warn_about_different_types(t *testing.T) {
|
||||
code := `Mode == 1`
|
||||
|
||||
_, err := expr.Compile(code, expr.Env(Env{}))
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid operation: == (mismatched types issue_test.ModeEnum and int)")
|
||||
}
|
||||
|
||||
func TestIssue730_eval(t *testing.T) {
|
||||
code := `Mode == 1`
|
||||
|
||||
tmp := ModeEnumA
|
||||
|
||||
env := map[string]any{
|
||||
"Mode": &tmp,
|
||||
}
|
||||
|
||||
// Golang also does not allow this:
|
||||
// _ = ModeEnumA == int(1) // will not compile
|
||||
|
||||
out, err := expr.Eval(code, env)
|
||||
require.NoError(t, err)
|
||||
require.False(t, out.(bool))
|
||||
}
|
||||
Reference in New Issue
Block a user