diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/go/types/decl.go | 85 | ||||
| -rw-r--r-- | src/go/types/testdata/cycles5.src | 39 | ||||
| -rw-r--r-- | src/go/types/typexpr.go | 18 |
3 files changed, 137 insertions, 5 deletions
diff --git a/src/go/types/decl.go b/src/go/types/decl.go index aa769ce678..b1543e8a11 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -49,6 +49,13 @@ func pathString(path []*TypeName) string { return s } +// useCycleMarking enables the new coloring-based cycle marking scheme +// for package-level objects. Set this flag to false to disable this +// code quickly and revert to the existing mechanism (and comment out +// some of the new tests in cycles5.src that will fail again). +// TODO(gri) remove this for Go 1.12 +const useCycleMarking = true + // objDecl type-checks the declaration of obj in its respective (file) context. // See check.typ for the details on def and path. func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) { @@ -117,12 +124,32 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) { switch obj := obj.(type) { case *Const: visited = obj.visited + case *Var: visited = obj.visited - default: + + case *TypeName: + assert(obj.Type() != nil) + if useCycleMarking { + check.typeCycle(obj) + } + return + + case *Func: + // Cycles involving functions require variables in + // the cycle; they are pretty esoteric. For now we + // handle this as before (for grey functions, the + // function type is set to an empty signature which + // makes it impossible to initialize a variable with + // the function). assert(obj.Type() != nil) return + + default: + unreachable() } + + // we have a *Const or *Var if obj.Type() != nil { return } @@ -176,6 +203,60 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) { } } +// 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 = new(TypeName) + +// typeCycle checks if the cycle starting with obj is valid and +// reports an error if it is not. +func (check *Checker) typeCycle(obj *TypeName) { + d := check.objMap[obj] + if d == nil { + check.dump("%v: %s should have been declared", obj.Pos(), obj) + unreachable() + } + + // A cycle must have at least one indirection and one defined + // type 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 defined type, we have a sequence of alias + // type names which will expand ad infinitum. + var hasIndir, hasDefType bool + assert(obj.color() >= grey) + start := obj.color() - grey // index of obj in objPath + cycle := check.objPath[start:] + for _, obj := range cycle { + // Cycles may contain various objects; for now only look at type names. + if tname, _ := obj.(*TypeName); tname != nil { + if tname == indir { + hasIndir = true + } else if !check.objMap[tname].alias { + hasDefType = true + } + if hasIndir && hasDefType { + return // cycle is permitted + } + } + } + + // break cycle + // (without this, calling underlying() below may lead to an endless loop) + obj.typ = Typ[Invalid] + + // 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.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented + } + check.errorf(obj.Pos(), "\t%s", obj.Name()) +} + func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) { assert(obj.typ == nil) @@ -353,7 +434,7 @@ func (check *Checker) addMethodDecls(obj *TypeName) { return } delete(check.methods, obj) - assert(!obj.IsAlias()) + assert(!check.objMap[obj].alias) // don't use TypeName.IsAlias (requires fully set up object) // use an objset to check for name conflicts var mset objset diff --git a/src/go/types/testdata/cycles5.src b/src/go/types/testdata/cycles5.src index aab9ee235e..3fa62af5b1 100644 --- a/src/go/types/testdata/cycles5.src +++ b/src/go/types/testdata/cycles5.src @@ -97,12 +97,12 @@ var _ = err.Error() // more esoteric cases type ( - T1 interface { T2 /* ERROR not an interface */ } + T1 interface { T2 } T2 /* ERROR cycle */ T2 ) type ( - T3 interface { T4 /* ERROR not an interface */ } + T3 interface { T4 } T4 /* ERROR cycle */ T5 T5 = T6 T6 = T7 @@ -117,3 +117,38 @@ const n = unsafe.Sizeof(func(){}) type I interface { m([unsafe.Sizeof(func() { I.m(nil, [n]byte{}) })]byte) } + + +// test cases for varias alias cycles + +type T10 /* ERROR cycle */ = *T10 // issue #25141 +type T11 /* ERROR cycle */ = interface{ f(T11) } // issue #23139 + +// issue #18640 +type ( + aa = bb + bb struct { + *aa + } +) + +type ( + a struct{ *b } + b = c + c struct{ *b } +) + +// issue #24939 +type ( + _ interface { + M(P) + } + + M interface { + F() P + } + + P = interface { + I() M + } +) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index d3841c9367..e3f50000ec 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -71,6 +71,12 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa case *TypeName: x.mode = typexpr + // package-level alias cycles are now checked by Checker.objDecl + if useCycleMarking { + if check.objMap[obj] != nil { + break + } + } if check.cycle(obj, path, true) { // maintain x.mode == typexpr despite error typ = Typ[Invalid] @@ -132,7 +138,11 @@ func (check *Checker) cycle(obj *TypeName, path []*TypeName, report bool) bool { // If def != nil, e is the type specification for the named type def, declared // in a type declaration, and def.underlying will be set to the type of e before // any components of e are type-checked. Path contains the path of named types -// referring to this type. +// referring to this type; i.e. it is the path of named types directly containing +// each other and leading to the current type e. Indirect containment (e.g. via +// pointer indirection, function parameter, etc.) breaks the path (leads to a new +// path, and usually via calling Checker.typ below) and those types are not found +// in the path. // func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type) { if trace { @@ -152,6 +162,12 @@ func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type) } func (check *Checker) typ(e ast.Expr) Type { + // typExpr is called with a nil path indicating an indirection: + // push indir sentinel on object path + if useCycleMarking { + check.push(indir) + defer check.pop() + } return check.typExpr(e, nil, nil) } |
