// For the meaning of def, see Checker.definedType, in typexpr.go.
func (check *Checker) objDecl(obj Object, def *Named) {
if trace {
- check.trace(obj.Pos(), "-- checking %s %s (objPath = %s)", obj.color(), obj, pathString(check.objPath))
+ check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
check.indent++
defer func() {
check.indent--
- check.trace(obj.Pos(), "=> %s", obj)
+ check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color())
}()
}
}
}
-// indir is a sentinel type name that is pushed onto the object path
-// to indicate an "indirection" in the dependency from one type name
-// to the next. For instance, for "type p *p" the object path contains
-// p followed by indir, indicating that there's an indirection *p.
-// Indirections are used to break type cycles.
-var indir = NewTypeName(token.NoPos, nil, "*", nil)
-
// typeCycle checks if the cycle starting with obj is valid and
// reports an error if it is not.
// TODO(gri) rename s/typeCycle/cycle/ once we don't need the other
}
}
- // Given the number of constants and variables (nval) in the cycle
- // and the cycle length (ncycle = number of named objects in the cycle),
- // we distinguish between cycles involving only constants and variables
- // (nval = ncycle), cycles involving types (and functions) only
- // (nval == 0), and mixed cycles (nval != 0 && nval != ncycle).
- // We ignore functions at the moment (taking them into account correctly
- // is complicated and it doesn't improve error reporting significantly).
- //
- // A cycle must have at least one indirection and one type definition
- // to be permitted: If there is no indirection, the size of the type
- // cannot be computed (it's either infinite or 0); if there is no type
- // definition, we have a sequence of alias type names which will expand
- // ad infinitum.
- var nval, ncycle int
- var hasIndir, hasTDef bool
+ // Count cycle objects.
assert(obj.color() >= grey)
start := obj.color() - grey // index of obj in objPath
cycle := check.objPath[start:]
- ncycle = len(cycle) // including indirections
+ nval := 0 // number of (constant or variable) values in the cycle
+ ndef := 0 // number of type definitions in the cycle
for _, obj := range cycle {
switch obj := obj.(type) {
case *Const, *Var:
nval++
case *TypeName:
- if obj == indir {
- ncycle-- // don't count (indirections are not objects)
- hasIndir = true
+ // Determine if the type name is an alias or not. For
+ // package-level objects, use the object map which
+ // provides syntactic information (which doesn't rely
+ // on the order in which the objects are set up). For
+ // local objects, we can rely on the order, so use
+ // the object's predicate.
+ // TODO(gri) It would be less fragile to always access
+ // the syntactic information. We should consider storing
+ // this information explicitly in the object.
+ var alias bool
+ if d := check.objMap[obj]; d != nil {
+ alias = d.alias // package-level object
} else {
- // Determine if the type name is an alias or not. For
- // package-level objects, use the object map which
- // provides syntactic information (which doesn't rely
- // on the order in which the objects are set up). For
- // local objects, we can rely on the order, so use
- // the object's predicate.
- // TODO(gri) It would be less fragile to always access
- // the syntactic information. We should consider storing
- // this information explicitly in the object.
- var alias bool
- if d := check.objMap[obj]; d != nil {
- alias = d.alias // package-level object
- } else {
- alias = obj.IsAlias() // function local object
- }
- if !alias {
- hasTDef = true
- }
+ alias = obj.IsAlias() // function local object
+ }
+ if !alias {
+ ndef++
}
case *Func:
// ignored for now
}
if trace {
- check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), ncycle)
- check.trace(obj.Pos(), "## cycle contains: %d values, has indirection = %v, has type definition = %v", nval, hasIndir, hasTDef)
+ check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), len(cycle))
+ check.trace(obj.Pos(), "## cycle contains: %d values, %d type definitions", nval, ndef)
defer func() {
if isCycle {
check.trace(obj.Pos(), "=> error: cycle is invalid")
// A cycle involving only constants and variables is invalid but we
// ignore them here because they are reported via the initialization
// cycle check.
- if nval == ncycle {
+ if nval == len(cycle) {
return false
}
- // A cycle involving only types (and possibly functions) must have at
- // least one indirection and one type definition to be permitted: If
- // there is no indirection, the size of the type cannot be computed
- // (it's either infinite or 0); if there is no type definition, we
+ // A cycle involving only types (and possibly functions) must have at least
+ // one type definition to be permitted: If there is no type definition, we
// have a sequence of alias type names which will expand ad infinitum.
- if nval == 0 && hasIndir && hasTDef {
+ if nval == 0 && ndef > 0 {
return false // cycle is permitted
}
- // report cycle
- check.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name())
- for _, obj := range cycle {
- if obj == indir {
- continue // don't print indir sentinels
+ check.cycleError(cycle)
+
+ return true
+}
+
+type typeInfo uint
+
+// validType verifies that the given type does not "expand" infinitely
+// producing a cycle in the type graph. Cycles are detected by marking
+// defined types.
+// (Cycles involving alias types, as in "type A = [10]A" are detected
+// earlier, via the objDecl cycle detection mechanism.)
+func (check *Checker) validType(typ Type, path []Object) typeInfo {
+ const (
+ unknown typeInfo = iota
+ marked
+ valid
+ invalid
+ )
+
+ switch t := typ.(type) {
+ case *Array:
+ return check.validType(t.elem, path)
+
+ case *Struct:
+ for _, f := range t.fields {
+ if check.validType(f.typ, path) == invalid {
+ return invalid
+ }
+ }
+
+ case *Interface:
+ for _, etyp := range t.embeddeds {
+ if check.validType(etyp, path) == invalid {
+ return invalid
+ }
}
+
+ case *Named:
+ switch t.info {
+ case unknown:
+ t.info = marked
+ t.info = check.validType(t.underlying, append(path, t.obj))
+ case marked:
+ // cycle detected
+ for i, tn := range path {
+ if tn == t.obj {
+ check.cycleError(path[i:])
+ t.info = invalid
+ t.underlying = Typ[Invalid]
+ return t.info
+ }
+ }
+ panic("internal error: cycle start not found")
+ }
+ return t.info
+ }
+
+ return valid
+}
+
+// cycleError reports a declaration cycle starting with
+// the object in cycle that is "first" in the source.
+func (check *Checker) cycleError(cycle []Object) {
+ // TODO(gri) Should we start with the last (rather than the first) object in the cycle
+ // since that is the earliest point in the source where we start seeing the
+ // cycle? That would be more consistent with other error messages.
+ i := firstInSrc(cycle)
+ obj := cycle[i]
+ check.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name())
+ 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", obj.Name())
+}
- return true
+// firstInSrc reports the index of the object with the "smallest"
+// source position in path. path must not be empty.
+func firstInSrc(path []Object) int {
+ fst, pos := 0, path[0].Pos()
+ for i, t := range path[1:] {
+ if t.Pos() < pos {
+ fst, pos = i+1, t.Pos()
+ }
+ }
+ return fst
}
func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
// underlying returns the underlying type of typ; possibly by following
// forward chains of named types. Such chains only exist while named types
-// are incomplete.
-func underlying(typ Type) Type {
+// are incomplete. If an underlying type is found, resolve the chain by
+// setting the underlying type for each defined type in the chain before
+// returning it.
+//
+// If no underlying type is found, a cycle error is reported and Typ[Invalid]
+// is used as underlying type for each defined type in the chain and returned
+// as result.
+func (check *Checker) underlying(typ Type) Type {
+ // If typ is not a defined type, its underlying type is itself.
+ n0, _ := typ.(*Named)
+ if n0 == nil {
+ return typ // nothing to do
+ }
+
+ // If the underlying type of a defined type is not a defined
+ // type, then that is the desired underlying type.
+ typ = n0.underlying
+ n, _ := typ.(*Named)
+ if n == nil {
+ return typ // common case
+ }
+
+ // Otherwise, follow the forward chain.
+ seen := map[*Named]int{n0: 0, n: 1}
+ path := []Object{n0.obj, n.obj}
for {
- n, _ := typ.(*Named)
+ typ = n.underlying
+ n, _ = typ.(*Named)
if n == nil {
+ break // end of chain
+ }
+
+ if i, ok := seen[n]; ok {
+ // cycle
+ check.cycleError(path[i:])
+ typ = Typ[Invalid]
break
}
- typ = n.underlying
+
+ seen[n] = len(seen)
+ path = append(path, n.obj)
}
+
+ for n := range seen {
+ n.underlying = typ
+ }
+
return typ
}
func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, alias bool) {
assert(obj.typ == nil)
+ check.later(func() {
+ check.validType(obj.typ, nil)
+ })
+
if alias {
obj.typ = Typ[Invalid]
// The type of C is the (named) type of A which is incomplete,
// and which has as its underlying type the named type B.
// Determine the (final, unnamed) underlying type by resolving
- // any forward chain (they always end in an unnamed type).
- named.underlying = underlying(named.underlying)
+ // any forward chain.
+ named.underlying = check.underlying(named)
}
return
}
-// indirectType is like typ but it also breaks the (otherwise) infinite size of recursive
-// types by introducing an indirection. It should be called for components of types that
-// are not laid out in place in memory, such as pointer base types, slice or map element
-// types, function parameter types, etc.
-func (check *Checker) indirectType(e ast.Expr) Type {
- check.push(indir)
- defer check.pop()
- return check.definedType(e, nil)
-}
-
// funcType type-checks a function or method type.
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) {
scope := NewScope(check.scope, token.NoPos, token.NoPos, "function")
} else {
typ := new(Slice)
def.setUnderlying(typ)
- typ.elem = check.indirectType(e.Elt)
+ typ.elem = check.typ(e.Elt)
return typ
}
case *ast.StarExpr:
typ := new(Pointer)
def.setUnderlying(typ)
- typ.base = check.indirectType(e.X)
+ typ.base = check.typ(e.X)
return typ
case *ast.FuncType:
typ := new(Map)
def.setUnderlying(typ)
- typ.key = check.indirectType(e.Key)
- typ.elem = check.indirectType(e.Value)
+ typ.key = check.typ(e.Key)
+ typ.elem = check.typ(e.Value)
// spec: "The comparison operators == and != must be fully defined
// for operands of the key type; thus the key type must not be a
}
typ.dir = dir
- typ.elem = check.indirectType(e.Value)
+ typ.elem = check.typ(e.Value)
return typ
default:
// ignore ... and continue
}
}
- typ := check.indirectType(ftype)
+ typ := check.typ(ftype)
// The parser ensures that f.Tag is nil and we don't
// care if a constructed AST contains a non-nil tag.
if len(field.Names) > 0 {
continue // ignore
}
- typ := check.indirectType(f.Type)
+ typ := check.typ(f.Type)
sig, _ := typ.(*Signature)
if sig == nil {
if typ != Typ[Invalid] {
// it if it's a valid interface.
typ := check.typ(f.Type)
- if _, ok := underlying(typ).(*Interface); !ok {
- if typ != Typ[Invalid] {
+ utyp := check.underlying(typ)
+ if _, ok := utyp.(*Interface); !ok {
+ if utyp != Typ[Invalid] {
check.errorf(f.Type.Pos(), "%s is not an interface", typ)
}
continue
}()
}
- ityp.allMethods = markComplete // avoid infinite recursion
+ // An infinitely expanding interface (due to a cycle) is detected
+ // elsewhere (Checker.validType), so here we simply assume we only
+ // have valid interfaces. Mark the interface as complete to avoid
+ // infinite recursion if the validType check occurs later for some
+ // reason.
+ ityp.allMethods = markComplete
// Methods of embedded interfaces are collected unchanged; i.e., the identity
// of a method I.m's Func Object of an interface I is the same as that of
posList := check.posMap[ityp]
for i, typ := range ityp.embeddeds {
pos := posList[i] // embedding position
- typ := underlying(typ).(*Interface)
+ typ, ok := check.underlying(typ).(*Interface)
+ if !ok {
+ // An error was reported when collecting the embedded types.
+ // Ignore it.
+ continue
+ }
check.completeInterface(typ)
for _, m := range typ.allMethods {
addMethod(pos, m, false) // use embedding position pos rather than m.pos