compiler: initialize packages according to go spec

`init()` functions should be called during package initialization,
after global variables were processed.
This commit is contained in:
Evgenii Stratonikov 2020-08-05 11:14:43 +03:00
parent 6f2759be3a
commit 439d9ff94d
4 changed files with 37 additions and 5 deletions

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"golang.org/x/tools/go/loader"
)
var (
@ -47,8 +48,20 @@ func (c *codegen) traverseGlobals() int {
return 0
}
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
c.ForEachFile(c.convertGlobals)
c.ForEachFile(c.convertInitFuncs)
c.ForEachPackage(func(pkg *loader.PackageInfo) {
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
c.convertGlobals(f, pkg.Pkg)
}
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
c.convertInitFuncs(f, pkg.Pkg)
}
// because we reuse `convertFuncDecl` for init funcs,
// we need to cleare scope, so that global variables
// encountered after will be recognized as globals.
c.scope = nil
})
}
return n
}

View file

@ -47,17 +47,25 @@ type buildInfo struct {
program *loader.Program
}
// ForEachFile executes fn on each file used in current program.
func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) {
// ForEachPackage executes fn on each package used in the current program
// in the order they should be initialized.
func (c *codegen) ForEachPackage(fn func(*loader.PackageInfo)) {
for i := range c.packages {
pkg := c.buildInfo.program.Package(c.packages[i])
c.typeInfo = &pkg.Info
c.currPkg = pkg.Pkg
fn(pkg)
}
}
// ForEachFile executes fn on each file used in current program.
func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) {
c.ForEachPackage(func(pkg *loader.PackageInfo) {
for _, f := range pkg.Files {
c.fillImportMap(f, pkg.Pkg)
fn(f, pkg.Pkg)
}
}
})
}
// fillImportMap fills import map for f.

View file

@ -80,4 +80,11 @@ func TestImportOrder(t *testing.T) {
func Main() int { return pkg3.A }`
eval(t, src, big.NewInt(1))
})
t.Run("InitializeOnce", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/pkg3"
var A = pkg3.A
func Main() int { return A }`
eval(t, src, big.NewInt(3))
})
}

View file

@ -1,3 +1,7 @@
package pkg3
var A int
func init() {
A = 3
}