diff --git a/docs/compiler.md b/docs/compiler.md index 8b2613478..7249a085b 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -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 diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 71870e98c..e83873d31 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -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", diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index a9cac98d8..42a9881c8 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -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 diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index 20223fb04..ca1d62fe7 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -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) + }) +} diff --git a/pkg/compiler/types.go b/pkg/compiler/types.go index 9b17de395..649c1f146 100644 --- a/pkg/compiler/types.go +++ b/pkg/compiler/types.go @@ -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) }