expr-lang / expr

Expression language and expression evaluation for Go
https://expr-lang.org
MIT License
5.85k stars 378 forks source link

Comparison with named integer type fails #632

Open mbertschler opened 2 months ago

mbertschler commented 2 months ago

Hey @antonmedv. Thanks for this amazing library, it is a lot of fun to work with.

After deciding to use it for a new feature with dynamic configuration, we unfortunately ran into some unexpected behavior. The problem is that the expression Named == 4 returns false with this environment definition:

type NamedUint uint32
type Env struct {
    Named NamedUint
}
env := &Env{Named: 4}

It seems to be because we use a named type NamedUint instead of the basic type uint32 in our struct.

Is this behavior expected?

Reproducing Test

Version: github.com/expr-lang/expr v1.16.1

func TestExprUint(t *testing.T) {
    type NamedUint uint32

    type Env struct {
        Plain uint32
        Named NamedUint
    }

    testEnv := &Env{
        Plain: 3,
        Named: 4,
    }

    evaluateCode := func(t *testing.T, code string, expected interface{}) {
        program, err := expr.Compile(code, expr.Env(&Env{}))
        if err != nil {
            t.Error("Compile", err)
        }

        output, err := expr.Run(program, testEnv)
        if err != nil {
            t.Error("Run", err)
        }
        formatted := fmt.Sprintf("%v", output)
        if formatted != fmt.Sprintf("%v", expected) {
            t.Errorf("expected %v, got %v. Code: %q Env: %+v", expected, output, code, testEnv)
        }
    }

    t.Run("Plain return", func(t *testing.T) {
        code := `Plain`
        evaluateCode(t, code, 3)
    })

    t.Run("Named return", func(t *testing.T) {
        code := `Named`
        evaluateCode(t, code, 4)
    })

    t.Run("Plain equal", func(t *testing.T) {
        code := `Plain == 3`
        evaluateCode(t, code, true)
    })

    // --- FAIL: TestExprUint (0.00s)
    //     --- FAIL: TestExprUint/Named_equal (0.00s)
    //         skan_assignment_test.go:394: expected true, got false. Code: "Named == 4" Env: &{Plain:3 Named:4}
    t.Run("Named equal", func(t *testing.T) {
        code := `Named == 4`
        evaluateCode(t, code, true)
    })
}
antonmedv commented 2 months ago

Hey @mbertschler, thanks!

I understand this problem. This is actually a Golang related one:

https://go.dev/play/p/nYw6xmQ9Ll2

package main

import (
    "fmt"
    "reflect"
)

type MyInt int

func main() {
    var my MyInt = 1
    var i int = 1
    fmt.Println(my == i) // Compilation will fail.
    fmt.Println(reflect.DeepEqual(my, i)) // Will return false.
}

Recently in #611 we improved int() builtin function to unwrap custom int types:

int(Named) == 4 // This will work in Expr.

One thing you can do is to write a patcher which will wrap all custom ints with int(...) call.