diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 7866fe5e7..e566055da 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -708,6 +708,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) { @@ -717,6 +718,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 @@ -763,9 +768,15 @@ 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. + // 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) + } case isSyscall(f): c.convertSyscall(n, f.selector.Name, f.name) default: @@ -987,6 +998,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..f0193acd0 100644 --- a/pkg/compiler/convert_test.go +++ b/pkg/compiler/convert_test.go @@ -61,3 +61,40 @@ 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)) +} + +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)) +} + +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")) +} 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 + } +}