Commit 97853b46 authored by Robert Griesemer's avatar Robert Griesemer

go/doc: steps towards collecting methods of embedded types

No visible external changes yet. The current approach is
a stop-gap approach: For methods of anonymous fields to be
seen, the anonymous field's types must be exported.

Missing: computing the actual MethodDocs and displaying them.

(Depending on the operation mode of godoc, the input to go/doc
is a pre-filtered AST with all non-exported nodes removed. Non-
exported anonymous fields are not even seen by go/doc in this
case, and it is impossible to collect associated (even exported)
methods. A correct fix will require some more significant re-
engineering; AST filtering will have to happen later, possibly
inside go/doc.)

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5500055
parent 721e19c2
...@@ -18,10 +18,11 @@ type typeDoc struct { ...@@ -18,10 +18,11 @@ 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
// values, factory functions, and methods 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.
...@@ -36,6 +37,7 @@ type docReader struct { ...@@ -36,6 +37,7 @@ type docReader struct {
pkgName string pkgName string
values []*ast.GenDecl // consts and vars values []*ast.GenDecl // consts and vars
types map[string]*typeDoc types map[string]*typeDoc
embedded map[string]*typeDoc // embedded types, possibly not exported
funcs map[string]*ast.FuncDecl funcs map[string]*ast.FuncDecl
bugs []*ast.CommentGroup bugs []*ast.CommentGroup
} }
...@@ -43,6 +45,7 @@ type docReader struct { ...@@ -43,6 +45,7 @@ type docReader struct {
func (doc *docReader) init(pkgName string) { func (doc *docReader) init(pkgName string) {
doc.pkgName = pkgName doc.pkgName = pkgName
doc.types = make(map[string]*typeDoc) doc.types = make(map[string]*typeDoc)
doc.embedded = make(map[string]*typeDoc)
doc.funcs = make(map[string]*ast.FuncDecl) doc.funcs = make(map[string]*ast.FuncDecl)
} }
...@@ -52,31 +55,25 @@ func (doc *docReader) addDoc(comments *ast.CommentGroup) { ...@@ -52,31 +55,25 @@ func (doc *docReader) addDoc(comments *ast.CommentGroup) {
doc.doc = comments doc.doc = comments
return return
} }
// More than one package comment: Usually there will be only // More than one package comment: Usually there will be only
// one file with a package comment, but it's better to collect // one file with a package comment, but it's better to collect
// all comments than drop them on the floor. // all comments than drop them on the floor.
// (This code isn't particularly clever - no amortized doubling is blankComment := &ast.Comment{token.NoPos, "//"}
// used - but this situation occurs rarely and is not time-critical.) list := append(doc.doc.List, blankComment)
n1 := len(doc.doc.List) doc.doc.List = append(list, comments.List...)
n2 := len(comments.List) }
list := make([]*ast.Comment, n1+1+n2) // + 1 for separator line
copy(list, doc.doc.List) func (doc *docReader) addType(decl *ast.GenDecl) *typeDoc {
list[n1] = &ast.Comment{token.NoPos, "//"} // separator line
copy(list[n1+1:], comments.List)
doc.doc = &ast.CommentGroup{list}
}
func (doc *docReader) addType(decl *ast.GenDecl) {
spec := decl.Specs[0].(*ast.TypeSpec) spec := decl.Specs[0].(*ast.TypeSpec)
typ := doc.lookupTypeDoc(spec.Name.Name) tdoc := doc.lookupTypeDoc(spec.Name.Name)
// typ should always be != nil since declared types // tdoc should always be != nil since declared types
// are always named - be conservative and check // are always named - be conservative and check
if typ != nil { if tdoc != nil {
// a type should be added at most once, so typ.decl // a type should be added at most once, so tdoc.decl
// should be nil - if it isn't, simply overwrite it // should be nil - if it isn't, simply overwrite it
typ.decl = decl tdoc.decl = decl
} }
return tdoc
} }
func (doc *docReader) lookupTypeDoc(name string) *typeDoc { func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
...@@ -87,21 +84,35 @@ func (doc *docReader) lookupTypeDoc(name string) *typeDoc { ...@@ -87,21 +84,35 @@ func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
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)} tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl), nil}
doc.types[name] = tdoc doc.types[name] = tdoc
return tdoc return tdoc
} }
func baseTypeName(typ ast.Expr) string { 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) { switch t := typ.(type) {
case *ast.Ident: case *ast.Ident:
// if the type is not exported, the effect to // if the type is not exported, the effect to
// a client is as if there were no type name // a client is as if there were no type name
if t.IsExported() { if t.IsExported() || allTypes {
return t.Name return t.Name
} }
case *ast.StarExpr: case *ast.StarExpr:
return baseTypeName(t.X) return baseTypeName(t.X, allTypes)
} }
return "" return ""
} }
...@@ -120,7 +131,7 @@ func (doc *docReader) addValue(decl *ast.GenDecl) { ...@@ -120,7 +131,7 @@ func (doc *docReader) addValue(decl *ast.GenDecl) {
switch { switch {
case v.Type != nil: case v.Type != nil:
// a type is present; determine its name // a type is present; determine its name
name = baseTypeName(v.Type) name = baseTypeName(v.Type, false)
case decl.Tok == token.CONST: case decl.Tok == token.CONST:
// no type is present but we have a constant declaration; // no type is present but we have a constant declaration;
// use the previous type name (w/o more type information // use the previous type name (w/o more type information
...@@ -178,7 +189,7 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) { ...@@ -178,7 +189,7 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) {
// determine if it should be associated with a type // determine if it should be associated with a type
if fun.Recv != nil { if fun.Recv != nil {
// method // method
typ := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type)) typ := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type, false))
if typ != nil { if typ != nil {
// exported receiver type // exported receiver type
setFunc(typ.methods, fun) setFunc(typ.methods, fun)
...@@ -199,7 +210,7 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) { ...@@ -199,7 +210,7 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) {
// exactly one (named or anonymous) result associated // exactly one (named or anonymous) result associated
// with the first type in result signature (there may // with the first type in result signature (there may
// be more than one result) // be more than one result)
tname := baseTypeName(res.Type) tname := baseTypeName(res.Type, false)
typ := doc.lookupTypeDoc(tname) typ := doc.lookupTypeDoc(tname)
if typ != nil { if typ != nil {
// named and exported result type // named and exported result type
...@@ -235,8 +246,30 @@ func (doc *docReader) addDecl(decl ast.Decl) { ...@@ -235,8 +246,30 @@ 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.
doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos}) 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. // A new GenDecl node is created, no need to nil out d.Doc.
if tdoc == nil {
continue // some error happened; ignore
}
var fields *ast.FieldList
switch typ := spec.(*ast.TypeSpec).Type.(type) {
case *ast.StructType:
fields = typ.Fields
case *ast.InterfaceType:
fields = typ.Methods
}
if fields == nil {
for _, field := range fields.List {
if len(field.Names) == 0 {
// anonymous field
name := baseTypeName(field.Type, true)
edoc := doc.lookupEmbeddedDoc(name)
if edoc != nil {
tdoc.embedded = append(tdoc.embedded, edoc)
}
}
}
}
} }
} }
} }
...@@ -408,6 +441,7 @@ type TypeDoc struct { ...@@ -408,6 +441,7 @@ type TypeDoc struct {
Vars []*ValueDoc Vars []*ValueDoc
Factories []*FuncDoc Factories []*FuncDoc
Methods []*FuncDoc Methods []*FuncDoc
Embedded []*FuncDoc
Decl *ast.GenDecl Decl *ast.GenDecl
order int order int
} }
...@@ -452,6 +486,7 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc { ...@@ -452,6 +486,7 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
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
t.Decl = old.decl t.Decl = old.decl
t.order = i t.order = i
d[i] = t d[i] = t
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment