mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-01-24 09:24:36 +00:00
Merge pull request #1247 from nspcc-dev/feature/pointers
Support pointers in compiler
This commit is contained in:
commit
e1d3223505
3 changed files with 126 additions and 5 deletions
|
@ -9,6 +9,8 @@ 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:
|
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
|
* `make()` ane `new()` are not supported, most of the time you can substitute
|
||||||
them with composite literals
|
them with composite literals
|
||||||
|
* 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
|
* there is no real distinction between different integer types, all of them
|
||||||
work as big.Int in Go with a limit of 256 bit in width, so you can use
|
work as big.Int in Go with a limit of 256 bit in width, so you can use
|
||||||
`int` for just about anything. This is the way integers work in Neo VM and
|
`int` for just about anything. This is the way integers work in Neo VM and
|
||||||
|
|
|
@ -465,7 +465,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.emitStoreVar(t.X.(*ast.Ident).Name, t.Sel.Name)
|
c.emitStoreVar(t.X.(*ast.Ident).Name, t.Sel.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
strct, ok := typ.Underlying().(*types.Struct)
|
strct, ok := c.getStruct(typ)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.prog.Err = fmt.Errorf("nested selector assigns not supported yet")
|
c.prog.Err = fmt.Errorf("nested selector assigns not supported yet")
|
||||||
return nil
|
return nil
|
||||||
|
@ -637,6 +637,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.emitLoadConst(c.typeAndValueOf(n))
|
c.emitLoadConst(c.typeAndValueOf(n))
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case *ast.StarExpr:
|
||||||
|
_, ok := c.getStruct(c.typeOf(n.X))
|
||||||
|
if !ok {
|
||||||
|
c.prog.Err = errors.New("dereferencing is only supported on structs")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ast.Walk(c, n.X)
|
||||||
|
c.emitConvert(stackitem.StructT)
|
||||||
|
return nil
|
||||||
|
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
if tv := c.typeAndValueOf(n); tv.Value != nil {
|
if tv := c.typeAndValueOf(n); tv.Value != nil {
|
||||||
c.emitLoadConst(tv)
|
c.emitLoadConst(tv)
|
||||||
|
@ -650,7 +660,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
case *ast.CompositeLit:
|
case *ast.CompositeLit:
|
||||||
switch typ := c.typeOf(n).Underlying().(type) {
|
switch typ := c.typeOf(n).Underlying().(type) {
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
c.convertStruct(n)
|
c.convertStruct(n, false)
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
c.convertMap(n)
|
c.convertMap(n)
|
||||||
default:
|
default:
|
||||||
|
@ -810,6 +820,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// Handle the arguments
|
// Handle the arguments
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
ast.Walk(c, arg)
|
ast.Walk(c, arg)
|
||||||
|
typ := c.typeOf(arg)
|
||||||
|
_, ok := typ.Underlying().(*types.Struct)
|
||||||
|
if ok && !isInteropPath(typ.String()) {
|
||||||
|
// To clone struct fields we create a new array and append struct to it.
|
||||||
|
// This way even non-pointer struct fields will be copied.
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.ROT)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.APPEND)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Do not swap for builtin functions.
|
// Do not swap for builtin functions.
|
||||||
if !isBuiltin {
|
if !isBuiltin {
|
||||||
|
@ -862,7 +884,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
strct, ok := typ.Underlying().(*types.Struct)
|
strct, ok := c.getStruct(typ)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.prog.Err = fmt.Errorf("selectors are supported only on structs")
|
c.prog.Err = fmt.Errorf("selectors are supported only on structs")
|
||||||
return nil
|
return nil
|
||||||
|
@ -873,6 +895,19 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *ast.UnaryExpr:
|
case *ast.UnaryExpr:
|
||||||
|
if n.Op == token.AND {
|
||||||
|
// We support only taking address from struct literals.
|
||||||
|
// For identifiers we can't support "taking address" in a general way
|
||||||
|
// because both struct and array are reference types.
|
||||||
|
lit, ok := n.X.(*ast.CompositeLit)
|
||||||
|
if ok {
|
||||||
|
c.convertStruct(lit, true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.prog.Err = fmt.Errorf("'&' can be used only with struct literals")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ast.Walk(c, n.X)
|
ast.Walk(c, n.X)
|
||||||
// From https://golang.org/ref/spec#Operators
|
// From https://golang.org/ref/spec#Operators
|
||||||
// there can be only following unary operators
|
// there can be only following unary operators
|
||||||
|
@ -1352,7 +1387,19 @@ func (c *codegen) convertMap(lit *ast.CompositeLit) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
func (c *codegen) getStruct(typ types.Type) (*types.Struct, bool) {
|
||||||
|
switch t := typ.Underlying().(type) {
|
||||||
|
case *types.Struct:
|
||||||
|
return t, true
|
||||||
|
case *types.Pointer:
|
||||||
|
strct, ok := t.Elem().Underlying().(*types.Struct)
|
||||||
|
return strct, ok
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codegen) convertStruct(lit *ast.CompositeLit, ptr bool) {
|
||||||
// Create a new structScope to initialize and store
|
// Create a new structScope to initialize and store
|
||||||
// the positions of its variables.
|
// the positions of its variables.
|
||||||
strct, ok := c.typeOf(lit).Underlying().(*types.Struct)
|
strct, ok := c.typeOf(lit).Underlying().(*types.Struct)
|
||||||
|
@ -1363,7 +1410,11 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
||||||
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.NOP)
|
emit.Opcode(c.prog.BinWriter, opcode.NOP)
|
||||||
emit.Int(c.prog.BinWriter, int64(strct.NumFields()))
|
emit.Int(c.prog.BinWriter, int64(strct.NumFields()))
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
|
if ptr {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
|
||||||
|
} else {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
|
||||||
|
}
|
||||||
|
|
||||||
keyedLit := len(lit.Elts) > 0
|
keyedLit := len(lit.Elts) > 0
|
||||||
if keyedLit {
|
if keyedLit {
|
||||||
|
|
68
pkg/compiler/pointer_test.go
Normal file
68
pkg/compiler/pointer_test.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddressOfLiteral(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
type Foo struct { A int }
|
||||||
|
func Main() int {
|
||||||
|
f := &Foo{}
|
||||||
|
setA(f, 3)
|
||||||
|
return f.A
|
||||||
|
}
|
||||||
|
func setA(s *Foo, a int) { s.A = a }`
|
||||||
|
eval(t, src, big.NewInt(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointerDereference(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
type Foo struct { A int }
|
||||||
|
func Main() int {
|
||||||
|
f := &Foo{A: 4}
|
||||||
|
setA(*f, 3)
|
||||||
|
return f.A
|
||||||
|
}
|
||||||
|
func setA(s Foo, a int) { s.A = a }`
|
||||||
|
eval(t, src, big.NewInt(4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructArgCopy(t *testing.T) {
|
||||||
|
t.Run("Simple", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
type Foo struct { A int }
|
||||||
|
func Main() int {
|
||||||
|
f := Foo{A: 4}
|
||||||
|
setA(f, 3)
|
||||||
|
return f.A
|
||||||
|
}
|
||||||
|
func setA(s Foo, a int) { s.A = a }`
|
||||||
|
eval(t, src, big.NewInt(4))
|
||||||
|
})
|
||||||
|
t.Run("StructField", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
type Bar struct { A int }
|
||||||
|
type Foo struct { B Bar }
|
||||||
|
func Main() int {
|
||||||
|
f := Foo{B: Bar{A: 4}}
|
||||||
|
setA(f, 3)
|
||||||
|
return f.B.A
|
||||||
|
}
|
||||||
|
func setA(s Foo, a int) { s.B.A = a }`
|
||||||
|
eval(t, src, big.NewInt(4))
|
||||||
|
})
|
||||||
|
t.Run("StructPointerField", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
type Bar struct { A int }
|
||||||
|
type Foo struct { B *Bar }
|
||||||
|
func Main() int {
|
||||||
|
f := Foo{B: &Bar{A: 4}}
|
||||||
|
setA(f, 3)
|
||||||
|
return f.B.A
|
||||||
|
}
|
||||||
|
func setA(s Foo, a int) { s.B.A = a }`
|
||||||
|
eval(t, src, big.NewInt(3))
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue