compiler: allow multi-return variables declaration
Problem: an attempt to compile the following code leads to a runtime
panic:
```
package foo
var a, b = f()
func Main() int {
return a + b
}
func f() (int, int) {
return 1, 2
}
```
```
panic: runtime error: index out of range [1] with length 1 [recovered]
panic: runtime error: index out of range [1] with length 1
goroutine 22 [running]:
testing.tRunner.func1.2({0xa341c0, 0xc0001647f8})
/usr/local/go/src/testing/testing.go:1209 +0x24e
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1212 +0x218
panic({0xa341c0, 0xc0001647f8})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).Visit(0xc0001623c0, {0xc75520, 0xc000155d80})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:591 +0x6559
go/ast.Walk({0xc6c4e0, 0xc0001623c0}, {0xc75520, 0xc000155d80})
/usr/local/go/src/go/ast/walk.go:50 +0x5f
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).convertGlobals.func1({0xc75520, 0xc000155d80})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:359 +0x70
go/ast.inspector.Visit(0xc000229740, {0xc75520, 0xc000155d80})
/usr/local/go/src/go/ast/walk.go:375 +0x31
go/ast.Walk({0xc6d920, 0xc000229740}, {0xc75520, 0xc000155d80})
/usr/local/go/src/go/ast/walk.go:50 +0x5f
go/ast.walkDeclList({0xc6d920, 0xc000229740}, {0xc000155e80, 0x3, 0x120})
/usr/local/go/src/go/ast/walk.go:36 +0x87
go/ast.Walk({0xc6d920, 0xc000229740}, {0xc75458, 0xc000156c80})
/usr/local/go/src/go/ast/walk.go:355 +0x15c5
go/ast.Inspect(...)
/usr/local/go/src/go/ast/walk.go:387
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).convertGlobals(0xc0001623c0, 0xc000156c80, 0xc000254280)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:354 +0x71
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).traverseGlobals.func2(0xc000254280)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/analysis.go:86 +0x16e
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachPackage(0xc0001623c0, 0xc000191b98)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:93 +0xc6
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).traverseGlobals(0xc0001623c0)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/analysis.go:82 +0x22c
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).compile(0xc0001623c0, 0xc000274d20, 0x1)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:2118 +0x17c
github.com/nspcc-dev/neo-go/pkg/compiler.codeGen(0xc000274d20)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:2191 +0x353
github.com/nspcc-dev/neo-go/pkg/compiler.CompileWithOptions({0xa6f39a, 0xc00023cee0}, {0xc6d240, 0xc00024a460}, 0x0)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:218 +0x65
github.com/nspcc-dev/neo-go/pkg/compiler_test.vmAndCompileInterop(0x5648df, {0xa9989f, 0x7d})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/vm_test.go:75 +0x113
github.com/nspcc-dev/neo-go/pkg/compiler_test.eval(0xc00024a440, {0xa9989f, 0x129f366e}, {0xa68880, 0xc00024a440})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/vm_test.go:36 +0x2d
github.com/nspcc-dev/neo-go/pkg/compiler_test.TestGenDeclWithMultiRet.func2(0x4079f9)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/global_test.go:36 +0x4f
testing.tRunner(0xc00022e9c0, 0xbce760)
/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1306 +0x35a
```
Solution:
Allow using multi-return function calls as general variable declaration
value. It was supported for assignment statements, so do the same for
*ast.GenDecl if it's a variable under the hood.
2022-08-16 12:25:53 +00:00
|
|
|
package compiler_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/big"
|
|
|
|
"testing"
|
2022-08-19 11:54:42 +00:00
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
compiler: allow multi-return variables declaration
Problem: an attempt to compile the following code leads to a runtime
panic:
```
package foo
var a, b = f()
func Main() int {
return a + b
}
func f() (int, int) {
return 1, 2
}
```
```
panic: runtime error: index out of range [1] with length 1 [recovered]
panic: runtime error: index out of range [1] with length 1
goroutine 22 [running]:
testing.tRunner.func1.2({0xa341c0, 0xc0001647f8})
/usr/local/go/src/testing/testing.go:1209 +0x24e
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1212 +0x218
panic({0xa341c0, 0xc0001647f8})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).Visit(0xc0001623c0, {0xc75520, 0xc000155d80})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:591 +0x6559
go/ast.Walk({0xc6c4e0, 0xc0001623c0}, {0xc75520, 0xc000155d80})
/usr/local/go/src/go/ast/walk.go:50 +0x5f
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).convertGlobals.func1({0xc75520, 0xc000155d80})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:359 +0x70
go/ast.inspector.Visit(0xc000229740, {0xc75520, 0xc000155d80})
/usr/local/go/src/go/ast/walk.go:375 +0x31
go/ast.Walk({0xc6d920, 0xc000229740}, {0xc75520, 0xc000155d80})
/usr/local/go/src/go/ast/walk.go:50 +0x5f
go/ast.walkDeclList({0xc6d920, 0xc000229740}, {0xc000155e80, 0x3, 0x120})
/usr/local/go/src/go/ast/walk.go:36 +0x87
go/ast.Walk({0xc6d920, 0xc000229740}, {0xc75458, 0xc000156c80})
/usr/local/go/src/go/ast/walk.go:355 +0x15c5
go/ast.Inspect(...)
/usr/local/go/src/go/ast/walk.go:387
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).convertGlobals(0xc0001623c0, 0xc000156c80, 0xc000254280)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:354 +0x71
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).traverseGlobals.func2(0xc000254280)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/analysis.go:86 +0x16e
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).ForEachPackage(0xc0001623c0, 0xc000191b98)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:93 +0xc6
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).traverseGlobals(0xc0001623c0)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/analysis.go:82 +0x22c
github.com/nspcc-dev/neo-go/pkg/compiler.(*codegen).compile(0xc0001623c0, 0xc000274d20, 0x1)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:2118 +0x17c
github.com/nspcc-dev/neo-go/pkg/compiler.codeGen(0xc000274d20)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/codegen.go:2191 +0x353
github.com/nspcc-dev/neo-go/pkg/compiler.CompileWithOptions({0xa6f39a, 0xc00023cee0}, {0xc6d240, 0xc00024a460}, 0x0)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/compiler.go:218 +0x65
github.com/nspcc-dev/neo-go/pkg/compiler_test.vmAndCompileInterop(0x5648df, {0xa9989f, 0x7d})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/vm_test.go:75 +0x113
github.com/nspcc-dev/neo-go/pkg/compiler_test.eval(0xc00024a440, {0xa9989f, 0x129f366e}, {0xa68880, 0xc00024a440})
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/vm_test.go:36 +0x2d
github.com/nspcc-dev/neo-go/pkg/compiler_test.TestGenDeclWithMultiRet.func2(0x4079f9)
/home/anna/Documents/GitProjects/nspcc-dev/neo-go/pkg/compiler/global_test.go:36 +0x4f
testing.tRunner(0xc00022e9c0, 0xbce760)
/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1306 +0x35a
```
Solution:
Allow using multi-return function calls as general variable declaration
value. It was supported for assignment statements, so do the same for
*ast.GenDecl if it's a variable under the hood.
2022-08-16 12:25:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestGenDeclWithMultiRet(t *testing.T) {
|
|
|
|
t.Run("global var decl", func(t *testing.T) {
|
|
|
|
src := `package foo
|
|
|
|
func Main() int {
|
|
|
|
var a, b = f()
|
|
|
|
return a + b
|
|
|
|
}
|
|
|
|
func f() (int, int) {
|
|
|
|
return 1, 2
|
|
|
|
}`
|
|
|
|
eval(t, src, big.NewInt(3))
|
|
|
|
})
|
|
|
|
t.Run("local var decl", func(t *testing.T) {
|
|
|
|
src := `package foo
|
|
|
|
var a, b = f()
|
|
|
|
func Main() int {
|
|
|
|
return a + b
|
|
|
|
}
|
|
|
|
func f() (int, int) {
|
|
|
|
return 1, 2
|
|
|
|
}`
|
|
|
|
eval(t, src, big.NewInt(3))
|
|
|
|
})
|
|
|
|
}
|
2022-08-19 11:54:42 +00:00
|
|
|
|
|
|
|
func TestUnderscoreLocalVarDontEmitCode(t *testing.T) {
|
|
|
|
src := `package foo
|
|
|
|
type Foo struct { Int int }
|
|
|
|
func Main() int {
|
|
|
|
var _ int
|
|
|
|
var _ = 1
|
|
|
|
var (
|
|
|
|
A = 2
|
|
|
|
_ = A + 3
|
|
|
|
_, B, _ = 4, 5, 6
|
|
|
|
_, _, _ = f(A, B) // unused, but has function call, so the code is expected
|
|
|
|
_, C, _ = f(A, B)
|
|
|
|
)
|
|
|
|
var D = 7 // unused but named, so the code is expected
|
|
|
|
_ = D
|
|
|
|
var _ = Foo{ Int: 5 }
|
|
|
|
var fo = Foo{ Int: 3 }
|
|
|
|
var _ = 1 + A + fo.Int
|
|
|
|
var _ = fo.GetInt() // unused, but has method call, so the code is expected
|
|
|
|
return C
|
|
|
|
}
|
|
|
|
func f(a, b int) (int, int, int) {
|
|
|
|
return 8, 9, 10
|
|
|
|
}
|
|
|
|
func (fo Foo) GetInt() int {
|
|
|
|
return fo.Int
|
|
|
|
}`
|
2023-04-03 10:34:24 +00:00
|
|
|
eval(t, src, big.NewInt(9), []any{opcode.INITSLOT, []byte{5, 0}}, // local slot for A, B, C, D, fo
|
2022-08-19 11:54:42 +00:00
|
|
|
opcode.PUSH2, opcode.STLOC0, // store A
|
|
|
|
opcode.PUSH5, opcode.STLOC1, // store B
|
2023-04-03 10:34:24 +00:00
|
|
|
opcode.LDLOC0, opcode.LDLOC1, opcode.SWAP, []any{opcode.CALL, []byte{27}}, // evaluate f() first time
|
2022-08-19 11:54:42 +00:00
|
|
|
opcode.DROP, opcode.DROP, opcode.DROP, // drop all values from f
|
2023-04-03 10:34:24 +00:00
|
|
|
opcode.LDLOC0, opcode.LDLOC1, opcode.SWAP, []any{opcode.CALL, []byte{19}}, // evaluate f() second time
|
2022-08-19 11:54:42 +00:00
|
|
|
opcode.DROP, opcode.STLOC2, opcode.DROP, // store C
|
|
|
|
opcode.PUSH7, opcode.STLOC3, // store D
|
|
|
|
opcode.LDLOC3, opcode.DROP, // empty assignment
|
|
|
|
opcode.PUSH3, opcode.PUSH1, opcode.PACKSTRUCT, opcode.STLOC4, // fo decl
|
2023-04-03 10:34:24 +00:00
|
|
|
opcode.LDLOC4, []any{opcode.CALL, []byte{12}}, opcode.DROP, // fo.GetInt()
|
2022-08-19 11:54:42 +00:00
|
|
|
opcode.LDLOC2, opcode.RET, // return C
|
2023-04-03 10:34:24 +00:00
|
|
|
[]any{opcode.INITSLOT, []byte{0, 2}}, opcode.PUSH10, opcode.PUSH9, opcode.PUSH8, opcode.RET, // f
|
|
|
|
[]any{opcode.INITSLOT, []byte{0, 1}}, opcode.LDARG0, opcode.PUSH0, opcode.PICKITEM, opcode.RET) // (fo Foo) GetInt() int
|
2022-08-19 11:54:42 +00:00
|
|
|
}
|