From 27520f6dae030e9da8e7ed5d3058af737e8aee33 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 20 Feb 2020 12:44:04 +0100 Subject: [PATCH] fix: re-apply GTA until all global types/constants are defined --- _test/assign1.go | 2 +- _test/bad0.go | 2 +- _test/complex3.go | 2 +- _test/const10.go | 17 +++++++++++ _test/const8.go | 15 +++++++++ _test/const9.go | 17 +++++++++++ _test/map19.go | 3 ++ interp/cfg.go | 13 +++++--- interp/gta.go | 52 +++++++++++++++++++++++++++++++- interp/interp.go | 12 ++------ interp/interp_consistent_test.go | 6 ++++ interp/src.go | 10 +++--- interp/type.go | 4 +-- 13 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 _test/const10.go create mode 100644 _test/const8.go create mode 100644 _test/const9.go diff --git a/_test/assign1.go b/_test/assign1.go index 99f564a2e..768c35a8b 100644 --- a/_test/assign1.go +++ b/_test/assign1.go @@ -9,5 +9,5 @@ func main() { fmt.Println(buf) } -// Output +// Output: // [] diff --git a/_test/bad0.go b/_test/bad0.go index f7e8fea2d..09b07b5ba 100644 --- a/_test/bad0.go +++ b/_test/bad0.go @@ -1,4 +1,4 @@ println("Hello") // Error: -// _test/bad0.go:1:1: expected 'package', found println +// 1:1: expected 'package', found println diff --git a/_test/complex3.go b/_test/complex3.go index 1e8956240..035b5921a 100644 --- a/_test/complex3.go +++ b/_test/complex3.go @@ -7,5 +7,5 @@ func main() { fmt.Printf("%T %v\n", s, s) } -// Output +// Output: // int 2 diff --git a/_test/const10.go b/_test/const10.go new file mode 100644 index 000000000..40bbb3cd2 --- /dev/null +++ b/_test/const10.go @@ -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 diff --git a/_test/const8.go b/_test/const8.go new file mode 100644 index 000000000..48aae4c35 --- /dev/null +++ b/_test/const8.go @@ -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 diff --git a/_test/const9.go b/_test/const9.go new file mode 100644 index 000000000..29330dde2 --- /dev/null +++ b/_test/const9.go @@ -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 diff --git a/_test/map19.go b/_test/map19.go index 89b60a2b4..808ba33e3 100644 --- a/_test/map19.go +++ b/_test/map19.go @@ -14,3 +14,6 @@ func main() { m := cmap{} fmt.Println(m) } + +// Output: +// {map[]} diff --git a/interp/cfg.go b/interp/cfg.go index e0398ef76..90efd337d 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -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, @@ -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 { diff --git a/interp/gta.go b/interp/gta.go index cbc1fb0b1..79d5c79c4 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -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 { @@ -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 { @@ -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 +} diff --git a/interp/interp.go b/interp/interp.go index f314366fb..f55d51ccd 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -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 @@ -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) diff --git a/interp/interp_consistent_test.go b/interp/interp_consistent_test.go index a66ccd78c..26c402bb3 100644 --- a/interp/interp_consistent_test.go +++ b/interp/interp_consistent_test.go @@ -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 @@ -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", diff --git a/interp/src.go b/interp/src.go index 986491122..7e493ddf6 100644 --- a/interp/src.go +++ b/interp/src.go @@ -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) { @@ -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 } } diff --git a/interp/type.go b/interp/type.go index 7583077ee..d0a86b30d 100644 --- a/interp/type.go +++ b/interp/type.go @@ -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 @@ -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 {