package compiler_test import ( "bytes" "fmt" "math/big" "strings" "testing" "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) var structTestCases = []testCase{ { "struct field assign", `func F%d() int { t := token1 { x: 2, y: 4, } age := t.x return age } type token1 struct { x int y int } `, big.NewInt(2), }, { "struct field from func result", `type S struct { x int } func fn() int { return 2 } func F%d() int { t := S{x: fn()} return t.x } `, big.NewInt(2), }, { "struct field return", `type token2 struct { x int y int } func F%d() int { t := token2 { x: 2, y: 4, } return t.x } `, big.NewInt(2), }, { "struct field assign", `type token3 struct { x int y int } func F%d() int { t := token3 { x: 2, y: 4, } t.x = 10 return t.x } `, big.NewInt(10), }, { "complex struct", `type token4 struct { x int y int } func F%d() int { x := 10 t := token4 { x: 2, y: 4, } y := x + t.x return y } `, big.NewInt(12), }, { "initialize struct field from variable", `type token5 struct { x int y int } func F%d() int { x := 10 t := token5 { x: x, y: 4, } y := t.x + t.y return y } `, big.NewInt(14), }, { "assign a variable to a struct field", `type token6 struct { x int y int } func F%d() int { ten := 10 t := token6 { x: 2, y: 4, } t.x = ten y := t.y + t.x return y } `, big.NewInt(14), }, { "increase struct field with +=", `type token7 struct { x int } func F%d() int { t := token7{x: 2} t.x += 3 return t.x } `, big.NewInt(5), }, { "assign a struct field to a struct field", `type token8 struct { x int y int } func F%d() int { t1 := token8 { x: 2, y: 4, } t2 := token8 { x: 3, y: 5, } t1.x = t2.y y := t1.x + t2.x return y } `, big.NewInt(8), }, { "initialize same struct twice", `type token9 struct { x int y int } func F%d() int { t1 := token9 { x: 2, y: 4, } t2 := token9 { x: 2, y: 4, } return t1.x + t2.y } `, big.NewInt(6), }, { "struct methods", `type token10 struct { x int } func(t token10) getInteger() int { return t.x } func F%d() int { t := token10 { x: 4, } someInt := t.getInteger() return someInt } `, big.NewInt(4), }, { "struct methods with arguments", `type token11 struct { x int } // Also tests if x conflicts with t.x func(t token11) addIntegers(x int, y int) int { return t.x + x + y } func F%d() int { t := token11 { x: 4, } someInt := t.addIntegers(2, 4) return someInt } `, big.NewInt(10), }, { "initialize struct partially", `type token12 struct { x int y int z string b bool } func F%d() int { t := token12 { x: 4, } return t.y } `, big.NewInt(0), }, { "test return struct from func", `type token13 struct { x int y int z string b bool } func newToken() token13 { return token13{ x: 1, y: 2, z: "hello", b: false, } } func F%d() token13 { return newToken() } `, []stackitem.Item{ stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewByteArray([]byte("hello")), stackitem.NewBool(false), }, }, { "pass struct as argument", `type Bar struct { amount int } func addToAmount(x int, bar Bar) int { bar.amount = bar.amount + x return bar.amount } func F%d() int { b := Bar{ amount: 10, } x := addToAmount(4, b) return x } `, big.NewInt(14), }, { "declare struct literal", `func F%d() int { var x struct { a int } x.a = 2 return x.a } `, big.NewInt(2), }, { "declare struct type", `type withA struct { a int } func F%d() int { var x withA x.a = 2 return x.a } `, big.NewInt(2), }, { "nested selectors (simple read)", `type S1 struct { x, y S2 } type S2 struct { a, b int } func F%d() int { var s1 S1 var s2 S2 s2.a = 3 s1.y = s2 return s1.y.a } `, big.NewInt(3), }, { "nested selectors (simple write)", `type S3 struct { x S4 } type S4 struct { a int } func F%d() int { s1 := S3{ x: S4 { a: 3, }, } s1.x.a = 11 return s1.x.a } `, big.NewInt(11), }, { "complex struct default value", `type S5 struct { x S6 } type S6 struct { y S7 } type S7 struct { a int } func F%d() int { var s1 S5 s1.x.y.a = 11 return s1.x.y.a } `, big.NewInt(11), }, { "lengthy struct default value", `type SS struct { x int; y []byte; z bool } func F%d() int { var s SS return s.x } `, big.NewInt(0), }, { "nested selectors (complex write)", `type S8 struct { x S9 } type S9 struct { y, z S10 } type S10 struct { a int } func F%d() int { var s1 S8 s1.x.y.a, s1.x.z.a = 11, 31 return s1.x.y.a + s1.x.z.a } `, big.NewInt(42), }, { "omit field names", `type pair struct { a, b int } func F%d() int { p := pair{1, 2} x := p.a * 10 return x + p.b } `, big.NewInt(12), }, { "uninitialized struct fields", `type Foo struct { i int m map[string]int b []byte a []int s struct { ii int } } func NewFoo() Foo { return Foo{} } func F%d() int { foo := NewFoo() if foo.i != 0 { return 1 } if len(foo.m) != 0 { return 1 } if len(foo.b) != 0 { return 1 } if len(foo.a) != 0 { return 1 } s := foo.s if s.ii != 0 { return 1 } return 2 } `, big.NewInt(2), }, } func TestStructs(t *testing.T) { srcBuilder := bytes.NewBuffer([]byte("package testcase\n")) for i, tc := range structTestCases { srcBuilder.WriteString(fmt.Sprintf(tc.src, i)) } ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil) require.NoError(t, err) for i, tc := range structTestCases { t.Run(tc.name, func(t *testing.T) { v := vm.New() invokeMethod(t, fmt.Sprintf("F%d", i), ne.Script, v, di) runAndCheck(t, v, tc.result) }) } }