Skip to content

Commit

Permalink
fix(gnovm): correct comparison between different types (gnolang#1890)
Browse files Browse the repository at this point in the history
Fixes gnolang#1376, fixes
gnolang#2386.

---------

Co-authored-by: deelawn <[email protected]>
  • Loading branch information
ltzmaxwell and deelawn authored Aug 16, 2024
1 parent 1e0e52c commit 8bd07c9
Show file tree
Hide file tree
Showing 26 changed files with 522 additions and 33 deletions.
97 changes: 68 additions & 29 deletions examples/gno.land/p/demo/uassert/uassert.gno
Original file line number Diff line number Diff line change
Expand Up @@ -379,46 +379,85 @@ func NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool {
return true
}

func isNumberEmpty(n interface{}) (isNumber, isEmpty bool) {
switch n := n.(type) {
// NOTE: the cases are split individually, so that n becomes of the
// asserted type; the type of '0' was correctly inferred and converted
// to the corresponding type, int, int8, etc.
case int:
return true, n == 0
case int8:
return true, n == 0
case int16:
return true, n == 0
case int32:
return true, n == 0
case int64:
return true, n == 0
case uint:
return true, n == 0
case uint8:
return true, n == 0
case uint16:
return true, n == 0
case uint32:
return true, n == 0
case uint64:
return true, n == 0
case float32:
return true, n == 0
case float64:
return true, n == 0
}
return false, false
}
func Empty(t TestingT, obj interface{}, msgs ...string) bool {
t.Helper()
switch val := obj.(type) {
case string:
if val != "" {
return fail(t, msgs, "uassert.Empty: not empty string: %s", val)
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if val != 0 {
return fail(t, msgs, "uassert.Empty: not empty number: %d", val)

isNumber, isEmpty := isNumberEmpty(obj)
if isNumber {
if !isEmpty {
return fail(t, msgs, "uassert.Empty: not empty number: %d", obj)
}
case std.Address:
var zeroAddr std.Address
if val != zeroAddr {
return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val))
} else {
switch val := obj.(type) {
case string:
if val != "" {
return fail(t, msgs, "uassert.Empty: not empty string: %s", val)
}
case std.Address:
var zeroAddr std.Address
if val != zeroAddr {
return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val))
}
default:
return fail(t, msgs, "uassert.Empty: unsupported type")
}
default:
return fail(t, msgs, "uassert.Empty: unsupported type")
}
return true
}

func NotEmpty(t TestingT, obj interface{}, msgs ...string) bool {
t.Helper()
switch val := obj.(type) {
case string:
if val == "" {
return fail(t, msgs, "uassert.NotEmpty: empty string: %s", val)
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if val == 0 {
return fail(t, msgs, "uassert.NotEmpty: empty number: %d", val)
}
case std.Address:
var zeroAddr std.Address
if val == zeroAddr {
return fail(t, msgs, "uassert.NotEmpty: empty std.Address: %s", string(val))
isNumber, isEmpty := isNumberEmpty(obj)
if isNumber {
if isEmpty {
return fail(t, msgs, "uassert.NotEmpty: empty number: %d", obj)
}
} else {
switch val := obj.(type) {
case string:
if val == "" {
return fail(t, msgs, "uassert.NotEmpty: empty string: %s", val)
}
case std.Address:
var zeroAddr std.Address
if val == zeroAddr {
return fail(t, msgs, "uassert.NotEmpty: empty std.Address: %s", string(val))
}
default:
return fail(t, msgs, "uassert.NotEmpty: unsupported type")
}
default:
return fail(t, msgs, "uassert.NotEmpty: unsupported type")
}
return true
}
2 changes: 2 additions & 0 deletions examples/gno.land/p/demo/uassert/uassert_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ func TestEmpty(t *testing.T) {
{"", true},
{0, true},
{int(0), true},
{int32(0), true},
{int64(0), true},
{uint(0), true},
// XXX: continue
Expand Down Expand Up @@ -335,6 +336,7 @@ func TestNotEmpty(t *testing.T) {
{"", false},
{0, false},
{int(0), false},
{int32(0), false},
{int64(0), false},
{uint(0), false},
{std.Address(""), false},
Expand Down
4 changes: 3 additions & 1 deletion gnovm/pkg/gnolang/op_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ func (m *Machine) doOpEql() {
if debug {
debugAssertEqualityTypes(lv.T, rv.T)
}

// set result in lv.
res := isEql(m.Store, lv, rv)
lv.T = UntypedBoolType
Expand Down Expand Up @@ -344,6 +343,9 @@ func isEql(store Store, lv, rv *TypedValue) bool {
} else if rvu {
return false
}
if err := checkSame(lv.T, rv.T, ""); err != nil {
return false
}
if lnt, ok := lv.T.(*NativeType); ok {
if rnt, ok := rv.T.(*NativeType); ok {
if lnt.Type != rnt.Type {
Expand Down
7 changes: 6 additions & 1 deletion gnovm/pkg/gnolang/op_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,13 @@ func (m *Machine) doOpRef() {
nv.Value = rv2
}
}
// when obtaining a pointer of the databyte type, use the ElemType of databyte
elt := xv.TV.T
if elt == DataByteType {
elt = xv.TV.V.(DataByteValue).ElemType
}
m.PushValue(TypedValue{
T: m.Alloc.NewType(&PointerType{Elt: xv.TV.T}),
T: m.Alloc.NewType(&PointerType{Elt: elt}),
V: xv,
})
}
Expand Down
13 changes: 11 additions & 2 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
checkOrConvertIntegerKind(store, last, n.High)
checkOrConvertIntegerKind(store, last, n.Max)

// if n.X is untyped, convert to corresponding type
t := evalStaticTypeOf(store, last, n.X)
if isUntyped(t) {
dt := defaultTypeOf(t)
checkOrConvertType(store, last, &n.X, dt, false)
}

// TRANS_LEAVE -----------------------
case *TypeAssertExpr:
if n.Type == nil {
Expand Down Expand Up @@ -2794,8 +2801,10 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative
// push t into bx.Left
checkOrConvertType(store, last, &bx.Left, t, autoNative)
return
// case EQL, LSS, GTR, NEQ, LEQ, GEQ:
// default:
case EQL, LSS, GTR, NEQ, LEQ, GEQ:
// do nothing
default:
// do nothing
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions gnovm/tests/files/types/cmp_databyte.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import "bytes"

func main() {
cmp := bytes.Compare([]byte("hello"), []byte("hey"))
println(cmp)

}

// Output:
// -1
27 changes: 27 additions & 0 deletions gnovm/tests/files/types/cmp_iface_0_stdlibs.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"errors"
"strconv"
)

type Error int64

func (e Error) Error() string {
return "error: " + strconv.Itoa(int(e))
}

var errCmp = errors.New("XXXX")

// special case:
// one is interface
func main() {
if Error(0) == errCmp {
println("what the firetruck?")
} else {
println("something else")
}
}

// Output:
// something else
29 changes: 29 additions & 0 deletions gnovm/tests/files/types/cmp_iface_1.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"errors"
"strconv"
)

type Error int64

func (e Error) Error() string {
return "error: " + strconv.Itoa(int(e))
}

// typed
var errCmp error = errors.New("XXXX")

// special case:
// one is interface
func main() {
const e Error = Error(0) // typed const
if e == errCmp {
println("what the firetruck?")
} else {
println("something else")
}
}

// Output:
// something else
32 changes: 32 additions & 0 deletions gnovm/tests/files/types/cmp_iface_2.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"fmt"
"strconv"
)

type E interface {
Error() string
}
type Error int64

func (e Error) Error() string {
return "error: " + strconv.Itoa(int(e))
}

// special case:
// one is interface
func main() {
var e0 E
e0 = Error(0)
fmt.Printf("%T \n", e0)
if e0 == Error(0) {
println("what the firetruck?")
} else {
println("something else")
}
}

// Output:
// int64
// what the firetruck?
27 changes: 27 additions & 0 deletions gnovm/tests/files/types/cmp_iface_3_stdlibs.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"errors"
"strconv"
)

type Error int64

func (e Error) Error() string {
return "error: " + strconv.Itoa(int(e))
}

var errCmp = errors.New("XXXX")

// special case:
// one is interface
func main() {
if Error(1) == errCmp {
println("what the firetruck?")
} else {
println("something else")
}
}

// Output:
// something else
24 changes: 24 additions & 0 deletions gnovm/tests/files/types/cmp_iface_4.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"strconv"
)

type Error int64

func (e Error) Error() string {
return "error: " + strconv.Itoa(int(e))
}

// both not const, and both interface
func main() {
var l interface{}
if l == Error(0) {
println("what the firetruck?")
} else {
println("something else")
}
}

// Output:
// something else
27 changes: 27 additions & 0 deletions gnovm/tests/files/types/cmp_iface_5_stdlibs.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"errors"
"strconv"
)

type Error int64

func (e Error) Error() string {
return "error: " + strconv.Itoa(int(e))
}

var errCmp = errors.New("XXXX")

// special case:
// one is interface
func main() {
if errCmp == int64(1) {
println("what the firetruck?")
} else {
println("something else")
}
}

// Error:
// main/files/types/cmp_iface_5_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error)
Loading

0 comments on commit 8bd07c9

Please sign in to comment.