forked from TrueCloudLab/neoneo-go
Merge pull request #1101 from nspcc-dev/feature/convert
Support type conversions
This commit is contained in:
commit
9ca87855a6
3 changed files with 86 additions and 3 deletions
|
@ -708,6 +708,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
name string
|
name string
|
||||||
numArgs = len(n.Args)
|
numArgs = len(n.Args)
|
||||||
isBuiltin bool
|
isBuiltin bool
|
||||||
|
isFunc bool
|
||||||
)
|
)
|
||||||
|
|
||||||
switch fun := n.Fun.(type) {
|
switch fun := n.Fun.(type) {
|
||||||
|
@ -717,6 +718,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
if !ok && !isBuiltin {
|
if !ok && !isBuiltin {
|
||||||
name = fun.Name
|
name = fun.Name
|
||||||
}
|
}
|
||||||
|
// distinguish lambda invocations from type conversions
|
||||||
|
if fun.Obj != nil && fun.Obj.Kind == ast.Var {
|
||||||
|
isFunc = true
|
||||||
|
}
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
// If this is a method call we need to walk the AST to load the struct locally.
|
// 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
|
// 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.
|
// We can be sure builtins are of type *ast.Ident.
|
||||||
c.convertBuiltin(n)
|
c.convertBuiltin(n)
|
||||||
case name != "":
|
case name != "":
|
||||||
// Function was not found thus is can be only an invocation of func-typed variable.
|
// Function was not found thus is can be only an invocation of func-typed variable or type conversion.
|
||||||
c.emitLoadVar(name)
|
// We care only about string conversions because all others are effectively no-op in NeoVM.
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
// 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):
|
case isSyscall(f):
|
||||||
c.convertSyscall(n, f.selector.Name, f.name)
|
c.convertSyscall(n, f.selector.Name, f.name)
|
||||||
default:
|
default:
|
||||||
|
@ -987,6 +998,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// not the assertion type.
|
// not the assertion type.
|
||||||
case *ast.TypeAssertExpr:
|
case *ast.TypeAssertExpr:
|
||||||
ast.Walk(c, n.X)
|
ast.Walk(c, n.X)
|
||||||
|
typ := toNeoType(c.typeOf(n.Type))
|
||||||
|
emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
|
|
|
@ -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"))
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package compiler
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *codegen) typeAndValueOf(e ast.Expr) types.TypeAndValue {
|
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)
|
t, ok := typ.Underlying().(*types.Slice)
|
||||||
return ok && isByte(t.Elem())
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue