expr icon indicating copy to clipboard operation
expr copied to clipboard

Comparison with named integer type fails

Open mbertschler opened this issue 10 months ago • 3 comments

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)
	})
}

mbertschler avatar Apr 22 '24 10:04 mbertschler

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.

antonmedv avatar Apr 22 '24 10:04 antonmedv