Skip to content

Commit e57cdd8

Browse files
committed
go/types: initial framework for marking-based cycle detection
The existing code explicitly passes a (type name) path around to determine cycles; it also restarts the path for types that "break" a cycle (such as a pointer, function, etc.). This does not work for alias types (whose cycles are broken in a different way). Furthermore, because the path is not passed through all type checker functions that need it, we can't see the path or use it for detection of some cycles (e.g. cycles involving array lengths), which required ad-hoc solutions in those cases. This change introduces an explicit marking scheme for any kind of object; an object is painted in various colors indicating its state. It also introduces an object path (a stack) main- tained with the Checker state, which is available in all type checker functions that need access to it. The change only introduces these mechanisms and exercises the basic functionality, with no effect on the existing code for now. For #25141. Change-Id: I7c28714bdafe6c8d9afedf12a8a887554237337c Reviewed-on: https://go-review.googlesource.com/114517 Reviewed-by: Alan Donovan <[email protected]>
1 parent 57d40f1 commit e57cdd8

File tree

4 files changed

+143
-12
lines changed

4 files changed

+143
-12
lines changed

src/go/types/check.go

+16
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ type Checker struct {
9090
interfaces map[*TypeName]*ifaceInfo // maps interface type names to corresponding interface infos
9191
untyped map[ast.Expr]exprInfo // map of expressions without final type
9292
delayed []func() // stack of delayed actions
93+
objPath []Object // path of object dependencies during type inference (for cycle reporting)
9394

9495
// context within which the current object is type-checked
9596
// (valid only for the duration of type-checking a specific object)
@@ -144,6 +145,21 @@ func (check *Checker) later(f func()) {
144145
check.delayed = append(check.delayed, f)
145146
}
146147

148+
// push pushes obj onto the object path and returns its index in the path.
149+
func (check *Checker) push(obj Object) int {
150+
check.objPath = append(check.objPath, obj)
151+
return len(check.objPath) - 1
152+
}
153+
154+
// pop pops and returns the topmost object from the object path.
155+
func (check *Checker) pop() Object {
156+
i := len(check.objPath) - 1
157+
obj := check.objPath[i]
158+
check.objPath[i] = nil
159+
check.objPath = check.objPath[:i]
160+
return obj
161+
}
162+
147163
// NewChecker returns a new Checker instance for a given package.
148164
// Package files may be added incrementally via checker.Files.
149165
func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker {

src/go/types/decl.go

+76-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,82 @@ func pathString(path []*TypeName) string {
5252
// objDecl type-checks the declaration of obj in its respective (file) context.
5353
// See check.typ for the details on def and path.
5454
func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
55-
if obj.Type() != nil {
56-
return // already checked - nothing to do
55+
// Checking the declaration of obj means inferring its type
56+
// (and possibly its value, for constants).
57+
// An object's type (and thus the object) may be in one of
58+
// three states which are expressed by colors:
59+
//
60+
// - an object whose type is not yet known is painted white (initial color)
61+
// - an object whose type is in the process of being inferred is painted grey
62+
// - an object whose type is fully inferred is painted black
63+
//
64+
// During type inference, an object's color changes from white to grey
65+
// to black (pre-declared objects are painted black from the start).
66+
// A black object (i.e., its type) can only depend on (refer to) other black
67+
// ones. White and grey objects may depend on white and black objects.
68+
// A dependency on a grey object indicates a cycle which may or may not be
69+
// valid.
70+
//
71+
// When objects turn grey, they are pushed on the object path (a stack);
72+
// they are popped again when they turn black. Thus, if a grey object (a
73+
// cycle) is encountered, it is on the object path, and all the objects
74+
// it depends on are the remaining objects on that path. Color encoding
75+
// is such that the color value of a grey object indicates the index of
76+
// that object in the object path.
77+
78+
// During type-checking, white objects may be assigned a type without
79+
// traversing through objDecl; e.g., when initializing constants and
80+
// variables. Update the colors of those objects here (rather than
81+
// everywhere where we set the type) to satisfy the color invariants.
82+
if obj.color() == white && obj.Type() != nil {
83+
obj.setColor(black)
84+
return
85+
}
86+
87+
switch obj.color() {
88+
case white:
89+
assert(obj.Type() == nil)
90+
// All color values other than white and black are considered grey.
91+
// Because black and white are < grey, all values >= grey are grey.
92+
// Use those values to encode the object's index into the object path.
93+
obj.setColor(grey + color(check.push(obj)))
94+
defer func() {
95+
check.pop().setColor(black)
96+
}()
97+
98+
case black:
99+
assert(obj.Type() != nil)
100+
return
101+
102+
default:
103+
// Color values other than white or black are considered grey.
104+
fallthrough
105+
106+
case grey:
107+
// We have a cycle.
108+
// In the existing code, this is marked by a non-nil type
109+
// for the object except for constants and variables, which
110+
// have their own "visited" flag (the new marking approach
111+
// will allow us to remove that flag eventually). Their type
112+
// may be nil because they haven't determined their init
113+
// values yet (from which to deduce the type). But in that
114+
// case, they must have been marked as visited.
115+
// For now, handle constants and variables specially.
116+
visited := false
117+
switch obj := obj.(type) {
118+
case *Const:
119+
visited = obj.visited
120+
case *Var:
121+
visited = obj.visited
122+
default:
123+
assert(obj.Type() != nil)
124+
return
125+
}
126+
if obj.Type() != nil {
127+
return
128+
}
129+
assert(visited)
130+
57131
}
58132

59133
if trace {

src/go/types/object.go

+49-9
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@ type Object interface {
3434
// 0 for all other objects (including objects in file scopes).
3535
order() uint32
3636

37+
// color returns the object's color.
38+
color() color
39+
3740
// setOrder sets the order number of the object. It must be > 0.
3841
setOrder(uint32)
3942

43+
// setColor sets the object's color. It must not be white.
44+
setColor(color color)
45+
4046
// setParent sets the parent scope of the object.
4147
setParent(*Scope)
4248

@@ -78,9 +84,41 @@ type object struct {
7884
name string
7985
typ Type
8086
order_ uint32
87+
color_ color
8188
scopePos_ token.Pos
8289
}
8390

91+
// color encodes the color of an object (see Checker.objDecl for details).
92+
type color uint32
93+
94+
// An object may be painted in one of three colors.
95+
// Color values other than white or black are considered grey.
96+
const (
97+
white color = iota
98+
black
99+
grey // must be > white and black
100+
)
101+
102+
func (c color) String() string {
103+
switch c {
104+
case white:
105+
return "white"
106+
case black:
107+
return "black"
108+
default:
109+
return "grey"
110+
}
111+
}
112+
113+
// colorFor returns the (initial) color for an object depending on
114+
// whether its type t is known or not.
115+
func colorFor(t Type) color {
116+
if t != nil {
117+
return black
118+
}
119+
return white
120+
}
121+
84122
// Parent returns the scope in which the object is declared.
85123
// The result is nil for methods and struct fields.
86124
func (obj *object) Parent() *Scope { return obj.parent }
@@ -108,10 +146,12 @@ func (obj *object) Id() string { return Id(obj.pkg, obj.name) }
108146

109147
func (obj *object) String() string { panic("abstract") }
110148
func (obj *object) order() uint32 { return obj.order_ }
149+
func (obj *object) color() color { return obj.color_ }
111150
func (obj *object) scopePos() token.Pos { return obj.scopePos_ }
112151

113152
func (obj *object) setParent(parent *Scope) { obj.parent = parent }
114153
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
154+
func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
115155
func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos }
116156

117157
func (obj *object) sameId(pkg *Package, name string) bool {
@@ -147,7 +187,7 @@ type PkgName struct {
147187
// NewPkgName returns a new PkgName object representing an imported package.
148188
// The remaining arguments set the attributes found with all Objects.
149189
func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName {
150-
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, token.NoPos}, imported, false}
190+
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, token.NoPos}, imported, false}
151191
}
152192

153193
// Imported returns the package that was imported.
@@ -164,7 +204,7 @@ type Const struct {
164204
// NewConst returns a new constant with value val.
165205
// The remaining arguments set the attributes found with all Objects.
166206
func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const {
167-
return &Const{object{nil, pos, pkg, name, typ, 0, token.NoPos}, val, false}
207+
return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, val, false}
168208
}
169209

170210
// Val returns the constant's value.
@@ -185,7 +225,7 @@ type TypeName struct {
185225
// argument for NewNamed, which will set the TypeName's type as a side-
186226
// effect.
187227
func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
188-
return &TypeName{object{nil, pos, pkg, name, typ, 0, token.NoPos}}
228+
return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}}
189229
}
190230

191231
// IsAlias reports whether obj is an alias name for a type.
@@ -224,19 +264,19 @@ type Var struct {
224264
// NewVar returns a new variable.
225265
// The arguments set the attributes found with all Objects.
226266
func NewVar(pos token.Pos, pkg *Package, name string, typ Type) *Var {
227-
return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}}
267+
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}}
228268
}
229269

230270
// NewParam returns a new variable representing a function parameter.
231271
func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var {
232-
return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}, used: true} // parameters are always 'used'
272+
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, used: true} // parameters are always 'used'
233273
}
234274

235275
// NewField returns a new variable representing a struct field.
236276
// For embedded fields, the name is the unqualified type name
237277
/// under which the field is accessible.
238278
func NewField(pos token.Pos, pkg *Package, name string, typ Type, embedded bool) *Var {
239-
return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}, embedded: embedded, isField: true}
279+
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, embedded: embedded, isField: true}
240280
}
241281

242282
// Anonymous reports whether the variable is an embedded field.
@@ -266,7 +306,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
266306
if sig != nil {
267307
typ = sig
268308
}
269-
return &Func{object{nil, pos, pkg, name, typ, 0, token.NoPos}}
309+
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}}
270310
}
271311

272312
// FullName returns the package- or receiver-type-qualified name of
@@ -291,7 +331,7 @@ type Label struct {
291331

292332
// NewLabel returns a new label.
293333
func NewLabel(pos token.Pos, pkg *Package, name string) *Label {
294-
return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, false}
334+
return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false}
295335
}
296336

297337
// A Builtin represents a built-in function.
@@ -302,7 +342,7 @@ type Builtin struct {
302342
}
303343

304344
func newBuiltin(id builtinId) *Builtin {
305-
return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id}
345+
return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id}
306346
}
307347

308348
// Nil represents the predeclared value nil.

src/go/types/universe.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func defPredeclaredConsts() {
102102
}
103103

104104
func defPredeclaredNil() {
105-
def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}})
105+
def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}})
106106
}
107107

108108
// A builtinId is the id of a builtin function.
@@ -207,6 +207,7 @@ func init() {
207207
// scope; other objects are inserted in the universe scope.
208208
//
209209
func def(obj Object) {
210+
assert(obj.color() == black)
210211
name := obj.Name()
211212
if strings.Contains(name, " ") {
212213
return // nothing to do

0 commit comments

Comments
 (0)