Skip to content

Commit

Permalink
fix: re-apply GTA until all global types/constants are defined
Browse files Browse the repository at this point in the history
  • Loading branch information
mvertes authored Feb 20, 2020
1 parent 7037424 commit 27520f6
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 25 deletions.
2 changes: 1 addition & 1 deletion _test/assign1.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ func main() {
fmt.Println(buf)
}

// Output
// Output:
// []
2 changes: 1 addition & 1 deletion _test/bad0.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
println("Hello")

// Error:
// _test/bad0.go:1:1: expected 'package', found println
// 1:1: expected 'package', found println
2 changes: 1 addition & 1 deletion _test/complex3.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ func main() {
fmt.Printf("%T %v\n", s, s)
}

// Output
// Output:
// int 2
17 changes: 17 additions & 0 deletions _test/const10.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

const (
a = 2
b = c + d
c = a + d
d = e + f
e = 3
f = 4
)

func main() {
println(b)
}

// Output:
// 16
15 changes: 15 additions & 0 deletions _test/const8.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

const (
a = 2
b = c + d
c = 4
d = 5
)

func main() {
println(a, b, c, d)
}

// Output:
// 2 9 4 5
17 changes: 17 additions & 0 deletions _test/const9.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

const (
a = 2
b = c + d
c = a + d
d = e + f
e = b + 2
f = 4
)

func main() {
println(b)
}

// Error:
// 5:2: constant definition loop
3 changes: 3 additions & 0 deletions _test/map19.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ func main() {
m := cmap{}
fmt.Println(m)
}

// Output:
// {map[]}
13 changes: 9 additions & 4 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import (
)

// A cfgError represents an error during CFG build stage
type cfgError error
type cfgError struct {
*node
error
}

func (c *cfgError) Error() string { return c.error.Error() }

var constOp = map[action]func(*node){
aAdd: addConst,
Expand Down Expand Up @@ -1539,13 +1544,13 @@ func childPos(n *node) int {
return -1
}

func (n *node) cfgErrorf(format string, a ...interface{}) cfgError {
func (n *node) cfgErrorf(format string, a ...interface{}) *cfgError {
a = append([]interface{}{n.interp.fset.Position(n.pos)}, a...)
return cfgError(fmt.Errorf("%s: "+format, a...))
return &cfgError{n, fmt.Errorf("%s: "+format, a...)}
}

func genRun(nod *node) error {
var err cfgError
var err error

nod.Walk(func(n *node) bool {
if err != nil {
Expand Down
52 changes: 51 additions & 1 deletion interp/gta.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
iotaValue = 0
// Early parse of constDecl subtree, to compute all constant
// values which may be necessary in further declarations.
_, err = interp.cfg(n, pkgID)
if _, err = interp.cfg(n, pkgID); err != nil {
// No error processing here, to allow recovery in subtree nodes.
err = nil
}

case blockStmt:
if n != root {
Expand All @@ -47,6 +50,14 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
for i := 0; i < n.nleft; i++ {
dest, src := n.child[i], n.child[sbase+i]
val := reflect.ValueOf(iotaValue)
if n.anc.kind == constDecl {
if _, err2 := interp.cfg(n, pkgID); err2 != nil {
// Constant value can not be computed yet.
// Come back when child dependencies are known.
revisit = append(revisit, n)
return false
}
}
typ := atyp
if typ == nil {
if typ, err = nodeType(interp, sc, src); err != nil {
Expand Down Expand Up @@ -210,3 +221,42 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
return revisit, err
}

// gtaRetry (re)applies gta until all global constants and types are defined.
func (interp *Interpreter) gtaRetry(nodes []*node, rpath, pkgID string) error {
revisit := []*node{}
for {
for _, n := range nodes {
list, err := interp.gta(n, rpath, pkgID)
if err != nil {
return err
}
revisit = append(revisit, list...)
}

if len(revisit) == 0 || equalNodes(nodes, revisit) {
break
}

nodes = revisit
revisit = []*node{}
}

if len(revisit) > 0 {
return revisit[0].cfgErrorf("constant definition loop")
}
return nil
}

// equalNodes returns true if two slices of nodes are identical.
func equalNodes(a, b []*node) bool {
if len(a) != len(b) {
return false
}
for i, n := range a {
if n != b[i] {
return false
}
}
return true
}
12 changes: 3 additions & 9 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func (interp *Interpreter) main() *node {
func (interp *Interpreter) Eval(src string) (reflect.Value, error) {
var res reflect.Value

// Parse source to AST
// Parse source to AST.
pkgName, root, err := interp.ast(src, interp.Name)
if err != nil || root == nil {
return res, err
Expand All @@ -307,16 +307,10 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) {
}
}

// Global type analysis
revisit, err := interp.gta(root, pkgName, interp.Name)
if err != nil {
// Perform global types analysis.
if err = interp.gtaRetry([]*node{root}, pkgName, interp.Name); err != nil {
return res, err
}
for _, n := range revisit {
if _, err = interp.gta(n, pkgName, interp.Name); err != nil {
return res, err
}
}

// Annotate AST with CFG infos
initNodes, err := interp.cfg(root, interp.Name)
Expand Down
6 changes: 6 additions & 0 deletions interp/interp_consistent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
for _, file := range files {
if filepath.Ext(file.Name()) != ".go" ||
file.Name() == "bad0.go" || // expect error
file.Name() == "const9.go" || // expect error
file.Name() == "export1.go" || // non-main package
file.Name() == "export0.go" || // non-main package
file.Name() == "for7.go" || // expect error
Expand Down Expand Up @@ -138,6 +139,11 @@ func TestInterpErrorConsistency(t *testing.T) {
expectedInterp: "1:1: expected 'package', found println",
expectedExec: "1:1: expected 'package', found println",
},
{
fileName: "const9.go",
expectedInterp: "5:2: constant definition loop",
expectedExec: "5:2: constant definition loop",
},
{
fileName: "if2.go",
expectedInterp: "7:5: non-bool used as if condition",
Expand Down
10 changes: 4 additions & 6 deletions interp/src.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (interp *Interpreter) importSrc(rPath, path string) error {
var root *node
var pkgName string

// Parse source files
// Parse source files.
for _, file := range files {
name := file.Name()
if skipFile(&interp.context, name) {
Expand Down Expand Up @@ -86,12 +86,10 @@ func (interp *Interpreter) importSrc(rPath, path string) error {
revisit[subRPath] = append(revisit[subRPath], list...)
}

// revisit incomplete nodes where GTA could not complete
// Revisit incomplete nodes where GTA could not complete.
for pkg, nodes := range revisit {
for _, n := range nodes {
if _, err = interp.gta(n, pkg, path); err != nil {
return err
}
if err = interp.gtaRetry(nodes, pkg, path); err != nil {
return err
}
}

Expand Down
4 changes: 2 additions & 2 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
}

var err cfgError
var err error
switch n.kind {
case addressExpr, starExpr:
t.cat = ptrT
Expand Down Expand Up @@ -614,7 +614,7 @@ func init() {

// if type is incomplete, re-parse it.
func (t *itype) finalize() (*itype, error) {
var err cfgError
var err error
if t.incomplete {
sym, _, found := t.scope.lookup(t.name)
if found && !sym.typ.incomplete {
Expand Down

0 comments on commit 27520f6

Please sign in to comment.