compiler: support make()

This commit is contained in:
Evgenii Stratonikov 2020-08-24 11:58:29 +03:00
parent d8badd9a8d
commit 0f11116040
5 changed files with 70 additions and 3 deletions

View file

@ -7,8 +7,8 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
The compiler is mostly compatible with regular Go language specification, but
there are some important deviations that you need to be aware of that make it
a dialect of Go rather than a complete port of the language:
* `make()` ane `new()` are not supported, most of the time you can substitute
them with composite literals
* `new()` is not supported, most of the time you can substitute structs with composite literals
* `make()` is supported for maps and slices with elements of basic types
* pointers are supported only for struct literals, one can't take an address
of an arbitrary variable
* there is no real distinction between different integer types, all of them

View file

@ -14,7 +14,7 @@ import (
var (
// Go language builtin functions.
goBuiltins = []string{"len", "append", "panic"}
goBuiltins = []string{"len", "append", "panic", "make"}
// Custom builtin utility functions.
customBuiltins = []string{
"FromAddress", "Equals",

View file

@ -1305,6 +1305,24 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
}
switch name {
case "make":
typ := c.typeOf(expr.Args[0])
switch {
case isMap(typ):
emit.Opcode(c.prog.BinWriter, opcode.NEWMAP)
default:
if len(expr.Args) == 3 {
c.prog.Err = fmt.Errorf("`make()` with a capacity argument is not supported")
return
}
ast.Walk(c, expr.Args[1])
if isByteSlice(typ) {
emit.Opcode(c.prog.BinWriter, opcode.NEWBUFFER)
} else {
neoT := toNeoType(typ.(*types.Slice).Elem())
emit.Instruction(c.prog.BinWriter, opcode.NEWARRAYT, []byte{byte(neoT)})
}
}
case "len":
emit.Opcode(c.prog.BinWriter, opcode.DUP)
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
@ -1397,6 +1415,9 @@ func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr {
if f.Name == "panic" {
return args[1:]
}
if f.Name == "make" {
return nil
}
}
return args

View file

@ -2,9 +2,12 @@ package compiler_test
import (
"math/big"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
var sliceTestCases = []testCase{
@ -323,3 +326,41 @@ func TestJumps(t *testing.T) {
`
eval(t, src, []byte{0x62, 0x01, 0x00})
}
func TestMake(t *testing.T) {
t.Run("Map", func(t *testing.T) {
src := `package foo
func Main() int {
a := make(map[int]int)
a[1] = 10
a[2] = 20
return a[1]
}`
eval(t, src, big.NewInt(10))
})
t.Run("IntSlice", func(t *testing.T) {
src := `package foo
func Main() int {
a := make([]int, 10)
return len(a) + a[0]
}`
eval(t, src, big.NewInt(10))
})
t.Run("ByteSlice", func(t *testing.T) {
src := `package foo
func Main() int {
a := make([]byte, 10)
return len(a) + int(a[0])
}`
eval(t, src, big.NewInt(10))
})
t.Run("CapacityError", func(t *testing.T) {
src := `package foo
func Main() int {
a := make([]int, 1, 2)
return a[0]
}`
_, err := compiler.Compile("foo.go", strings.NewReader(src))
require.Error(t, err)
})
}

View file

@ -27,6 +27,11 @@ func isBasicTypeOfKind(typ types.Type, ks ...types.BasicKind) bool {
return false
}
func isMap(typ types.Type) bool {
_, ok := typ.Underlying().(*types.Map)
return ok
}
func isByte(typ types.Type) bool {
return isBasicTypeOfKind(typ, types.Uint8, types.Int8)
}