From 9f7f94a6fbc36a05df5e464989c901d9784161e8 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 8 Jun 2020 12:02:03 +0300 Subject: [PATCH] compiler: implement Remove builtin Support removing items from collections via REMOVE opcode. --- pkg/compiler/analysis.go | 1 + pkg/compiler/codegen.go | 13 +++++++++ pkg/compiler/interop_test.go | 51 ++++++++++++++++++++++++++++++++++++ pkg/interop/util/util.go | 5 ++++ 4 files changed, 70 insertions(+) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index bf4430c74..6c052e5af 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -17,6 +17,7 @@ var ( "VerifySignature", "AppCall", "FromAddress", "Equals", "panic", "DynAppCall", + "delete", "Remove", } ) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 032d3d2f1..c53282399 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1075,6 +1075,19 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { } else { c.prog.Err = errors.New("panic should have string or nil argument") } + case "delete", "Remove": + arg := expr.Args[0] + errNotSupported := errors.New("only maps and non-byte slices are supported in `Remove`") + switch typ := c.typeInfo.Types[arg].Type.Underlying().(type) { + case *types.Map: + case *types.Slice: + if isByte(typ.Elem()) { + c.prog.Err = errNotSupported + } + default: + c.prog.Err = errNotSupported + } + emit.Opcode(c.prog.BinWriter, opcode.REMOVE) case "SHA256": emit.Opcode(c.prog.BinWriter, opcode.SHA256) case "SHA1": diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index e335f5882..1b2e3539d 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -2,6 +2,7 @@ package compiler_test import ( "fmt" + "math/big" "strings" "testing" @@ -13,6 +14,56 @@ import ( "github.com/stretchr/testify/require" ) +func TestRemove(t *testing.T) { + srcTmpl := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/util" + func Main() int { + a := %s + util.Remove(a, %d) + return len(a) * a[%d] + }` + testRemove := func(item string, key, index, result int64) func(t *testing.T) { + return func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, item, key, index) + if result > 0 { + eval(t, src, big.NewInt(result)) + return + } + v := vmAndCompile(t, src) + require.Error(t, v.Run()) + } + } + t.Run("Map", func(t *testing.T) { + item := "map[int]int{1: 2, 5: 7, 11: 13}" + t.Run("RemovedKey", testRemove(item, 5, 5, -1)) + t.Run("AnotherKey", testRemove(item, 5, 11, 26)) + }) + t.Run("Slice", func(t *testing.T) { + item := "[]int{5, 7, 11, 13}" + t.Run("RemovedKey", testRemove(item, 2, 2, 39)) + t.Run("AnotherKey", testRemove(item, 2, 1, 21)) + t.Run("LastKey", testRemove(item, 2, 3, -1)) + }) + t.Run("Invalid", func(t *testing.T) { + srcTmpl := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/util" + func Main() int { + util.Remove(%s, 2) + return 1 + }` + t.Run("BasicType", func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, "1") + _, err := compiler.Compile(strings.NewReader(src)) + require.Error(t, err) + }) + t.Run("ByteSlice", func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, "[]byte{1, 2}") + _, err := compiler.Compile(strings.NewReader(src)) + require.Error(t, err) + }) + }) +} + func TestFromAddress(t *testing.T) { as1 := "Aej1fe4mUgou48Zzup5j8sPrE3973cJ5oz" addr1, err := address.StringToUint160(as1) diff --git a/pkg/interop/util/util.go b/pkg/interop/util/util.go index 1aba144cb..a87ea200a 100644 --- a/pkg/interop/util/util.go +++ b/pkg/interop/util/util.go @@ -17,3 +17,8 @@ func FromAddress(address string) []byte { func Equals(a, b interface{}) bool { return false } + +// Remove removes item with the specified key from slice or map. +// For maps it is similar to `delete`. +// For slices it performs mutable update as if slice was provided by pointer. +func Remove(sliceOrMap, key interface{}) {}