From 904b2136fc4d2cff264053ecb2d726017051def0 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 24 Jun 2020 17:58:15 +0300 Subject: [PATCH 1/3] compiler: emit CONVERT opcode for type assertions Emit CONVERT for converting between different types. NeoVM behavior is different from that of Go (e.g. assertions of `int` and `uint32` are equivalent). --- pkg/compiler/codegen.go | 2 ++ pkg/compiler/convert_test.go | 11 +++++++++++ pkg/compiler/types.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index b30513dda..7f565ab3d 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -991,6 +991,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // not the assertion type. case *ast.TypeAssertExpr: ast.Walk(c, n.X) + typ := toNeoType(c.typeOf(n.Type)) + emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) return nil } return c diff --git a/pkg/compiler/convert_test.go b/pkg/compiler/convert_test.go index 379d7ce39..e768d7573 100644 --- a/pkg/compiler/convert_test.go +++ b/pkg/compiler/convert_test.go @@ -61,3 +61,14 @@ func TestConvert(t *testing.T) { }) } } + +func TestTypeAssertion(t *testing.T) { + src := `package foo + func Main() int { + a := []byte{1} + var u interface{} + u = a + return u.(int) + }` + eval(t, src, big.NewInt(1)) +} diff --git a/pkg/compiler/types.go b/pkg/compiler/types.go index 2011220fc..77bbcfc1c 100644 --- a/pkg/compiler/types.go +++ b/pkg/compiler/types.go @@ -3,6 +3,8 @@ package compiler import ( "go/ast" "go/types" + + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) func (c *codegen) typeAndValueOf(e ast.Expr) types.TypeAndValue { @@ -42,3 +44,34 @@ func isByteSlice(typ types.Type) bool { t, ok := typ.Underlying().(*types.Slice) return ok && isByte(t.Elem()) } + +func toNeoType(typ types.Type) stackitem.Type { + if typ == nil { + return stackitem.AnyT + } + switch t := typ.Underlying().(type) { + case *types.Basic: + info := t.Info() + switch { + case info&types.IsInteger != 0: + return stackitem.IntegerT + case info&types.IsBoolean != 0: + return stackitem.BooleanT + case info&types.IsString != 0: + return stackitem.ByteArrayT + default: + return stackitem.AnyT + } + case *types.Map: + return stackitem.MapT + case *types.Struct: + return stackitem.StructT + case *types.Slice: + if isByte(t.Elem()) { + return stackitem.BufferT + } + return stackitem.ArrayT + default: + return stackitem.AnyT + } +} From 1d275ceb65659e95a5fdc6f76121efb5787ee7ef Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 24 Jun 2020 18:36:21 +0300 Subject: [PATCH 2/3] compiler: distinguish between type conversions and function calls RN every identifier in call expression is considered to be lambda. But it also can be type expression, so we need to distinguish between these cases. --- pkg/compiler/codegen.go | 13 ++++++++++--- pkg/compiler/convert_test.go | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 7f565ab3d..7e838946c 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -712,6 +712,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { name string numArgs = len(n.Args) isBuiltin bool + isFunc bool ) switch fun := n.Fun.(type) { @@ -721,6 +722,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if !ok && !isBuiltin { name = fun.Name } + // distinguish lambda invocations from type conversions + if fun.Obj != nil && fun.Obj.Kind == ast.Var { + isFunc = true + } case *ast.SelectorExpr: // If this is a method call we need to walk the AST to load the struct locally. // Otherwise this is a function call from a imported package and we can call it @@ -767,9 +772,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // We can be sure builtins are of type *ast.Ident. c.convertBuiltin(n) case name != "": - // Function was not found thus is can be only an invocation of func-typed variable. - c.emitLoadVar(name) - emit.Opcode(c.prog.BinWriter, opcode.CALLA) + // Function was not found thus is can be only an invocation of func-typed variable or type conversion. + if isFunc { + c.emitLoadVar(name) + emit.Opcode(c.prog.BinWriter, opcode.CALLA) + } case isSyscall(f): c.convertSyscall(n, f.selector.Name, f.name) default: diff --git a/pkg/compiler/convert_test.go b/pkg/compiler/convert_test.go index e768d7573..613e1af3a 100644 --- a/pkg/compiler/convert_test.go +++ b/pkg/compiler/convert_test.go @@ -72,3 +72,17 @@ func TestTypeAssertion(t *testing.T) { }` eval(t, src, big.NewInt(1)) } + +func TestTypeConversion(t *testing.T) { + src := `package foo + type myInt int + func Main() int32 { + var a int32 = 41 + b := myInt(a) + incMy := func(x myInt) myInt { return x + 1 } + c := incMy(b) + return int32(c) + }` + + eval(t, src, big.NewInt(42)) +} From 2c5ab95b8ac9043df455e955d03487abe8bed90d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 24 Jun 2020 18:43:29 +0300 Subject: [PATCH 3/3] compiler: convert to ByteArray for string variables Convert to ByteArray when converting variable to `string`, because underlying byte-slice changes should not affect result string. --- pkg/compiler/codegen.go | 6 +++++- pkg/compiler/convert_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 7e838946c..585aafa19 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -773,7 +773,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.convertBuiltin(n) case name != "": // Function was not found thus is can be only an invocation of func-typed variable or type conversion. - if isFunc { + // We care only about string conversions because all others are effectively no-op in NeoVM. + // E.g. one cannot write `bool(int(a))`, only `int32(int(a))`. + if isString(c.typeOf(n.Fun)) { + c.emitConvert(stackitem.ByteArrayT) + } else if isFunc { c.emitLoadVar(name) emit.Opcode(c.prog.BinWriter, opcode.CALLA) } diff --git a/pkg/compiler/convert_test.go b/pkg/compiler/convert_test.go index 613e1af3a..f0193acd0 100644 --- a/pkg/compiler/convert_test.go +++ b/pkg/compiler/convert_test.go @@ -86,3 +86,15 @@ func TestTypeConversion(t *testing.T) { eval(t, src, big.NewInt(42)) } + +func TestTypeConversionString(t *testing.T) { + src := `package foo + type mystr string + func Main() mystr { + b := []byte{'l', 'a', 'm', 'a', 'o'} + s := mystr(b) + b[0] = 'u' + return s + }` + eval(t, src, []byte("lamao")) +}