1
0
mirror of https://github.com/golang/go synced 2024-11-22 06:04:39 -07:00

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 { type typeDoc struct {
// len(decl.Specs) == 1, and the element type is *ast.TypeSpec // len(decl.Specs) == 1, and the element type is *ast.TypeSpec
// if the type declaration hasn't been seen yet, decl is nil // 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 // declarations associated with the type
values []*ast.GenDecl // consts and vars values []*ast.GenDecl // consts and vars
factories map[string]*ast.FuncDecl factories map[string]*ast.FuncDecl
methods map[string]*ast.FuncDecl methods map[string]*ast.FuncDecl
embedded []*typeDoc // list of embedded types
} }
// docReader accumulates documentation for a single package. // 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...) 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 { func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
if name == "" { if name == "" || name == "_" {
return nil // no type docs for anonymous types return nil // no type docs for anonymous types
} }
if tdoc, found := doc.types[name]; found { if tdoc, found := doc.types[name]; found {
return tdoc return tdoc
} }
// type wasn't found - add one without declaration // 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 doc.types[name] = tdoc
return 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 { func baseTypeName(typ ast.Expr, allTypes bool) string {
switch t := typ.(type) { switch t := typ.(type) {
case *ast.Ident: case *ast.Ident:
@ -235,10 +220,17 @@ func (doc *docReader) addDecl(decl ast.Decl) {
case token.TYPE: case token.TYPE:
// types are handled individually // types are handled individually
for _, spec := range d.Specs { 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 // (we need to do this here - as opposed to just
// for printing - so we don't lose the GenDecl // 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 // TODO(gri): Consider just collecting the TypeSpec
// node (and copy in the GenDecl.doc if there is no // 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 // makeTypeDocs below). Simpler data structures, but
// would lose GenDecl documentation if the TypeSpec // would lose GenDecl documentation if the TypeSpec
// has documentation as well. // has documentation as well.
tdoc := doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos}) fake := &ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos,
// A new GenDecl node is created, no need to nil out d.Doc. []ast.Spec{tspec}, token.NoPos}
if tdoc == nil { // A type should be added at most once, so tdoc.decl
continue // some error happened; ignore // 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 var fields *ast.FieldList
switch typ := spec.(*ast.TypeSpec).Type.(type) { switch typ := spec.(*ast.TypeSpec).Type.(type) {
case *ast.StructType: case *ast.StructType:
@ -261,11 +254,13 @@ func (doc *docReader) addDecl(decl ast.Decl) {
if fields != nil { if fields != nil {
for _, field := range fields.List { for _, field := range fields.List {
if len(field.Names) == 0 { 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) name := baseTypeName(field.Type, true)
edoc := doc.lookupEmbeddedDoc(name) edoc := doc.lookupTypeDoc(name)
if edoc != nil { 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 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. // TypeDoc is the documentation for a declared type.
// Consts and Vars are sorted lists of constants and variables of (mostly) that 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. // Factories is a sorted list of factory functions that return that type.
@ -440,8 +454,9 @@ type TypeDoc struct {
Consts []*ValueDoc Consts []*ValueDoc
Vars []*ValueDoc Vars []*ValueDoc
Factories []*FuncDoc Factories []*FuncDoc
Methods []*FuncDoc methods []*FuncDoc // top-level methods only
Embedded []*FuncDoc embedded methodSet // embedded methods only
Methods []*FuncDoc // all methods including embedded ones
Decl *ast.GenDecl Decl *ast.GenDecl
order int order int
} }
@ -464,7 +479,13 @@ func (p sortTypeDoc) Less(i, j int) bool {
// blocks, but the doc extractor above has split them into // blocks, but the doc extractor above has split them into
// individual declarations. // individual declarations.
func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc { 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 i := 0
for _, old := range m { for _, old := range m {
// all typeDocs should have a declaration associated with // 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.Consts = makeValueDocs(old.values, token.CONST)
t.Vars = makeValueDocs(old.values, token.VAR) t.Vars = makeValueDocs(old.values, token.VAR)
t.Factories = makeFuncDocs(old.factories) t.Factories = makeFuncDocs(old.factories)
t.Methods = makeFuncDocs(old.methods) t.methods = makeFuncDocs(old.methods)
// TODO(gri) compute list of embedded 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.Decl = old.decl
t.order = i t.order = i
d[i] = t old.forward = t // old has been processed
list[i] = t
i++ i++
} else { } else {
// no corresponding type declaration found - move any associated // 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 list = list[0:i] // some types may have been ignored
sort.Sort(sortTypeDoc(d))
return d // 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 { func makeBugDocs(list []*ast.CommentGroup) []string {