Debugging support.
For #65711.
Change-Id: I2b8b03d2c6e02d32a4f9272313e852f17da35b3e
Reviewed-on: https://go-review.googlesource.com/c/go/+/567975
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
 
 package types2
 
+import "cmd/compile/internal/syntax"
+
 // validType verifies that the given type does not "expand" indefinitely
 // producing a cycle in the type graph.
 // (Cycles involving alias types, as in "type A = [10]A" are detected
 // earlier, via the objDecl cycle detection mechanism.)
 func (check *Checker) validType(typ *Named) {
-       check.validType0(typ, nil, nil)
+       check.validType0(nopos, typ, nil, nil)
 }
 
 // validType0 checks if the given type is valid. If typ is a type parameter
 // of) F in S, leading to the nest S->F. If a type appears in its own nest
 // (say S->F->S) we have an invalid recursive type. The path list is the full
 // path of named types in a cycle, it is only needed for error reporting.
-func (check *Checker) validType0(typ Type, nest, path []*Named) bool {
-       switch t := Unalias(typ).(type) {
+func (check *Checker) validType0(pos syntax.Pos, typ Type, nest, path []*Named) bool {
+       typ = Unalias(typ)
+
+       if check.conf.Trace {
+               if t, _ := typ.(*Named); t != nil && t.obj != nil /* obj should always exist but be conservative */ {
+                       pos = t.obj.pos
+               }
+               check.indent++
+               check.trace(pos, "validType(%s) nest %v, path %v", typ, pathString(makeObjList(nest)), pathString(makeObjList(path)))
+               defer func() {
+                       check.indent--
+               }()
+       }
+
+       switch t := typ.(type) {
        case nil:
                // We should never see a nil type but be conservative and panic
                // only in debug mode.
                }
 
        case *Array:
-               return check.validType0(t.elem, nest, path)
+               return check.validType0(pos, t.elem, nest, path)
 
        case *Struct:
                for _, f := range t.fields {
-                       if !check.validType0(f.typ, nest, path) {
+                       if !check.validType0(pos, f.typ, nest, path) {
                                return false
                        }
                }
 
        case *Union:
                for _, t := range t.terms {
-                       if !check.validType0(t.typ, nest, path) {
+                       if !check.validType0(pos, t.typ, nest, path) {
                                return false
                        }
                }
 
        case *Interface:
                for _, etyp := range t.embeddeds {
-                       if !check.validType0(etyp, nest, path) {
+                       if !check.validType0(pos, etyp, nest, path) {
                                return false
                        }
                }
                // Every type added to nest is also added to path; thus every type that is in nest
                // must also be in path (invariant). But not every type in path is in nest, since
                // nest may be pruned (see below, *TypeParam case).
-               if !check.validType0(t.Origin().fromRHS, append(nest, t), append(path, t)) {
+               if !check.validType0(pos, t.Origin().fromRHS, append(nest, t), append(path, t)) {
                        return false
                }
 
                                        // the current (instantiated) type (see the example
                                        // at the end of this file).
                                        // For error reporting we keep the full path.
-                                       return check.validType0(targ, nest[:len(nest)-1], path)
+                                       return check.validType0(pos, targ, nest[:len(nest)-1], path)
                                }
                        }
                }
 
        "unify.go":         fixSprintf,
        "universe.go":      fixGlobalTypVarDecl,
        "util_test.go":     fixTokenPos,
-       "validtype.go":     nil,
+       "validtype.go":     func(f *ast.File) { fixTokenPos(f); renameSelectors(f, "Trace->_Trace") },
 }
 
 // TODO(gri) We should be able to make these rewriters more configurable/composable.
 
 
 package types
 
+import "go/token"
+
 // validType verifies that the given type does not "expand" indefinitely
 // producing a cycle in the type graph.
 // (Cycles involving alias types, as in "type A = [10]A" are detected
 // earlier, via the objDecl cycle detection mechanism.)
 func (check *Checker) validType(typ *Named) {
-       check.validType0(typ, nil, nil)
+       check.validType0(nopos, typ, nil, nil)
 }
 
 // validType0 checks if the given type is valid. If typ is a type parameter
 // of) F in S, leading to the nest S->F. If a type appears in its own nest
 // (say S->F->S) we have an invalid recursive type. The path list is the full
 // path of named types in a cycle, it is only needed for error reporting.
-func (check *Checker) validType0(typ Type, nest, path []*Named) bool {
-       switch t := Unalias(typ).(type) {
+func (check *Checker) validType0(pos token.Pos, typ Type, nest, path []*Named) bool {
+       typ = Unalias(typ)
+
+       if check.conf._Trace {
+               if t, _ := typ.(*Named); t != nil && t.obj != nil /* obj should always exist but be conservative */ {
+                       pos = t.obj.pos
+               }
+               check.indent++
+               check.trace(pos, "validType(%s) nest %v, path %v", typ, pathString(makeObjList(nest)), pathString(makeObjList(path)))
+               defer func() {
+                       check.indent--
+               }()
+       }
+
+       switch t := typ.(type) {
        case nil:
                // We should never see a nil type but be conservative and panic
                // only in debug mode.
                }
 
        case *Array:
-               return check.validType0(t.elem, nest, path)
+               return check.validType0(pos, t.elem, nest, path)
 
        case *Struct:
                for _, f := range t.fields {
-                       if !check.validType0(f.typ, nest, path) {
+                       if !check.validType0(pos, f.typ, nest, path) {
                                return false
                        }
                }
 
        case *Union:
                for _, t := range t.terms {
-                       if !check.validType0(t.typ, nest, path) {
+                       if !check.validType0(pos, t.typ, nest, path) {
                                return false
                        }
                }
 
        case *Interface:
                for _, etyp := range t.embeddeds {
-                       if !check.validType0(etyp, nest, path) {
+                       if !check.validType0(pos, etyp, nest, path) {
                                return false
                        }
                }
                // Every type added to nest is also added to path; thus every type that is in nest
                // must also be in path (invariant). But not every type in path is in nest, since
                // nest may be pruned (see below, *TypeParam case).
-               if !check.validType0(t.Origin().fromRHS, append(nest, t), append(path, t)) {
+               if !check.validType0(pos, t.Origin().fromRHS, append(nest, t), append(path, t)) {
                        return false
                }
 
                                        // the current (instantiated) type (see the example
                                        // at the end of this file).
                                        // For error reporting we keep the full path.
-                                       return check.validType0(targ, nest[:len(nest)-1], path)
+                                       return check.validType0(pos, targ, nest[:len(nest)-1], path)
                                }
                        }
                }