go/types, types2: swap object.color for Checker.objPathIdx

The type checker assigns types to objects. An object can be in
1 of 3 states, which are mapped to colors. This is stored as a
field on the object.

 - white : type not known, awaiting processing
 - grey  : type pending, in processing
 - black : type known, done processing

With the addition of Checker.objPathIdx, which maps objects to
a path index, presence in the map could signal grey coloring.
White and black coloring can be signaled by presence of
object.typ (a simple nil check).

This change removes the object.color field and its associated
methods, replacing it with an equivalent use of Checker.objPathIdx.
Checker.objPathIdx is integrated into the existing push and pop
methods, while slightly simplifying their signatures.

Note that the concept of object coloring remains the same - we
are merely changing and simplifying the mechanism which represents
the colors.

Change-Id: I91fb5e9a59dcb34c08ffc5b4ebc3f20a400094b6
Reviewed-on: https://go-review.googlesource.com/c/go/+/715840
Reviewed-by: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Mark Freeman <markfreeman@google.com>
This commit is contained in:
Mark Freeman 2025-10-28 17:54:52 -04:00 committed by Gopher Robot
parent 9daaab305c
commit ddd8558e61
14 changed files with 168 additions and 346 deletions

View file

@ -171,12 +171,13 @@ type Checker struct {
usedPkgNames map[*PkgName]bool // set of used package names usedPkgNames map[*PkgName]bool // set of used package names
mono monoGraph // graph for detecting non-monomorphizable instantiation loops mono monoGraph // graph for detecting non-monomorphizable instantiation loops
firstErr error // first error encountered firstErr error // first error encountered
methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
untyped map[syntax.Expr]exprInfo // map of expressions without final type untyped map[syntax.Expr]exprInfo // map of expressions without final type
delayed []action // stack of delayed action segments; segments are processed in FIFO order delayed []action // stack of delayed action segments; segments are processed in FIFO order
objPath []Object // path of object dependencies during type inference (for cycle reporting) objPath []Object // path of object dependencies during type-checking (for cycle reporting)
cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking objPathIdx map[Object]int // map of object to object path index during type-checking (for cycle reporting)
cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
// environment within which the current object is type-checked (valid only // environment within which the current object is type-checked (valid only
// for the duration of type-checking a specific object) // for the duration of type-checking a specific object)
@ -248,19 +249,22 @@ func (check *Checker) later(f func()) *action {
return &check.delayed[i] return &check.delayed[i]
} }
// push pushes obj onto the object path and returns its index in the path. // push pushes obj onto the object path and records its index in the path index map.
func (check *Checker) push(obj Object) int { func (check *Checker) push(obj Object) {
if check.objPathIdx == nil {
check.objPathIdx = make(map[Object]int)
}
check.objPathIdx[obj] = len(check.objPath)
check.objPath = append(check.objPath, obj) check.objPath = append(check.objPath, obj)
return len(check.objPath) - 1
} }
// pop pops and returns the topmost object from the object path. // pop pops an object from the object path and removes it from the path index map.
func (check *Checker) pop() Object { func (check *Checker) pop() {
i := len(check.objPath) - 1 i := len(check.objPath) - 1
obj := check.objPath[i] obj := check.objPath[i]
check.objPath[i] = nil check.objPath[i] = nil // help the garbage collector
check.objPath = check.objPath[:i] check.objPath = check.objPath[:i]
return obj delete(check.objPathIdx, obj)
} }
type cleaner interface { type cleaner interface {
@ -319,6 +323,7 @@ func (check *Checker) initFiles(files []*syntax.File) {
check.untyped = nil check.untyped = nil
check.delayed = nil check.delayed = nil
check.objPath = nil check.objPath = nil
check.objPathIdx = nil
check.cleaners = nil check.cleaners = nil
// We must initialize usedVars and usedPkgNames both here and in NewChecker, // We must initialize usedVars and usedPkgNames both here and in NewChecker,

View file

@ -54,7 +54,6 @@ func (check *Checker) directCycle(tname *TypeName, pathIdx map[*TypeName]int) {
// tname is marked grey - we have a cycle on the path beginning at start. // tname is marked grey - we have a cycle on the path beginning at start.
// Mark tname as invalid. // Mark tname as invalid.
tname.setType(Typ[Invalid]) tname.setType(Typ[Invalid])
tname.setColor(black)
// collect type names on cycle // collect type names on cycle
var cycle []Object var cycle []Object

View file

@ -62,114 +62,77 @@ func (check *Checker) objDecl(obj Object, def *TypeName) {
if check.indent == 0 { if check.indent == 0 {
fmt.Println() // empty line between top-level objects for readability fmt.Println() // empty line between top-level objects for readability
} }
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath)) check.trace(obj.Pos(), "-- checking %s (objPath = %s)", obj, pathString(check.objPath))
check.indent++ check.indent++
defer func() { defer func() {
check.indent-- check.indent--
check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color()) check.trace(obj.Pos(), "=> %s", obj)
}() }()
} }
// Checking the declaration of obj means inferring its type // Checking the declaration of an object means determining its type
// (and possibly its value, for constants). // (and also its value for constants). An object (and thus its type)
// An object's type (and thus the object) may be in one of // may be in 1 of 3 states:
// three states which are expressed by colors:
// //
// - an object whose type is not yet known is painted white (initial color) // - not in Checker.objPathIdx and type == nil : type is not yet known (white)
// - an object whose type is in the process of being inferred is painted grey // - in Checker.objPathIdx : type is pending (grey)
// - an object whose type is fully inferred is painted black // - not in Checker.objPathIdx and type != nil : type is known (black)
// //
// During type inference, an object's color changes from white to grey // During type-checking, an object changes from white to grey to black.
// to black (pre-declared objects are painted black from the start). // Predeclared objects start as black (their type is known without checking).
// A black object (i.e., its type) can only depend on (refer to) other black
// ones. White and grey objects may depend on white and black objects.
// A dependency on a grey object indicates a cycle which may or may not be
// valid.
// //
// When objects turn grey, they are pushed on the object path (a stack); // A black object may only depend on (refer to) to other black objects. White
// they are popped again when they turn black. Thus, if a grey object (a // and grey objects may depend on white or black objects. A dependency on a
// cycle) is encountered, it is on the object path, and all the objects // grey object indicates a (possibly invalid) cycle.
// it depends on are the remaining objects on that path. Color encoding //
// is such that the color value of a grey object indicates the index of // When an object is marked grey, it is pushed onto the object path (a stack)
// that object in the object path. // and its index in the path is recorded in the path index map. It is popped
// and removed from the map when its type is determined (and marked black).
// During type-checking, white objects may be assigned a type without // If this object is grey, we have a (possibly invalid) cycle. This is signaled
// traversing through objDecl; e.g., when initializing constants and // by a non-nil type for the object, except for constants and variables whose
// variables. Update the colors of those objects here (rather than // type may be non-nil (known), or nil if it depends on a not-yet known
// everywhere where we set the type) to satisfy the color invariants. // initialization value.
if obj.color() == white && obj.Type() != nil { //
obj.setColor(black) // In the former case, set the type to Typ[Invalid] because we have an
return // initialization cycle. The cycle error will be reported later, when
} // determining initialization order.
//
switch obj.color() { // TODO(gri) Report cycle here and simplify initialization order code.
case white: if _, ok := check.objPathIdx[obj]; ok {
assert(obj.Type() == nil)
// All color values other than white and black are considered grey.
// Because black and white are < grey, all values >= grey are grey.
// Use those values to encode the object's index into the object path.
obj.setColor(grey + color(check.push(obj)))
defer func() {
check.pop().setColor(black)
}()
case black:
assert(obj.Type() != nil)
return
default:
// Color values other than white or black are considered grey.
fallthrough
case grey:
// We have a (possibly invalid) cycle.
// In the existing code, this is marked by a non-nil type
// for the object except for constants and variables whose
// type may be non-nil (known), or nil if it depends on the
// not-yet known initialization value.
// In the former case, set the type to Typ[Invalid] because
// we have an initialization cycle. The cycle error will be
// reported later, when determining initialization order.
// TODO(gri) Report cycle here and simplify initialization
// order code.
switch obj := obj.(type) { switch obj := obj.(type) {
case *Const: case *Const, *Var:
if !check.validCycle(obj) || obj.typ == nil { if !check.validCycle(obj) || obj.Type() == nil {
obj.typ = Typ[Invalid] obj.setType(Typ[Invalid])
} }
case *Var:
if !check.validCycle(obj) || obj.typ == nil {
obj.typ = Typ[Invalid]
}
case *TypeName: case *TypeName:
if !check.validCycle(obj) { if !check.validCycle(obj) {
// break cycle obj.setType(Typ[Invalid])
// (without this, calling underlying()
// below may lead to an endless loop
// if we have a cycle for a defined
// (*Named) type)
obj.typ = Typ[Invalid]
} }
case *Func: case *Func:
if !check.validCycle(obj) { if !check.validCycle(obj) {
// Don't set obj.typ to Typ[Invalid] here // Don't set type to Typ[Invalid]; plenty of code asserts that
// because plenty of code type-asserts that // functions have a *Signature type. Instead, leave the type
// functions have a *Signature type. Grey // as an empty signature, which makes it impossible to
// functions have their type set to an empty
// signature which makes it impossible to
// initialize a variable with the function. // initialize a variable with the function.
} }
default: default:
panic("unreachable") panic("unreachable")
} }
assert(obj.Type() != nil) assert(obj.Type() != nil)
return return
} }
if obj.Type() != nil { // black, meaning it's already type-checked
return
}
// white, meaning it must be type-checked
check.push(obj)
defer check.pop()
d := check.objMap[obj] d := check.objMap[obj]
if d == nil { if d == nil {
check.dump("%v: %s should have been declared", obj.Pos(), obj) check.dump("%v: %s should have been declared", obj.Pos(), obj)
@ -221,8 +184,8 @@ func (check *Checker) validCycle(obj Object) (valid bool) {
} }
// Count cycle objects. // Count cycle objects.
assert(obj.color() >= grey) start, found := check.objPathIdx[obj]
start := obj.color() - grey // index of obj in objPath assert(found)
cycle := check.objPath[start:] cycle := check.objPath[start:]
tparCycle := false // if set, the cycle is through a type parameter list tparCycle := false // if set, the cycle is through a type parameter list
nval := 0 // number of (constant or variable) values in the cycle nval := 0 // number of (constant or variable) values in the cycle
@ -764,17 +727,8 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
sig := new(Signature) sig := new(Signature)
obj.typ = sig // guard against cycles obj.typ = sig // guard against cycles
// Avoid cycle error when referring to method while type-checking the signature.
// This avoids a nuisance in the best case (non-parameterized receiver type) and
// since the method is not a type, we get an error. If we have a parameterized
// receiver type, instantiating the receiver type leads to the instantiation of
// its methods, and we don't want a cycle error in that case.
// TODO(gri) review if this is correct and/or whether we still need this?
saved := obj.color_
obj.color_ = black
fdecl := decl.fdecl fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type) check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type)
obj.color_ = saved
// Set the scope's extent to the complete "func (...) { ... }" // Set the scope's extent to the complete "func (...) { ... }"
// so that Scope.Innermost works correctly. // so that Scope.Innermost works correctly.
@ -921,10 +875,9 @@ func (check *Checker) declStmt(list []syntax.Decl) {
// the innermost containing block." // the innermost containing block."
scopePos := s.Name.Pos() scopePos := s.Name.Pos()
check.declare(check.scope, s.Name, obj, scopePos) check.declare(check.scope, s.Name, obj, scopePos)
// mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl) check.push(obj) // mark as grey
obj.setColor(grey + color(check.push(obj))) defer check.pop()
check.typeDecl(obj, s, nil) check.typeDecl(obj, s, nil)
check.pop().setColor(black)
default: default:
check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s) check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s)

View file

@ -42,18 +42,12 @@ type Object interface {
// 0 for all other objects (including objects in file scopes). // 0 for all other objects (including objects in file scopes).
order() uint32 order() uint32
// color returns the object's color.
color() color
// setType sets the type of the object. // setType sets the type of the object.
setType(Type) setType(Type)
// setOrder sets the order number of the object. It must be > 0. // setOrder sets the order number of the object. It must be > 0.
setOrder(uint32) setOrder(uint32)
// setColor sets the object's color. It must not be white.
setColor(color color)
// setParent sets the parent scope of the object. // setParent sets the parent scope of the object.
setParent(*Scope) setParent(*Scope)
@ -102,41 +96,9 @@ type object struct {
name string name string
typ Type typ Type
order_ uint32 order_ uint32
color_ color
scopePos_ syntax.Pos scopePos_ syntax.Pos
} }
// color encodes the color of an object (see Checker.objDecl for details).
type color uint32
// An object may be painted in one of three colors.
// Color values other than white or black are considered grey.
const (
white color = iota
black
grey // must be > white and black
)
func (c color) String() string {
switch c {
case white:
return "white"
case black:
return "black"
default:
return "grey"
}
}
// colorFor returns the (initial) color for an object depending on
// whether its type t is known or not.
func colorFor(t Type) color {
if t != nil {
return black
}
return white
}
// Parent returns the scope in which the object is declared. // Parent returns the scope in which the object is declared.
// The result is nil for methods and struct fields. // The result is nil for methods and struct fields.
func (obj *object) Parent() *Scope { return obj.parent } func (obj *object) Parent() *Scope { return obj.parent }
@ -164,13 +126,11 @@ func (obj *object) Id() string { return Id(obj.pkg, obj.name) }
func (obj *object) String() string { panic("abstract") } func (obj *object) String() string { panic("abstract") }
func (obj *object) order() uint32 { return obj.order_ } func (obj *object) order() uint32 { return obj.order_ }
func (obj *object) color() color { return obj.color_ }
func (obj *object) scopePos() syntax.Pos { return obj.scopePos_ } func (obj *object) scopePos() syntax.Pos { return obj.scopePos_ }
func (obj *object) setParent(parent *Scope) { obj.parent = parent } func (obj *object) setParent(parent *Scope) { obj.parent = parent }
func (obj *object) setType(typ Type) { obj.typ = typ } func (obj *object) setType(typ Type) { obj.typ = typ }
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order } func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
func (obj *object) setScopePos(pos syntax.Pos) { obj.scopePos_ = pos } func (obj *object) setScopePos(pos syntax.Pos) { obj.scopePos_ = pos }
func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool { func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool {
@ -247,7 +207,7 @@ type PkgName struct {
// NewPkgName returns a new PkgName object representing an imported package. // NewPkgName returns a new PkgName object representing an imported package.
// The remaining arguments set the attributes found with all Objects. // The remaining arguments set the attributes found with all Objects.
func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName { func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName {
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported} return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, nopos}, imported}
} }
// Imported returns the package that was imported. // Imported returns the package that was imported.
@ -263,7 +223,7 @@ type Const struct {
// NewConst returns a new constant with value val. // NewConst returns a new constant with value val.
// The remaining arguments set the attributes found with all Objects. // The remaining arguments set the attributes found with all Objects.
func NewConst(pos syntax.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const { func NewConst(pos syntax.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const {
return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, val} return &Const{object{nil, pos, pkg, name, typ, 0, nopos}, val}
} }
// Val returns the constant's value. // Val returns the constant's value.
@ -288,7 +248,7 @@ type TypeName struct {
// argument for NewNamed, which will set the TypeName's type as a side- // argument for NewNamed, which will set the TypeName's type as a side-
// effect. // effect.
func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName { func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName {
return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}} return &TypeName{object{nil, pos, pkg, name, typ, 0, nopos}}
} }
// NewTypeNameLazy returns a new defined type like NewTypeName, but it // NewTypeNameLazy returns a new defined type like NewTypeName, but it
@ -402,7 +362,7 @@ func NewField(pos syntax.Pos, pkg *Package, name string, typ Type, embedded bool
// newVar returns a new variable. // newVar returns a new variable.
// The arguments set the attributes found with all Objects. // The arguments set the attributes found with all Objects.
func newVar(kind VarKind, pos syntax.Pos, pkg *Package, name string, typ Type) *Var { func newVar(kind VarKind, pos syntax.Pos, pkg *Package, name string, typ Type) *Var {
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, kind: kind} return &Var{object: object{nil, pos, pkg, name, typ, 0, nopos}, kind: kind}
} }
// Anonymous reports whether the variable is an embedded field. // Anonymous reports whether the variable is an embedded field.
@ -452,7 +412,7 @@ func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func {
// as this would violate object.{Type,color} invariants. // as this would violate object.{Type,color} invariants.
// TODO(adonovan): propose to disallow NewFunc with nil *Signature. // TODO(adonovan): propose to disallow NewFunc with nil *Signature.
} }
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false, nil} return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil}
} }
// Signature returns the signature (type) of the function or method. // Signature returns the signature (type) of the function or method.
@ -534,7 +494,7 @@ type Label struct {
// NewLabel returns a new label. // NewLabel returns a new label.
func NewLabel(pos syntax.Pos, pkg *Package, name string) *Label { func NewLabel(pos syntax.Pos, pkg *Package, name string) *Label {
return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false} return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, false}
} }
// A Builtin represents a built-in function. // A Builtin represents a built-in function.
@ -545,7 +505,7 @@ type Builtin struct {
} }
func newBuiltin(id builtinId) *Builtin { func newBuiltin(id builtinId) *Builtin {
return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id} return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id}
} }
// Nil represents the predeclared value nil. // Nil represents the predeclared value nil.

View file

@ -217,10 +217,8 @@ func (*lazyObject) Exported() bool { panic("unreachable") }
func (*lazyObject) Id() string { panic("unreachable") } func (*lazyObject) Id() string { panic("unreachable") }
func (*lazyObject) String() string { panic("unreachable") } func (*lazyObject) String() string { panic("unreachable") }
func (*lazyObject) order() uint32 { panic("unreachable") } func (*lazyObject) order() uint32 { panic("unreachable") }
func (*lazyObject) color() color { panic("unreachable") }
func (*lazyObject) setType(Type) { panic("unreachable") } func (*lazyObject) setType(Type) { panic("unreachable") }
func (*lazyObject) setOrder(uint32) { panic("unreachable") } func (*lazyObject) setOrder(uint32) { panic("unreachable") }
func (*lazyObject) setColor(color color) { panic("unreachable") }
func (*lazyObject) setParent(*Scope) { panic("unreachable") } func (*lazyObject) setParent(*Scope) { panic("unreachable") }
func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") } func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") }
func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") } func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") }

View file

@ -36,14 +36,14 @@ func TestSizeof(t *testing.T) {
{term{}, 12, 24}, {term{}, 12, 24},
// Objects // Objects
{PkgName{}, 60, 96}, {PkgName{}, 56, 96},
{Const{}, 64, 104}, {Const{}, 60, 104},
{TypeName{}, 56, 88}, {TypeName{}, 52, 88},
{Var{}, 64, 104}, {Var{}, 60, 104},
{Func{}, 64, 104}, {Func{}, 60, 104},
{Label{}, 60, 96}, {Label{}, 56, 96},
{Builtin{}, 60, 96}, {Builtin{}, 56, 96},
{Nil{}, 56, 88}, {Nil{}, 52, 88},
// Misc // Misc
{Scope{}, 60, 104}, {Scope{}, 60, 104},

View file

@ -98,7 +98,6 @@ func defPredeclaredTypes() {
// interface. // interface.
{ {
universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet}) universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
universeAnyNoAlias.setColor(black)
// ensure that the any TypeName reports a consistent Parent, after // ensure that the any TypeName reports a consistent Parent, after
// hijacking Universe.Lookup with gotypesalias=0. // hijacking Universe.Lookup with gotypesalias=0.
universeAnyNoAlias.setParent(Universe) universeAnyNoAlias.setParent(Universe)
@ -107,7 +106,6 @@ func defPredeclaredTypes() {
// into the Universe, but we lean toward the future and insert the Alias // into the Universe, but we lean toward the future and insert the Alias
// representation. // representation.
universeAnyAlias = NewTypeName(nopos, nil, "any", nil) universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
universeAnyAlias.setColor(black)
_ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias _ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
def(universeAnyAlias) def(universeAnyAlias)
} }
@ -115,7 +113,6 @@ func defPredeclaredTypes() {
// type error interface{ Error() string } // type error interface{ Error() string }
{ {
obj := NewTypeName(nopos, nil, "error", nil) obj := NewTypeName(nopos, nil, "error", nil)
obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil) typ := (*Checker)(nil).newNamed(obj, nil, nil)
// error.Error() string // error.Error() string
@ -136,7 +133,6 @@ func defPredeclaredTypes() {
// type comparable interface{} // marked as comparable // type comparable interface{} // marked as comparable
{ {
obj := NewTypeName(nopos, nil, "comparable", nil) obj := NewTypeName(nopos, nil, "comparable", nil)
obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil) typ := (*Checker)(nil).newNamed(obj, nil, nil)
// interface{} // marked as comparable // interface{} // marked as comparable
@ -165,7 +161,7 @@ func defPredeclaredConsts() {
} }
func defPredeclaredNil() { func defPredeclaredNil() {
def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}}) def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}})
} }
// A builtinId is the id of a builtin function. // A builtinId is the id of a builtin function.
@ -289,7 +285,7 @@ func init() {
// a scope. Objects with exported names are inserted in the unsafe package // a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope. // scope; other objects are inserted in the universe scope.
func def(obj Object) { func def(obj Object) {
assert(obj.color() == black) assert(obj.Type() != nil)
name := obj.Name() name := obj.Name()
if strings.Contains(name, " ") { if strings.Contains(name, " ") {
return // nothing to do return // nothing to do

View file

@ -191,12 +191,13 @@ type Checker struct {
usedPkgNames map[*PkgName]bool // set of used package names usedPkgNames map[*PkgName]bool // set of used package names
mono monoGraph // graph for detecting non-monomorphizable instantiation loops mono monoGraph // graph for detecting non-monomorphizable instantiation loops
firstErr error // first error encountered firstErr error // first error encountered
methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
untyped map[ast.Expr]exprInfo // map of expressions without final type untyped map[ast.Expr]exprInfo // map of expressions without final type
delayed []action // stack of delayed action segments; segments are processed in FIFO order delayed []action // stack of delayed action segments; segments are processed in FIFO order
objPath []Object // path of object dependencies during type inference (for cycle reporting) objPath []Object // path of object dependencies during type-checking (for cycle reporting)
cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking objPathIdx map[Object]int // map of object to object path index during type-checking (for cycle reporting)
cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
// environment within which the current object is type-checked (valid only // environment within which the current object is type-checked (valid only
// for the duration of type-checking a specific object) // for the duration of type-checking a specific object)
@ -268,19 +269,22 @@ func (check *Checker) later(f func()) *action {
return &check.delayed[i] return &check.delayed[i]
} }
// push pushes obj onto the object path and returns its index in the path. // push pushes obj onto the object path and records its index in the path index map.
func (check *Checker) push(obj Object) int { func (check *Checker) push(obj Object) {
if check.objPathIdx == nil {
check.objPathIdx = make(map[Object]int)
}
check.objPathIdx[obj] = len(check.objPath)
check.objPath = append(check.objPath, obj) check.objPath = append(check.objPath, obj)
return len(check.objPath) - 1
} }
// pop pops and returns the topmost object from the object path. // pop pops an object from the object path and removes it from the path index map.
func (check *Checker) pop() Object { func (check *Checker) pop() {
i := len(check.objPath) - 1 i := len(check.objPath) - 1
obj := check.objPath[i] obj := check.objPath[i]
check.objPath[i] = nil check.objPath[i] = nil // help the garbage collector
check.objPath = check.objPath[:i] check.objPath = check.objPath[:i]
return obj delete(check.objPathIdx, obj)
} }
type cleaner interface { type cleaner interface {
@ -343,6 +347,7 @@ func (check *Checker) initFiles(files []*ast.File) {
check.untyped = nil check.untyped = nil
check.delayed = nil check.delayed = nil
check.objPath = nil check.objPath = nil
check.objPathIdx = nil
check.cleaners = nil check.cleaners = nil
// We must initialize usedVars and usedPkgNames both here and in NewChecker, // We must initialize usedVars and usedPkgNames both here and in NewChecker,

View file

@ -57,7 +57,6 @@ func (check *Checker) directCycle(tname *TypeName, pathIdx map[*TypeName]int) {
// tname is marked grey - we have a cycle on the path beginning at start. // tname is marked grey - we have a cycle on the path beginning at start.
// Mark tname as invalid. // Mark tname as invalid.
tname.setType(Typ[Invalid]) tname.setType(Typ[Invalid])
tname.setColor(black)
// collect type names on cycle // collect type names on cycle
var cycle []Object var cycle []Object

View file

@ -63,114 +63,77 @@ func (check *Checker) objDecl(obj Object, def *TypeName) {
if check.indent == 0 { if check.indent == 0 {
fmt.Println() // empty line between top-level objects for readability fmt.Println() // empty line between top-level objects for readability
} }
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath)) check.trace(obj.Pos(), "-- checking %s (objPath = %s)", obj, pathString(check.objPath))
check.indent++ check.indent++
defer func() { defer func() {
check.indent-- check.indent--
check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color()) check.trace(obj.Pos(), "=> %s", obj)
}() }()
} }
// Checking the declaration of obj means inferring its type // Checking the declaration of an object means determining its type
// (and possibly its value, for constants). // (and also its value for constants). An object (and thus its type)
// An object's type (and thus the object) may be in one of // may be in 1 of 3 states:
// three states which are expressed by colors:
// //
// - an object whose type is not yet known is painted white (initial color) // - not in Checker.objPathIdx and type == nil : type is not yet known (white)
// - an object whose type is in the process of being inferred is painted grey // - in Checker.objPathIdx : type is pending (grey)
// - an object whose type is fully inferred is painted black // - not in Checker.objPathIdx and type != nil : type is known (black)
// //
// During type inference, an object's color changes from white to grey // During type-checking, an object changes from white to grey to black.
// to black (pre-declared objects are painted black from the start). // Predeclared objects start as black (their type is known without checking).
// A black object (i.e., its type) can only depend on (refer to) other black
// ones. White and grey objects may depend on white and black objects.
// A dependency on a grey object indicates a cycle which may or may not be
// valid.
// //
// When objects turn grey, they are pushed on the object path (a stack); // A black object may only depend on (refer to) to other black objects. White
// they are popped again when they turn black. Thus, if a grey object (a // and grey objects may depend on white or black objects. A dependency on a
// cycle) is encountered, it is on the object path, and all the objects // grey object indicates a (possibly invalid) cycle.
// it depends on are the remaining objects on that path. Color encoding //
// is such that the color value of a grey object indicates the index of // When an object is marked grey, it is pushed onto the object path (a stack)
// that object in the object path. // and its index in the path is recorded in the path index map. It is popped
// and removed from the map when its type is determined (and marked black).
// During type-checking, white objects may be assigned a type without // If this object is grey, we have a (possibly invalid) cycle. This is signaled
// traversing through objDecl; e.g., when initializing constants and // by a non-nil type for the object, except for constants and variables whose
// variables. Update the colors of those objects here (rather than // type may be non-nil (known), or nil if it depends on a not-yet known
// everywhere where we set the type) to satisfy the color invariants. // initialization value.
if obj.color() == white && obj.Type() != nil { //
obj.setColor(black) // In the former case, set the type to Typ[Invalid] because we have an
return // initialization cycle. The cycle error will be reported later, when
} // determining initialization order.
//
switch obj.color() { // TODO(gri) Report cycle here and simplify initialization order code.
case white: if _, ok := check.objPathIdx[obj]; ok {
assert(obj.Type() == nil)
// All color values other than white and black are considered grey.
// Because black and white are < grey, all values >= grey are grey.
// Use those values to encode the object's index into the object path.
obj.setColor(grey + color(check.push(obj)))
defer func() {
check.pop().setColor(black)
}()
case black:
assert(obj.Type() != nil)
return
default:
// Color values other than white or black are considered grey.
fallthrough
case grey:
// We have a (possibly invalid) cycle.
// In the existing code, this is marked by a non-nil type
// for the object except for constants and variables whose
// type may be non-nil (known), or nil if it depends on the
// not-yet known initialization value.
// In the former case, set the type to Typ[Invalid] because
// we have an initialization cycle. The cycle error will be
// reported later, when determining initialization order.
// TODO(gri) Report cycle here and simplify initialization
// order code.
switch obj := obj.(type) { switch obj := obj.(type) {
case *Const: case *Const, *Var:
if !check.validCycle(obj) || obj.typ == nil { if !check.validCycle(obj) || obj.Type() == nil {
obj.typ = Typ[Invalid] obj.setType(Typ[Invalid])
} }
case *Var:
if !check.validCycle(obj) || obj.typ == nil {
obj.typ = Typ[Invalid]
}
case *TypeName: case *TypeName:
if !check.validCycle(obj) { if !check.validCycle(obj) {
// break cycle obj.setType(Typ[Invalid])
// (without this, calling underlying()
// below may lead to an endless loop
// if we have a cycle for a defined
// (*Named) type)
obj.typ = Typ[Invalid]
} }
case *Func: case *Func:
if !check.validCycle(obj) { if !check.validCycle(obj) {
// Don't set obj.typ to Typ[Invalid] here // Don't set type to Typ[Invalid]; plenty of code asserts that
// because plenty of code type-asserts that // functions have a *Signature type. Instead, leave the type
// functions have a *Signature type. Grey // as an empty signature, which makes it impossible to
// functions have their type set to an empty
// signature which makes it impossible to
// initialize a variable with the function. // initialize a variable with the function.
} }
default: default:
panic("unreachable") panic("unreachable")
} }
assert(obj.Type() != nil) assert(obj.Type() != nil)
return return
} }
if obj.Type() != nil { // black, meaning it's already type-checked
return
}
// white, meaning it must be type-checked
check.push(obj) // mark as grey
defer check.pop()
d := check.objMap[obj] d := check.objMap[obj]
if d == nil { if d == nil {
check.dump("%v: %s should have been declared", obj.Pos(), obj) check.dump("%v: %s should have been declared", obj.Pos(), obj)
@ -222,8 +185,8 @@ func (check *Checker) validCycle(obj Object) (valid bool) {
} }
// Count cycle objects. // Count cycle objects.
assert(obj.color() >= grey) start, found := check.objPathIdx[obj]
start := obj.color() - grey // index of obj in objPath assert(found)
cycle := check.objPath[start:] cycle := check.objPath[start:]
tparCycle := false // if set, the cycle is through a type parameter list tparCycle := false // if set, the cycle is through a type parameter list
nval := 0 // number of (constant or variable) values in the cycle nval := 0 // number of (constant or variable) values in the cycle
@ -857,17 +820,8 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
sig := new(Signature) sig := new(Signature)
obj.typ = sig // guard against cycles obj.typ = sig // guard against cycles
// Avoid cycle error when referring to method while type-checking the signature.
// This avoids a nuisance in the best case (non-parameterized receiver type) and
// since the method is not a type, we get an error. If we have a parameterized
// receiver type, instantiating the receiver type leads to the instantiation of
// its methods, and we don't want a cycle error in that case.
// TODO(gri) review if this is correct and/or whether we still need this?
saved := obj.color_
obj.color_ = black
fdecl := decl.fdecl fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.Type) check.funcType(sig, fdecl.Recv, fdecl.Type)
obj.color_ = saved
// Set the scope's extent to the complete "func (...) { ... }" // Set the scope's extent to the complete "func (...) { ... }"
// so that Scope.Innermost works correctly. // so that Scope.Innermost works correctly.
@ -980,10 +934,9 @@ func (check *Checker) declStmt(d ast.Decl) {
// the innermost containing block." // the innermost containing block."
scopePos := d.spec.Name.Pos() scopePos := d.spec.Name.Pos()
check.declare(check.scope, d.spec.Name, obj, scopePos) check.declare(check.scope, d.spec.Name, obj, scopePos)
// mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl) check.push(obj) // mark as grey
obj.setColor(grey + color(check.push(obj))) defer check.pop()
check.typeDecl(obj, d.spec, nil) check.typeDecl(obj, d.spec, nil)
check.pop().setColor(black)
default: default:
check.errorf(d.node(), InvalidSyntaxTree, "unknown ast.Decl node %T", d.node()) check.errorf(d.node(), InvalidSyntaxTree, "unknown ast.Decl node %T", d.node())
} }

View file

@ -45,18 +45,12 @@ type Object interface {
// 0 for all other objects (including objects in file scopes). // 0 for all other objects (including objects in file scopes).
order() uint32 order() uint32
// color returns the object's color.
color() color
// setType sets the type of the object. // setType sets the type of the object.
setType(Type) setType(Type)
// setOrder sets the order number of the object. It must be > 0. // setOrder sets the order number of the object. It must be > 0.
setOrder(uint32) setOrder(uint32)
// setColor sets the object's color. It must not be white.
setColor(color color)
// setParent sets the parent scope of the object. // setParent sets the parent scope of the object.
setParent(*Scope) setParent(*Scope)
@ -105,41 +99,9 @@ type object struct {
name string name string
typ Type typ Type
order_ uint32 order_ uint32
color_ color
scopePos_ token.Pos scopePos_ token.Pos
} }
// color encodes the color of an object (see Checker.objDecl for details).
type color uint32
// An object may be painted in one of three colors.
// Color values other than white or black are considered grey.
const (
white color = iota
black
grey // must be > white and black
)
func (c color) String() string {
switch c {
case white:
return "white"
case black:
return "black"
default:
return "grey"
}
}
// colorFor returns the (initial) color for an object depending on
// whether its type t is known or not.
func colorFor(t Type) color {
if t != nil {
return black
}
return white
}
// Parent returns the scope in which the object is declared. // Parent returns the scope in which the object is declared.
// The result is nil for methods and struct fields. // The result is nil for methods and struct fields.
func (obj *object) Parent() *Scope { return obj.parent } func (obj *object) Parent() *Scope { return obj.parent }
@ -167,13 +129,11 @@ func (obj *object) Id() string { return Id(obj.pkg, obj.name) }
func (obj *object) String() string { panic("abstract") } func (obj *object) String() string { panic("abstract") }
func (obj *object) order() uint32 { return obj.order_ } func (obj *object) order() uint32 { return obj.order_ }
func (obj *object) color() color { return obj.color_ }
func (obj *object) scopePos() token.Pos { return obj.scopePos_ } func (obj *object) scopePos() token.Pos { return obj.scopePos_ }
func (obj *object) setParent(parent *Scope) { obj.parent = parent } func (obj *object) setParent(parent *Scope) { obj.parent = parent }
func (obj *object) setType(typ Type) { obj.typ = typ } func (obj *object) setType(typ Type) { obj.typ = typ }
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order } func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos } func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos }
func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool { func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool {
@ -250,7 +210,7 @@ type PkgName struct {
// NewPkgName returns a new PkgName object representing an imported package. // NewPkgName returns a new PkgName object representing an imported package.
// The remaining arguments set the attributes found with all Objects. // The remaining arguments set the attributes found with all Objects.
func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName { func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName {
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported} return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, nopos}, imported}
} }
// Imported returns the package that was imported. // Imported returns the package that was imported.
@ -266,7 +226,7 @@ type Const struct {
// NewConst returns a new constant with value val. // NewConst returns a new constant with value val.
// The remaining arguments set the attributes found with all Objects. // The remaining arguments set the attributes found with all Objects.
func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const { func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const {
return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, val} return &Const{object{nil, pos, pkg, name, typ, 0, nopos}, val}
} }
// Val returns the constant's value. // Val returns the constant's value.
@ -291,7 +251,7 @@ type TypeName struct {
// argument for NewNamed, which will set the TypeName's type as a side- // argument for NewNamed, which will set the TypeName's type as a side-
// effect. // effect.
func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}} return &TypeName{object{nil, pos, pkg, name, typ, 0, nopos}}
} }
// NewTypeNameLazy returns a new defined type like NewTypeName, but it // NewTypeNameLazy returns a new defined type like NewTypeName, but it
@ -405,7 +365,7 @@ func NewField(pos token.Pos, pkg *Package, name string, typ Type, embedded bool)
// newVar returns a new variable. // newVar returns a new variable.
// The arguments set the attributes found with all Objects. // The arguments set the attributes found with all Objects.
func newVar(kind VarKind, pos token.Pos, pkg *Package, name string, typ Type) *Var { func newVar(kind VarKind, pos token.Pos, pkg *Package, name string, typ Type) *Var {
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, kind: kind} return &Var{object: object{nil, pos, pkg, name, typ, 0, nopos}, kind: kind}
} }
// Anonymous reports whether the variable is an embedded field. // Anonymous reports whether the variable is an embedded field.
@ -455,7 +415,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
// as this would violate object.{Type,color} invariants. // as this would violate object.{Type,color} invariants.
// TODO(adonovan): propose to disallow NewFunc with nil *Signature. // TODO(adonovan): propose to disallow NewFunc with nil *Signature.
} }
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false, nil} return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil}
} }
// Signature returns the signature (type) of the function or method. // Signature returns the signature (type) of the function or method.
@ -537,7 +497,7 @@ type Label struct {
// NewLabel returns a new label. // NewLabel returns a new label.
func NewLabel(pos token.Pos, pkg *Package, name string) *Label { func NewLabel(pos token.Pos, pkg *Package, name string) *Label {
return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false} return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, false}
} }
// A Builtin represents a built-in function. // A Builtin represents a built-in function.
@ -548,7 +508,7 @@ type Builtin struct {
} }
func newBuiltin(id builtinId) *Builtin { func newBuiltin(id builtinId) *Builtin {
return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id} return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id}
} }
// Nil represents the predeclared value nil. // Nil represents the predeclared value nil.

View file

@ -220,10 +220,8 @@ func (*lazyObject) Exported() bool { panic("unreachable") }
func (*lazyObject) Id() string { panic("unreachable") } func (*lazyObject) Id() string { panic("unreachable") }
func (*lazyObject) String() string { panic("unreachable") } func (*lazyObject) String() string { panic("unreachable") }
func (*lazyObject) order() uint32 { panic("unreachable") } func (*lazyObject) order() uint32 { panic("unreachable") }
func (*lazyObject) color() color { panic("unreachable") }
func (*lazyObject) setType(Type) { panic("unreachable") } func (*lazyObject) setType(Type) { panic("unreachable") }
func (*lazyObject) setOrder(uint32) { panic("unreachable") } func (*lazyObject) setOrder(uint32) { panic("unreachable") }
func (*lazyObject) setColor(color color) { panic("unreachable") }
func (*lazyObject) setParent(*Scope) { panic("unreachable") } func (*lazyObject) setParent(*Scope) { panic("unreachable") }
func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") } func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") }
func (*lazyObject) scopePos() token.Pos { panic("unreachable") } func (*lazyObject) scopePos() token.Pos { panic("unreachable") }

View file

@ -35,14 +35,14 @@ func TestSizeof(t *testing.T) {
{term{}, 12, 24}, {term{}, 12, 24},
// Objects // Objects
{PkgName{}, 44, 80}, {PkgName{}, 40, 80},
{Const{}, 48, 88}, {Const{}, 44, 88},
{TypeName{}, 40, 72}, {TypeName{}, 36, 72},
{Var{}, 48, 88}, {Var{}, 44, 88},
{Func{}, 48, 88}, {Func{}, 44, 88},
{Label{}, 44, 80}, {Label{}, 40, 80},
{Builtin{}, 44, 80}, {Builtin{}, 40, 80},
{Nil{}, 40, 72}, {Nil{}, 36, 72},
// Misc // Misc
{Scope{}, 44, 88}, {Scope{}, 44, 88},

View file

@ -101,7 +101,6 @@ func defPredeclaredTypes() {
// interface. // interface.
{ {
universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet}) universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
universeAnyNoAlias.setColor(black)
// ensure that the any TypeName reports a consistent Parent, after // ensure that the any TypeName reports a consistent Parent, after
// hijacking Universe.Lookup with gotypesalias=0. // hijacking Universe.Lookup with gotypesalias=0.
universeAnyNoAlias.setParent(Universe) universeAnyNoAlias.setParent(Universe)
@ -110,7 +109,6 @@ func defPredeclaredTypes() {
// into the Universe, but we lean toward the future and insert the Alias // into the Universe, but we lean toward the future and insert the Alias
// representation. // representation.
universeAnyAlias = NewTypeName(nopos, nil, "any", nil) universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
universeAnyAlias.setColor(black)
_ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias _ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
def(universeAnyAlias) def(universeAnyAlias)
} }
@ -118,7 +116,6 @@ func defPredeclaredTypes() {
// type error interface{ Error() string } // type error interface{ Error() string }
{ {
obj := NewTypeName(nopos, nil, "error", nil) obj := NewTypeName(nopos, nil, "error", nil)
obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil) typ := (*Checker)(nil).newNamed(obj, nil, nil)
// error.Error() string // error.Error() string
@ -139,7 +136,6 @@ func defPredeclaredTypes() {
// type comparable interface{} // marked as comparable // type comparable interface{} // marked as comparable
{ {
obj := NewTypeName(nopos, nil, "comparable", nil) obj := NewTypeName(nopos, nil, "comparable", nil)
obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil) typ := (*Checker)(nil).newNamed(obj, nil, nil)
// interface{} // marked as comparable // interface{} // marked as comparable
@ -168,7 +164,7 @@ func defPredeclaredConsts() {
} }
func defPredeclaredNil() { func defPredeclaredNil() {
def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}}) def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}})
} }
// A builtinId is the id of a builtin function. // A builtinId is the id of a builtin function.
@ -292,7 +288,7 @@ func init() {
// a scope. Objects with exported names are inserted in the unsafe package // a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope. // scope; other objects are inserted in the universe scope.
func def(obj Object) { func def(obj Object) {
assert(obj.color() == black) assert(obj.Type() != nil)
name := obj.Name() name := obj.Name()
if strings.Contains(name, " ") { if strings.Contains(name, " ") {
return // nothing to do return // nothing to do