go.tools/go/types: print initialization cycles
Also: Always report cycle at first (in source order) variable in the cycle. R=adonovan, gri CC=golang-dev https://golang.org/cl/23370043
This commit is contained in:
parent
0d4ee40f21
commit
e8fe66cd57
|
@ -224,10 +224,9 @@ func checkFiles(t *testing.T, testfiles []string) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Ignore error messages containing "other declaration":
|
// Ignore secondary error messages starting with "\t";
|
||||||
// They are follow-up error messages after a redeclaration
|
// they are clarifying messages for a primary error.
|
||||||
// error.
|
if !strings.Contains(err.Error(), ": \t") {
|
||||||
if !strings.Contains(err.Error(), "other declaration") {
|
|
||||||
errlist = append(errlist, err)
|
errlist = append(errlist, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func (check *checker) reportAltDecl(obj Object) {
|
||||||
// We use "other" rather than "previous" here because
|
// We use "other" rather than "previous" here because
|
||||||
// the first declaration seen may not be textually
|
// the first declaration seen may not be textually
|
||||||
// earlier in the source.
|
// earlier in the source.
|
||||||
check.errorf(pos, "other declaration of %s", obj.Name())
|
check.errorf(pos, "\tother declaration of %s", obj.Name()) // secondary error, \t indented
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +420,8 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
for _, obj := range objList {
|
for _, obj := range objList {
|
||||||
if obj, _ := obj.(*Var); obj != nil {
|
if obj, _ := obj.(*Var); obj != nil {
|
||||||
if init := initMap[obj]; init != nil {
|
if init := initMap[obj]; init != nil {
|
||||||
check.dependencies(obj, init, nil, 0)
|
// provide non-nil path for re-use of underlying array
|
||||||
|
check.dependencies(obj, init, make([]Object, 0, 8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -494,54 +495,72 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
// manner and appends the encountered variables in postorder to the Info.InitOrder list.
|
// manner and appends the encountered variables in postorder to the Info.InitOrder list.
|
||||||
// As a result, that list ends up being sorted topologically in the order of dependencies.
|
// As a result, that list ends up being sorted topologically in the order of dependencies.
|
||||||
//
|
//
|
||||||
// The current node is represented by the pair (obj, init); last is the last variable seen
|
// The current node is represented by the pair (obj, init); and path contains all nodes
|
||||||
// on the path to the current node; and n is the number of variables seen on the path before
|
// on the path to the current node (excluding the current node).
|
||||||
// reaching the current node.
|
|
||||||
//
|
//
|
||||||
// To detect cyles, the nodes are marked as follows: Initially, all nodes are unmarked
|
// To detect cyles, the nodes are marked as follows: Initially, all nodes are unmarked
|
||||||
// (declInfo.mark == 0). On the way down, a node is marked with a value > 0 ("in progress"),
|
// (declInfo.mark == 0). On the way down, a node is appended to the path, and the node
|
||||||
// and on the way up, a node is marked with a value < 0 ("finished"). A cycle is detected
|
// is marked with a value > 0 ("in progress"). On the way up, a node is marked with a
|
||||||
// if traversal reaches a node that is marked "in progress".
|
// value < 0 ("finished"). A cycle is detected if a node is reached that is marked as
|
||||||
|
// "in progress".
|
||||||
//
|
//
|
||||||
// A cycle must contain at least one variable to be invalid (cycles containing only functions
|
// A cycle must contain at least one variable to be invalid (cycles containing only
|
||||||
// are permitted). To detect such cycles, the "in progress" value is the number of variables
|
// functions are permitted). To detect such a cycle, and in order to print it, the
|
||||||
// seen on the path to a specific node (including that node). That value is always > 0 because
|
// mark value indicating "in progress" is the path length up to (and including) the
|
||||||
// the root nodes are variables. If the mark values are different when a cycle is detected,
|
// current node; i.e. the length of the path after appending the node. Naturally,
|
||||||
// the difference is the number of variables in the cycle, and the last variable seen on the
|
// that value is > 0 as required for "in progress" marks. In other words, each node's
|
||||||
// path must be a variable in the cycle.
|
// "in progress" mark value corresponds to the node's path index plus 1. Accordingly,
|
||||||
|
// when the first node of a cycle is reached, that node's mark value indicates the
|
||||||
|
// start of the cycle in the path. The tail of the path (path[mark-1:]) contains all
|
||||||
|
// nodes of the cycle.
|
||||||
//
|
//
|
||||||
func (check *checker) dependencies(obj Object, init *declInfo, last *Var, n int) {
|
func (check *checker) dependencies(obj Object, init *declInfo, path []Object) {
|
||||||
if init.mark < 0 {
|
if init.mark < 0 {
|
||||||
return // finished
|
return // finished
|
||||||
}
|
}
|
||||||
|
|
||||||
this, _ := obj.(*Var)
|
|
||||||
if this != nil {
|
|
||||||
last = this // this is the last variable seen on the path
|
|
||||||
n++ // count the variable
|
|
||||||
}
|
|
||||||
|
|
||||||
if init.mark > 0 {
|
if init.mark > 0 {
|
||||||
// cycle detected
|
// cycle detected - find index of first variable in cycle, if any
|
||||||
if init.mark != n {
|
first := -1
|
||||||
// cycle contains variables - use the last one seen for error
|
cycle := path[init.mark-1:]
|
||||||
check.errorf(last.pos, "initialization cycle for %s", last.name)
|
for i, obj := range cycle {
|
||||||
// TODO(gri) print the cycle
|
if _, ok := obj.(*Var); ok {
|
||||||
init.mark = -1 // avoid further errors
|
first = i
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// only report an error if there's at least one variable
|
||||||
|
if first >= 0 {
|
||||||
|
obj := cycle[first]
|
||||||
|
check.errorf(obj.Pos(), "initialization cycle for %s", obj.Name())
|
||||||
|
// print cycle
|
||||||
|
i := first
|
||||||
|
for _ = range cycle {
|
||||||
|
check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented
|
||||||
|
i++
|
||||||
|
if i >= len(cycle) {
|
||||||
|
i = 0
|
||||||
|
}
|
||||||
|
obj = cycle[i]
|
||||||
|
}
|
||||||
|
check.errorf(obj.Pos(), "\t%s (cycle start)", obj.Name())
|
||||||
|
|
||||||
|
}
|
||||||
|
init.mark = -1 // avoid further errors
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init.mark == 0
|
// init.mark == 0
|
||||||
|
|
||||||
init.mark = n // init.mark > 0
|
path = append(path, obj) // len(path) > 0
|
||||||
|
init.mark = len(path) // init.mark > 0
|
||||||
for obj, dep := range init.deps {
|
for obj, dep := range init.deps {
|
||||||
check.dependencies(obj, dep, last, n)
|
check.dependencies(obj, dep, path)
|
||||||
}
|
}
|
||||||
init.mark = -1 // init.mark < 0
|
init.mark = -1 // init.mark < 0
|
||||||
|
|
||||||
// record the init order for variables
|
// record the init order for variables
|
||||||
if this != nil {
|
if this, _ := obj.(*Var); this != nil {
|
||||||
initLhs := init.lhs // possibly nil (see declInfo.lhs field comment)
|
initLhs := init.lhs // possibly nil (see declInfo.lhs field comment)
|
||||||
if initLhs == nil {
|
if initLhs == nil {
|
||||||
initLhs = []*Var{this}
|
initLhs = []*Var{this}
|
||||||
|
|
|
@ -9,6 +9,8 @@ package init0
|
||||||
// type-checking, not initialization cycles (we don't know the types)
|
// type-checking, not initialization cycles (we don't know the types)
|
||||||
// (avoid duplicate errors)
|
// (avoid duplicate errors)
|
||||||
var (
|
var (
|
||||||
|
x0 /* ERROR cycle */ = x0
|
||||||
|
|
||||||
x1 /* ERROR cycle */ = y1
|
x1 /* ERROR cycle */ = y1
|
||||||
y1 = x1
|
y1 = x1
|
||||||
|
|
||||||
|
@ -20,6 +22,8 @@ var (
|
||||||
|
|
||||||
// initialization cycles (we know the types)
|
// initialization cycles (we know the types)
|
||||||
var (
|
var (
|
||||||
|
x00 /* ERROR initialization cycle */ int = x00
|
||||||
|
|
||||||
x2 /* ERROR initialization cycle */ int = y2
|
x2 /* ERROR initialization cycle */ int = y2
|
||||||
y2 = x2
|
y2 = x2
|
||||||
|
|
||||||
|
@ -42,7 +46,7 @@ var x4 = x5
|
||||||
var x5 /* ERROR initialization cycle */ = f1()
|
var x5 /* ERROR initialization cycle */ = f1()
|
||||||
func f1() int { return x5*10 }
|
func f1() int { return x5*10 }
|
||||||
|
|
||||||
var x6, x7 /* ERROR initialization cycle */ = f2()
|
var x6 /* ERROR initialization cycle */ , x7 = f2()
|
||||||
var x8 = x7
|
var x8 = x7
|
||||||
func f2() (int, int) { return f3() + f3(), 0 }
|
func f2() (int, int) { return f3() + f3(), 0 }
|
||||||
func f3() int { return x8 }
|
func f3() int { return x8 }
|
||||||
|
|
Loading…
Reference in New Issue