go/doc, godoc: show methods of anonymous fields

Missing: Handling of embedded interfaces.

Also, for reasons outlined in the previous CL (5500055), embedded
types have to be exported for its "inherited" methods to be visible.
This will be addressed w/ a subsequent CL.

R=r, rsc
CC=golang-dev
https://golang.org/cl/5502059
This commit is contained in:
Robert Griesemer 2011-12-22 13:11:40 -08:00
parent b1a287e3a1
commit 7ea92ddd66

View File

@ -14,15 +14,24 @@ import (
// ----------------------------------------------------------------------------
// embeddedType describes the type of an anonymous field.
//
type embeddedType struct {
typ *typeDoc // the corresponding base type
ptr bool // if set, the anonymous field type is a pointer
}
type typeDoc struct {
// len(decl.Specs) == 1, and the element type is *ast.TypeSpec
// if the type declaration hasn't been seen yet, decl is nil
decl *ast.GenDecl
decl *ast.GenDecl
embedded []embeddedType
forward *TypeDoc // forward link to processed type documentation
// declarations associated with the type
values []*ast.GenDecl // consts and vars
factories map[string]*ast.FuncDecl
methods map[string]*ast.FuncDecl
embedded []*typeDoc // list of embedded types
}
// docReader accumulates documentation for a single package.
@ -63,46 +72,22 @@ func (doc *docReader) addDoc(comments *ast.CommentGroup) {
doc.doc.List = append(list, comments.List...)
}
func (doc *docReader) addType(decl *ast.GenDecl) *typeDoc {
spec := decl.Specs[0].(*ast.TypeSpec)
tdoc := doc.lookupTypeDoc(spec.Name.Name)
// tdoc should always be != nil since declared types
// are always named - be conservative and check
if tdoc != nil {
// a type should be added at most once, so tdoc.decl
// should be nil - if it isn't, simply overwrite it
tdoc.decl = decl
}
return tdoc
}
func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
if name == "" {
if name == "" || name == "_" {
return nil // no type docs for anonymous types
}
if tdoc, found := doc.types[name]; found {
return tdoc
}
// type wasn't found - add one without declaration
tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl), nil}
tdoc := &typeDoc{
factories: make(map[string]*ast.FuncDecl),
methods: make(map[string]*ast.FuncDecl),
}
doc.types[name] = tdoc
return tdoc
}
func (doc *docReader) lookupEmbeddedDoc(name string) *typeDoc {
if name == "" {
return nil
}
if tdoc, found := doc.embedded[name]; found {
return tdoc
}
// type wasn't found - add one without declaration
// note: embedded types only have methods associated with them
tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl), nil}
doc.embedded[name] = tdoc
return tdoc
}
func baseTypeName(typ ast.Expr, allTypes bool) string {
switch t := typ.(type) {
case *ast.Ident:
@ -235,10 +220,17 @@ func (doc *docReader) addDecl(decl ast.Decl) {
case token.TYPE:
// types are handled individually
for _, spec := range d.Specs {
// make a (fake) GenDecl node for this TypeSpec
tspec := spec.(*ast.TypeSpec)
// add the type to the documentation
tdoc := doc.lookupTypeDoc(tspec.Name.Name)
if tdoc == nil {
continue // no name - ignore the type
}
// Make a (fake) GenDecl node for this TypeSpec
// (we need to do this here - as opposed to just
// for printing - so we don't lose the GenDecl
// documentation)
// documentation). Since a new GenDecl node is
// created, there's no need to nil out d.Doc.
//
// TODO(gri): Consider just collecting the TypeSpec
// node (and copy in the GenDecl.doc if there is no
@ -246,11 +238,12 @@ func (doc *docReader) addDecl(decl ast.Decl) {
// makeTypeDocs below). Simpler data structures, but
// would lose GenDecl documentation if the TypeSpec
// has documentation as well.
tdoc := doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})
// A new GenDecl node is created, no need to nil out d.Doc.
if tdoc == nil {
continue // some error happened; ignore
}
fake := &ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos,
[]ast.Spec{tspec}, token.NoPos}
// A type should be added at most once, so tdoc.decl
// should be nil - if it isn't, simply overwrite it.
tdoc.decl = fake
// Look for anonymous fields that might contribute methods.
var fields *ast.FieldList
switch typ := spec.(*ast.TypeSpec).Type.(type) {
case *ast.StructType:
@ -261,11 +254,13 @@ func (doc *docReader) addDecl(decl ast.Decl) {
if fields != nil {
for _, field := range fields.List {
if len(field.Names) == 0 {
// anonymous field
// anonymous field - add corresponding type
// to the tdoc and collect it in doc
name := baseTypeName(field.Type, true)
edoc := doc.lookupEmbeddedDoc(name)
edoc := doc.lookupTypeDoc(name)
if edoc != nil {
tdoc.embedded = append(tdoc.embedded, edoc)
_, ptr := field.Type.(*ast.StarExpr)
tdoc.embedded = append(tdoc.embedded, embeddedType{edoc, ptr})
}
}
}
@ -430,6 +425,25 @@ func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
return d
}
type methodSet map[string]*FuncDoc
func (mset methodSet) add(m *FuncDoc) {
if mset[m.Name] == nil {
mset[m.Name] = m
}
}
func (mset methodSet) sortedList() []*FuncDoc {
list := make([]*FuncDoc, len(mset))
i := 0
for _, m := range mset {
list[i] = m
i++
}
sort.Sort(sortFuncDoc(list))
return list
}
// TypeDoc is the documentation for a declared type.
// Consts and Vars are sorted lists of constants and variables of (mostly) that type.
// Factories is a sorted list of factory functions that return that type.
@ -440,8 +454,9 @@ type TypeDoc struct {
Consts []*ValueDoc
Vars []*ValueDoc
Factories []*FuncDoc
Methods []*FuncDoc
Embedded []*FuncDoc
methods []*FuncDoc // top-level methods only
embedded methodSet // embedded methods only
Methods []*FuncDoc // all methods including embedded ones
Decl *ast.GenDecl
order int
}
@ -464,7 +479,13 @@ func (p sortTypeDoc) Less(i, j int) bool {
// blocks, but the doc extractor above has split them into
// individual declarations.
func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
d := make([]*TypeDoc, len(m))
// TODO(gri) Consider computing the embedded method information
// before calling makeTypeDocs. Then this function can
// be single-phased again. Also, it might simplify some
// of the logic.
//
// phase 1: associate collected declarations with TypeDocs
list := make([]*TypeDoc, len(m))
i := 0
for _, old := range m {
// all typeDocs should have a declaration associated with
@ -485,11 +506,16 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
t.Consts = makeValueDocs(old.values, token.CONST)
t.Vars = makeValueDocs(old.values, token.VAR)
t.Factories = makeFuncDocs(old.factories)
t.Methods = makeFuncDocs(old.methods)
// TODO(gri) compute list of embedded methods
t.methods = makeFuncDocs(old.methods)
// The list of embedded types' methods is computed from the list
// of embedded types, some of which may not have been processed
// yet (i.e., their forward link is nil) - do this in a 2nd phase.
// The final list of methods can only be computed after that -
// do this in a 3rd phase.
t.Decl = old.decl
t.order = i
d[i] = t
old.forward = t // old has been processed
list[i] = t
i++
} else {
// no corresponding type declaration found - move any associated
@ -512,9 +538,99 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
}
}
}
d = d[0:i] // some types may have been ignored
sort.Sort(sortTypeDoc(d))
return d
list = list[0:i] // some types may have been ignored
// phase 2: collect embedded methods for each processed typeDoc
for _, old := range m {
if t := old.forward; t != nil {
// old has been processed into t; collect embedded
// methods for t from the list of processed embedded
// types in old (and thus for which the methods are known)
typ := t.Type
if _, ok := typ.Type.(*ast.StructType); ok {
// struct
t.embedded = make(methodSet)
collectEmbeddedMethods(t.embedded, old, typ.Name.Name)
} else {
// interface
// TODO(gri) fix this
}
}
}
// phase 3: compute final method set for each TypeDoc
for _, d := range list {
if len(d.embedded) > 0 {
// there are embedded methods - exclude
// the ones with names conflicting with
// non-embedded methods
mset := make(methodSet)
// top-level methods have priority
for _, m := range d.methods {
mset.add(m)
}
// add non-conflicting embedded methods
for _, m := range d.embedded {
mset.add(m)
}
d.Methods = mset.sortedList()
} else {
// no embedded methods
d.Methods = d.methods
}
}
sort.Sort(sortTypeDoc(list))
return list
}
// collectEmbeddedMethods collects the embedded methods from all
// processed embedded types found in tdoc in mset. It considers
// embedded types at the most shallow level first so that more
// deeply nested embedded methods with conflicting names are
// excluded.
//
func collectEmbeddedMethods(mset methodSet, tdoc *typeDoc, recvTypeName string) {
for _, e := range tdoc.embedded {
if e.typ.forward != nil { // == e was processed
for _, m := range e.typ.forward.methods {
mset.add(customizeRecv(m, e.ptr, recvTypeName))
}
collectEmbeddedMethods(mset, e.typ, recvTypeName)
}
}
}
func customizeRecv(m *FuncDoc, embeddedIsPtr bool, recvTypeName string) *FuncDoc {
if m == nil || m.Decl == nil || m.Decl.Recv == nil || len(m.Decl.Recv.List) != 1 {
return m // shouldn't happen, but be safe
}
// copy existing receiver field and set new type
// TODO(gri) is receiver type computation correct?
// what about deeply nested embeddings?
newField := *m.Decl.Recv.List[0]
_, origRecvIsPtr := newField.Type.(*ast.StarExpr)
var typ ast.Expr = ast.NewIdent(recvTypeName)
if embeddedIsPtr || origRecvIsPtr {
typ = &ast.StarExpr{token.NoPos, typ}
}
newField.Type = typ
// copy existing receiver field list and set new receiver field
newFieldList := *m.Decl.Recv
newFieldList.List = []*ast.Field{&newField}
// copy existing function declaration and set new receiver field list
newFuncDecl := *m.Decl
newFuncDecl.Recv = &newFieldList
// copy existing function documentation and set new declaration
newM := *m
newM.Decl = &newFuncDecl
newM.Recv = typ
return &newM
}
func makeBugDocs(list []*ast.CommentGroup) []string {