[インデックス 11147] ファイルの概要
このコミットは、Go言語のドキュメント生成ツールであるgo/doc
パッケージの重要な改善を含んでいます。具体的には、匿名(埋め込み)の非エクスポートフィールドが持つエクスポートされたメソッドが、生成されるドキュメントから無視される問題を修正します。この修正に伴い、以前この問題の回避策として存在していたtesting
パッケージ内のwrapper.go
ファイルが削除され、関連するコードもクリーンアップされました。
コミット
go/doc: don't ignore anonymous non-exported fields
- remove wrapper.go from testing package (not needed anymore)
Fixes #1000.
R=rsc, golang-dev, n13m3y3r
CC=golang-dev
https://golang.org/cl/5502074
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9535b86a27bb6ef585e6bafe89ba19dd1bff2cb7
元コミット内容
go/doc: don't ignore anonymous non-exported fields
- remove wrapper.go from testing package (not needed anymore)
Fixes #1000.
R=rsc, golang-dev, n13m3y3r
CC=golang-dev
https://golang.org/cl/5502074
変更の背景
Go言語のドキュメント生成ツールであるgodoc
(内部でgo/doc
パッケージを使用)は、Goのソースコードから自動的にドキュメントを生成し、開発者がAPIを理解する上で不可欠な役割を果たしています。しかし、以前のgo/doc
の実装には、構造体内に匿名で埋め込まれた非エクスポートフィールドが持つエクスポートされたメソッドが、生成されるドキュメントに含まれないという既知のバグが存在していました。
Goの埋め込みの性質上、埋め込まれた型のメソッドは、埋め込み先の型から直接呼び出すことができます。したがって、たとえ埋め込まれたフィールド自体が非エクスポートであっても、そのフィールドが提供するエクスポートされたメソッドは、埋め込み先の型の公開APIの一部と見なされるべきであり、ドキュメントに表示されるべきでした。
この問題は、Go Issue #1000として追跡されており、src/pkg/testing/wrapper.go
ファイル内のコメント(// TODO: delete when godoc shows exported methods for unexported embedded fields.
)からも、この問題が認識されており、一時的な回避策が講じられていたことが伺えます。このコミットは、根本的な原因であるgo/doc
のバグを修正し、その結果として不要になった回避策のコードを削除することを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念と関連パッケージの知識が役立ちます。
-
Goの埋め込み (Embedding in Go): Go言語の構造体は、他の構造体やインターフェースを匿名フィールドとして含めることができます。これを「埋め込み」と呼びます。埋め込まれた型のフィールドやメソッドは、あたかも外側の構造体自身のフィールドやメソッドであるかのように、直接アクセスできるようになります。これは、Goにおける「コンポジション(合成)」によるコードの再利用の強力なメカニズムであり、従来のオブジェクト指向言語の「継承」とは異なるアプローチを提供します。
type Logger struct { prefix string } func (l Logger) Log(message string) { fmt.Printf("%s: %s\n", l.prefix, message) } type Server struct { Logger // Logger型を匿名で埋め込み Addr string } func main() { s := Server{Logger: Logger{prefix: "SERVER"}, Addr: ":8080"} s.Log("Server starting...") // ServerからLoggerのLogメソッドを直接呼び出し }
-
エクスポートされた識別子と非エクスポートされた識別子 (Exported and Unexported Identifiers): Go言語では、識別子(変数名、関数名、型名、構造体のフィールド名など)の最初の文字が大文字で始まる場合、その識別子はパッケージ外からアクセス可能(「エクスポートされる」)になります。一方、小文字で始まる場合は、その識別子は宣言されたパッケージ内でのみアクセス可能(「非エクスポート」)です。
godoc
は通常、エクスポートされた識別子のみをドキュメント化の対象とします。 -
go/doc
パッケージ: Goの標準ライブラリの一部であり、Goのソースコードを解析してドキュメントを生成するためのAPIを提供します。godoc
コマンドは、このパッケージの機能を利用して、Goのパッケージ、型、関数、変数などのドキュメントを生成します。このパッケージは、Goのソースコードの抽象構文木(AST)を操作してドキュメント情報を抽出します。 -
go/ast
パッケージ (Abstract Syntax Tree): Goのソースコードを抽象構文木(AST)として表現するためのパッケージです。Goのコンパイラやツール(go/doc
を含む)は、ソースコードをこのASTに変換し、それを解析・操作することで様々な処理を行います。このコミットでは、ASTのノード(ast.FieldList
,ast.Expr
,ast.TypeSpec
など)を走査し、その構造を分析してドキュメント情報を抽出するロジックが変更されています。 -
go/token
パッケージ: Goのソースコードの字句要素(トークン、例:func
,var
,ident
など)や、ソースコード内の位置情報(ファイル、行、列)を扱うためのパッケージです。 -
runtime.Caller
関数:runtime
パッケージに含まれる関数で、現在のゴルーチンのコールスタックに関する情報を取得するために使用されます。runtime.Caller(skip)
は、skip
で指定されたフレーム数だけ遡った呼び出し元のファイル名、行番号、関数名などを返します。このコミットでは、wrapper.go
の削除によりコールスタックの深さが変わったため、この関数の引数が調整されています。
技術的詳細
このコミットの主要な目的は、go/doc
が匿名(埋め込み)の非エクスポートフィールドを正しく処理し、それらが持つエクスポートされたメソッドをドキュメントに含めるようにすることです。この修正は、主にsrc/pkg/go/doc/exports.go
とsrc/pkg/go/doc/reader.go
の2つのファイルにわたる変更によって実現されています。
-
go/doc
パッケージの変更点:-
src/pkg/go/doc/doc.go
:NewPackageDoc
関数がdocReader
のinit
メソッドを呼び出す際に、exportsOnly
というブール値の引数を追加しました。これにより、docReader
がドキュメント生成時にエクスポートされた要素のみを対象とするか、あるいは全ての要素を対象とするかを制御できるようになります。これは、内部的なドキュメント生成ロジックの柔軟性を高めるための変更です。 -
src/pkg/go/doc/exports.go
:filterFieldList
関数のシグネチャがfunc (doc *docReader) filterFieldList(tinfo *typeInfo, fields *ast.FieldList) (removedFields bool)
に変更されました。tinfo *typeInfo
引数の追加により、この関数が処理しているフィールドがどの型(typeInfo
)に属しているかというコンテキストを得られるようになりました。これにより、匿名フィールドの処理において、そのフィールドが埋め込まれている親の型に関する情報を利用できるようになります。- 匿名フィールドの処理ロジックが大幅に修正されました。以前は、匿名フィールドがエクスポートされている場合にのみ
keepField = true
として、そのフィールドをドキュメントに含めていました。しかし、変更後は、匿名フィールドが非エクスポートであっても、その型が持つメソッドを収集するために、tinfo.addEmbeddedType(embedded, ptr)
を呼び出して、埋め込み型としてtypeInfo
に登録するようになりました。この変更は、非エクスポートの埋め込み型が持つエクスポートされたメソッドもドキュメントに反映させるための核心的な部分です。 filterType
関数のシグネチャもfunc (doc *docReader) filterType(tinfo *typeInfo, typ ast.Expr) bool
に変更されました。これにより、型をフィルタリングする際に、その型が属するtypeInfo
を利用できるようになり、特にast.StructType
やast.InterfaceType
のフィールドを処理する際に、より正確な情報に基づいてフィルタリングが行えるようになりました。
-
src/pkg/go/doc/reader.go
:typeInfo
構造体にname string
とisStruct bool
フィールドが追加されました。name
は基本型名、isStruct
はその型が構造体であるかを示し、ドキュメント生成時の内部処理で利用されます。docReader
構造体にexportsOnly bool
フィールドが追加され、init
関数で初期化されるようになりました。これは、ドキュメント生成時にエクスポートされた要素のみを対象とするかどうかを制御します。addFunc
関数(メソッドを追加するロジック)が修正されました。レシーバの型が非エクスポートであっても、その型が埋め込み型として既にdoc.types
に認識されている場合は、そのメソッドを収集するようになりました。これにより、非エクスポートの埋め込み型に属するエクスポートされたメソッドも正しくドキュメントに反映されます。makeTypeDocs
関数(型ドキュメントを生成するロジック)が大幅に修正されました。特に、old.exported() || !doc.exportsOnly
という条件が追加され、非エクスポートの型であっても、それが埋め込み型である場合や、exportsOnly
がfalse
(つまり全ての型をドキュメント化するモード)である場合には、その型をドキュメントリストに追加するようになりました。これにより、非エクスポートの埋め込み型が持つメソッドもドキュメントに含めることができるようになりました。collectEmbeddedMethods
関数が修正され、埋め込み型がポインタ型である場合のレシーバ型の計算ロジックが改善されました。embeddedIsPtr
という引数が追加され、深くネストされた埋め込み型の場合でも、レシーバ型が正しく解決されるようになりました。
-
-
testing
パッケージの変更点:-
src/pkg/testing/wrapper.go
の削除: このファイルは、go/doc
が非エクスポートの埋め込みフィールドのメソッドを正しく表示しない問題に対する一時的な回避策として存在していました。go/doc
の修正により、このファイルは不要になったため、完全に削除されました。この削除は、コードベースのクリーンアップと、一時的な回避策の解消を意味します。 -
src/pkg/testing/Makefile
の変更:wrapper.go
が削除されたため、Makefile
からwrapper.go
への参照が削除されました。 -
src/pkg/testing/testing.go
の変更:decorate
関数内で使用されているruntime.Caller
の引数が4
から3
に変更されました。runtime.Caller
はコールスタックの深さを指定して呼び出し元情報を取得する関数であり、wrapper.go
が削除されたことでコールスタックの深さが1つ減ったため、ログ出力時に正しいファイル名と行番号を取得するためにこの調整が必要となりました。
-
これらの変更により、go/doc
はGo言語の埋め込みのセマンティクスをより正確に反映し、非エクスポートの匿名フィールドが持つエクスポートされたメソッドも適切にドキュメントに含めることができるようになりました。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/doc/doc.go b/src/pkg/go/doc/doc.go
index 044e996a9e..4011c1fc7a 100644
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -56,7 +56,7 @@ type FuncDoc struct {
// included in the documentation.
func NewPackageDoc(pkg *ast.Package, importpath string, exportsOnly bool) *PackageDoc {
var r docReader
- r.init(pkg.Name)
+ r.init(pkg.Name, exportsOnly)
filenames := make([]string, len(pkg.Files))
i := 0
for filename, f := range pkg.Files {
diff --git a/src/pkg/go/doc/exports.go b/src/pkg/go/doc/exports.go
index 9cd186a9c7..994bf503b5 100644
--- a/src/pkg/go/doc/exports.go
+++ b/src/pkg/go/doc/exports.go
@@ -33,7 +33,7 @@ func baseName(x ast.Expr) *ast.Ident {
return nil
}
-func (doc *docReader) filterFieldList(fields *ast.FieldList) (removedFields bool) {
+func (doc *docReader) filterFieldList(tinfo *typeInfo, fields *ast.FieldList) (removedFields bool) {
if fields == nil {
return false
}
@@ -44,7 +44,18 @@ func (doc *docReader) filterFieldList(fields *ast.FieldList) (removedFields bool
if len(f.Names) == 0 {
// anonymous field
name := baseName(f.Type)
-\t\t\tkeepField = name != nil && name.IsExported()
+\t\t\tif name != nil && name.IsExported() {
+\t\t\t\t// we keep the field - in this case doc.addDecl
+\t\t\t\t// will take care of adding the embedded type
+\t\t\t\tkeepField = true
+\t\t\t} else if tinfo != nil {
+\t\t\t\t// we don't keep the field - add it as an embedded
+\t\t\t\t// type so we won't loose its methods, if any
+\t\t\t\tif embedded := doc.lookupTypeInfo(name.Name); embedded != nil {
+\t\t\t\t\t_, ptr := f.Type.(*ast.StarExpr)
+\t\t\t\t\ttinfo.addEmbeddedType(embedded, ptr)
+\t\t\t\t}
+\t\t\t}
} else {
n := len(f.Names)
f.Names = filterIdentList(f.Names)
@@ -54,7 +65,7 @@ func (doc *docReader) filterFieldList(fields *ast.FieldList) (removedFields bool
keepField = len(f.Names) > 0
}
if keepField {
-\t\t\tdoc.filterType(f.Type)
+\t\t\tdoc.filterType(nil, f.Type)
list[j] = f
j++
}
@@ -72,23 +83,23 @@ func (doc *docReader) filterParamList(fields *ast.FieldList) bool {
}
var b bool
for _, f := range fields.List {
-\t\tif doc.filterType(f.Type) {\n+\t\tif doc.filterType(nil, f.Type) {\
b = true
}
}
return b
}
-func (doc *docReader) filterType(typ ast.Expr) bool {
+func (doc *docReader) filterType(tinfo *typeInfo, typ ast.Expr) bool {
switch t := typ.(type) {
case *ast.Ident:
return ast.IsExported(t.Name)
case *ast.ParenExpr:
-\t\treturn doc.filterType(t.X)
+\t\treturn doc.filterType(nil, t.X)
case *ast.ArrayType:
-\t\treturn doc.filterType(t.Elt)
+\t\treturn doc.filterType(nil, t.Elt)
case *ast.StructType:
-\t\tif doc.filterFieldList(t.Fields) {
+\t\tif doc.filterFieldList(tinfo, t.Fields) {
t.Incomplete = true
}
return len(t.Fields.List) > 0
@@ -97,16 +108,16 @@ func (doc *docReader) filterType(typ ast.Expr) bool {
b2 := doc.filterParamList(t.Results)
return b1 || b2
case *ast.InterfaceType:
-\t\tif doc.filterFieldList(t.Methods) {
+\t\tif doc.filterFieldList(tinfo, t.Methods) {
t.Incomplete = true
}
return len(t.Methods.List) > 0
case *ast.MapType:
-\t\tb1 := doc.filterType(t.Key)
-\t\tb2 := doc.filterType(t.Value)
+\t\tb1 := doc.filterType(nil, t.Key)
+\t\tb2 := doc.filterType(nil, t.Value)
return b1 || b2
case *ast.ChanType:
-\t\treturn doc.filterType(t.Value)
+\t\treturn doc.filterType(nil, t.Value)
}
return false
}
@@ -116,12 +127,12 @@ func (doc *docReader) filterSpec(spec ast.Spec) bool {
case *ast.ValueSpec:
s.Names = filterIdentList(s.Names)
if len(s.Names) > 0 {
-\t\t\tdoc.filterType(s.Type)
+\t\t\tdoc.filterType(nil, s.Type)
return true
}
case *ast.TypeSpec:
if ast.IsExported(s.Name.Name) {
-\t\t\tdoc.filterType(s.Type)
+\t\t\tdoc.filterType(doc.lookupTypeInfo(s.Name.Name), s.Type)
return true
}
}
diff --git a/src/pkg/go/doc/reader.go b/src/pkg/go/doc/reader.go
index 86448d044e..025fc85a10 100644
--- a/src/pkg/go/doc/reader.go
+++ b/src/pkg/go/doc/reader.go
@@ -23,6 +23,8 @@ type embeddedType struct {
}
type typeInfo struct {
+\tname string // base type name
+\tisStruct bool
// 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
@@ -35,6 +37,10 @@ type typeInfo struct {
methods map[string]*ast.FuncDecl
}
+func (info *typeInfo) exported() bool {\n+\treturn ast.IsExported(info.name)\n+}\n+\n func (info *typeInfo) addEmbeddedType(embedded *typeInfo, isPtr bool) {\
info.embedded = append(info.embedded, embeddedType{embedded, isPtr})\
}
@@ -47,17 +53,19 @@ func (info *typeInfo) addEmbeddedType(embedded *typeInfo, isPtr bool) {\
// printing the corresponding AST node).\
//\
type docReader struct {\
-\tdoc *ast.CommentGroup // package documentation, if any\
-\tpkgName string\
-\tvalues []*ast.GenDecl // consts and vars\
-\ttypes map[string]*typeInfo\
-\tembedded map[string]*typeInfo // embedded types, possibly not exported\
-\tfuncs map[string]*ast.FuncDecl\
-\tbugs []*ast.CommentGroup\
+\tdoc *ast.CommentGroup // package documentation, if any\
+\tpkgName string\
+\texportsOnly bool\
+\tvalues []*ast.GenDecl // consts and vars\
+\ttypes map[string]*typeInfo\
+\tembedded map[string]*typeInfo // embedded types, possibly not exported\
+\tfuncs map[string]*ast.FuncDecl\
+\tbugs []*ast.CommentGroup\
}\
-func (doc *docReader) init(pkgName string) {\
+func (doc *docReader) init(pkgName string, exportsOnly bool) {\
\tdoc.pkgName = pkgName\
+\tdoc.exportsOnly = exportsOnly\
\tdoc.types = make(map[string]*typeInfo)\
\tdoc.embedded = make(map[string]*typeInfo)\
\tdoc.funcs = make(map[string]*ast.FuncDecl)\
@@ -86,6 +94,7 @@ func (doc *docReader) lookupTypeInfo(name string) *typeInfo {\
}\
// type wasn't found - add one without declaration\
info := &typeInfo{\
+\t\tname: name,\
\t\tfactories: make(map[string]*ast.FuncDecl),\
\t\tmethods: make(map[string]*ast.FuncDecl),\
\t}\
@@ -182,9 +191,23 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) {\
// determine if it should be associated with a type\
if fun.Recv != nil {\
// method\
-\t\ttyp := doc.lookupTypeInfo(baseTypeName(fun.Recv.List[0].Type, false))\
+\t\trecvTypeName := baseTypeName(fun.Recv.List[0].Type, true /* exported or not */ )\
+\t\tvar typ *typeInfo\
+\t\tif ast.IsExported(recvTypeName) {\
+\t\t\t// exported recv type: if not found, add it to doc.types\
+\t\t\ttyp = doc.lookupTypeInfo(recvTypeName)\
+\t\t} else {\n+\t\t\t// unexported recv type: if not found, do not add it\n+\t\t\t// (unexported embedded types are added before this\n+\t\t\t// phase, so if the type doesn\'t exist yet, we don\'t\n+\t\t\t// care about this method)\n+\t\t\ttyp = doc.types[recvTypeName]\
+\t\t}\
if typ != nil {\
// exported receiver type\
+\t\t\t// associate method with the type\n+\t\t\t// (if the type is not exported, it may be embedded\n+\t\t\t// somewhere so we need to collect the method anyway)\
setFunc(typ.methods, fun)\
}\
// otherwise don't show the method\
@@ -256,6 +279,7 @@ func (doc *docReader) addDecl(decl ast.Decl) {\
switch typ := spec.(*ast.TypeSpec).Type.(type) {\
case *ast.StructType:\
fields = typ.Fields\
+\t\t\t\t\t\tinfo.isStruct = true\
case *ast.InterfaceType:\
fields = typ.Methods\
}\
@@ -439,21 +463,25 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeInfo) []*TypeDoc {\
list := make([]*TypeDoc, len(m))\
i := 0\
for _, old := range m {\
-\t\t// all typeInfos should have a declaration associated with\
-\t\t// them after processing an entire package - be conservative\
-\t\t// and check\
-\t\tif decl := old.decl; decl != nil {\
-\t\t\ttypespec := decl.Specs[0].(*ast.TypeSpec)\
+\t\t// old typeInfos may not have a declaration associated with them\
+\t\t// if they are not exported but embedded, or because the package\
+\t\t// is incomplete.\
+\t\tif decl := old.decl; decl != nil || !old.exported() {\
+\t\t\t// process the type even if not exported so that we have\n+\t\t\t// its methods in case they are embedded somewhere\
\t\t\tt := new(TypeDoc)\
-\t\t\tdoc := typespec.Doc\
-\t\t\ttypespec.Doc = nil // doc consumed - remove from ast.TypeSpec node\
-\t\t\tif doc == nil {\
-\t\t\t\t// no doc associated with the spec, use the declaration doc, if any\
-\t\t\t\tdoc = decl.Doc\
+\t\t\tif decl != nil {\
+\t\t\t\ttypespec := decl.Specs[0].(*ast.TypeSpec)\
+\t\t\t\tdoc := typespec.Doc\
+\t\t\t\ttypespec.Doc = nil // doc consumed - remove from ast.TypeSpec node\
+\t\t\t\tif doc == nil {\
+\t\t\t\t\t// no doc associated with the spec, use the declaration doc, if any\
+\t\t\t\t\tdoc = decl.Doc\
+\t\t\t\t}\n+\t\t\t\tdecl.Doc = nil // doc consumed - remove from ast.Decl node\
+\t\t\t\tt.Doc = doc.Text()\
+\t\t\t\tt.Type = typespec\
\t\t\t}\
-\t\t\tdecl.Doc = nil // doc consumed - remove from ast.Decl node\
-\t\t\tt.Doc = doc.Text()\
-\t\t\tt.Type = typespec\
\t\t\tt.Consts = makeValueDocs(old.values, token.CONST)\
\t\t\tt.Vars = makeValueDocs(old.values, token.VAR)\
\t\t\tt.Factories = makeFuncDocs(old.factories)\
@@ -466,8 +504,12 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeInfo) []*TypeDoc {\
\t\t\tt.Decl = old.decl\
\t\t\tt.order = i\
\t\t\told.forward = t // old has been processed\
-\t\t\tlist[i] = t\
-\t\t\ti++\
+\t\t\t// only add the type to the final type list if it\n+\t\t\t// is exported or if we want to see all types\n+\t\t\tif old.exported() || !doc.exportsOnly {\n+\t\t\t\tlist[i] = t\n+\t\t\t\ti++\n+\t\t\t}\
\t\t} else {\
\t\t\t// no corresponding type declaration found - move any associated\
\t\t\t// values, factory functions, and methods back to the top-level\
@@ -497,11 +539,10 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeInfo) []*TypeDoc {\
\t\t\t// old has been processed into t; collect embedded\
\t\t\t// methods for t from the list of processed embedded\
\t\t\t// types in old (and thus for which the methods are known)\
-\t\t\ttyp := t.Type\
-\t\t\tif _, ok := typ.Type.(*ast.StructType); ok {\
+\t\t\tif old.isStruct {\
\t\t\t\t// struct\
\t\t\t\tt.embedded = make(methodSet)\
-\t\t\t\tcollectEmbeddedMethods(t.embedded, old, typ.Name.Name)\
+\t\t\t\tcollectEmbeddedMethods(t.embedded, old, old.name, false)\
\t\t\t} else {\
\t\t\t\t// interface\
\t\t\t\t// TODO(gri) fix this\
@@ -541,13 +582,19 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeInfo) []*TypeDoc {\
// deeply nested embedded methods with conflicting names are\
// excluded.\
//\
-func collectEmbeddedMethods(mset methodSet, info *typeInfo, recvTypeName string) {\
+func collectEmbeddedMethods(mset methodSet, info *typeInfo, recvTypeName string, embeddedIsPtr bool) {\
\tfor _, e := range info.embedded {\
\t\tif e.typ.forward != nil { // == e was processed\
+\t\t\t// Once an embedded type was embedded as a pointer type\n+\t\t\t// all embedded types in those types are treated like\n+\t\t\t// pointer types for the purpose of the receiver type\n+\t\t\t// computation; i.e., embeddedIsPtr is sticky for this\n+\t\t\t// embedding hierarchy.\n+\t\t\tthisEmbeddedIsPtr := embeddedIsPtr || e.ptr\
\t\t\tfor _, m := range e.typ.forward.methods {\
-\t\t\t\tmset.add(customizeRecv(m, e.ptr, recvTypeName))\n+\t\t\t\tmset.add(customizeRecv(m, thisEmbeddedIsPtr, recvTypeName))\
\t\t\t}\
-\t\t\tcollectEmbeddedMethods(mset, e.typ, recvTypeName)\
+\t\t\tcollectEmbeddedMethods(mset, e.typ, recvTypeName, thisEmbeddedIsPtr)\
\t\t}\
\t}\
}\
@@ -558,12 +605,10 @@ func customizeRecv(m *FuncDoc, embeddedIsPtr bool, recvTypeName string) *FuncDoc\
\t}\
\n \t// copy existing receiver field and set new type\n-\t// TODO(gri) is receiver type computation correct?\n-\t// what about deeply nested embeddings?\n \tnewField := *m.Decl.Recv.List[0]\n \t_, origRecvIsPtr := newField.Type.(*ast.StarExpr)\n \tvar typ ast.Expr = ast.NewIdent(recvTypeName)\n-\tif embeddedIsPtr || origRecvIsPtr {\n+\tif !embeddedIsPtr && origRecvIsPtr {\
\t\ttyp = &ast.StarExpr{token.NoPos, typ}\n \t}\n \tnewField.Type = typ\
diff --git a/src/pkg/testing/Makefile b/src/pkg/testing/Makefile
index 4b148d9717..a0c1232e36 100644
--- a/src/pkg/testing/Makefile
+++ b/src/pkg/testing/Makefile
@@ -9,6 +9,5 @@ GOFILES=\
benchmark.go\\
example.go\\
testing.go\\
-\twrapper.go\\
\n include ../../Make.pkg
diff --git a/src/pkg/testing/testing.go b/src/pkg/testing/testing.go
index d75dac8f60..cfe212dc1d 100644
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -90,7 +90,7 @@ func Short() bool {\
// If addFileLine is true, it also prefixes the string with the file and line of the call site.\
func decorate(s string, addFileLine bool) string {\
if addFileLine {\
-\t\t_, file, line, ok := runtime.Caller(4) // decorate + log + public function.\
+\t\t_, file, line, ok := runtime.Caller(3) // decorate + log + public function.\
\t\tif ok {\
\t\t\t// Truncate file name at last file name separator.\
\t\t\tif index := strings.LastIndex(file, \"/\"); index >= 0 {\
diff --git a/src/pkg/testing/wrapper.go b/src/pkg/testing/wrapper.go
deleted file mode 100644
index 2bef9df9c6..0000000000
--- a/src/pkg/testing/wrapper.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.\
-// Use of this source code is governed by a BSD-style\
-// license that can be found in the LICENSE file.\
-\
-// This file contains wrappers so t.Errorf etc. have documentation.\
-// TODO: delete when godoc shows exported methods for unexported embedded fields.\
-// TODO: need to change the argument to runtime.Caller in testing.go from 4 to 3 at that point.\
-\
-package testing\
-\
-// Fail marks the function as having failed but continues execution.\
-func (b *B) Fail() {\
-\tb.common.Fail()\
-}\
-\
-// Failed returns whether the function has failed.\
-func (b *B) Failed() bool {\
-\treturn b.common.Failed()\
-}\
-\
-// FailNow marks the function as having failed and stops its execution.\
-// Execution will continue at the next Test.\
-func (b *B) FailNow() {\
-\tb.common.FailNow()\
-}\
-\
-// Log formats its arguments using default formatting, analogous to Println(),\
-// and records the text in the error log.\
-func (b *B) Log(args ...interface{}) {\
-\tb.common.Log(args...)\
-}\
-\
-// Logf formats its arguments according to the format, analogous to Printf(),\
-// and records the text in the error log.\
-func (b *B) Logf(format string, args ...interface{}) {\
-\tb.common.Logf(format, args...)\
-}\
-\
-// Error is equivalent to Log() followed by Fail().\
-func (b *B) Error(args ...interface{}) {\
-\tb.common.Error(args...)\
-}\
-\n-// Errorf is equivalent to Logf() followed by Fail().\
-func (b *B) Errorf(format string, args ...interface{}) {\
-\tb.common.Errorf(format, args...)\
-}\
-\n-// Fatal is equivalent to Log() followed by FailNow().\
-func (b *B) Fatal(args ...interface{}) {\
-\tb.common.Fatal(args...)\
-}\
-\n-// Fatalf is equivalent to Logf() followed by FailNow().\
-func (b *B) Fatalf(format string, args ...interface{}) {\
-\tb.common.Fatalf(format, args...)\
-}\
-\n-// Fail marks the function as having failed but continues execution.\
-func (t *T) Fail() {\
-\tt.common.Fail()\
-}\
-\n-// Failed returns whether the function has failed.\
-func (t *T) Failed() bool {\
-\treturn t.common.Failed()\
-}\
-\n-// FailNow marks the function as having failed and stops its execution.\
-// Execution will continue at the next Test.\
-func (t *T) FailNow() {\
-\tt.common.FailNow()\
-}\
-\n-// Log formats its arguments using default formatting, analogous to Println(),\
-// and records the text in the error log.\
-func (t *T) Log(args ...interface{}) {\
-\tt.common.Log(args...)\
-}\
-\n-// Logf formats its arguments according to the format, analogous to Printf(),\
-// and records the text in the error log.\
-func (t *T) Logf(format string, args ...interface{}) {\
-\tt.common.Logf(format, args...)\
-}\
-\n-// Error is equivalent to Log() followed by Fail().\
-func (t *T) Error(args ...interface{}) {\
-\tt.common.Error(args...)\
-}\
-\n-// Errorf is equivalent to Logf() followed by Fail().\
-func (t *T) Errorf(format string, args ...interface{}) {\
-\tt.common.Errorf(format, args...)\
-}\
-\n-// Fatal is equivalent to Log() followed by FailNow().\
-func (t *T) Fatal(args ...interface{}) {\
-\tt.common.Fatal(args...)\
-}\
-\n-// Fatalf is equivalent to Logf() followed by FailNow().\
-func (t *T) Fatalf(format string, args ...interface{}) {\
-\tt.common.Fatalf(format, args...)\
-}\
コアとなるコードの解説
-
src/pkg/go/doc/doc.go
:NewPackageDoc
関数は、パッケージのドキュメントを生成するエントリポイントです。この変更により、docReader
の初期化時にexportsOnly
というフラグが渡されるようになりました。このフラグは、生成されるドキュメントがエクスポートされた要素のみを含むべきか(true
の場合)、あるいは全ての要素を含むべきか(false
の場合)をdocReader
に伝えます。これにより、go/doc
の内部ロジックが、ドキュメント生成の対象範囲をより柔軟に制御できるようになります。
-
src/pkg/go/doc/exports.go
:filterFieldList
関数は、構造体やインターフェースのフィールドリストをフィルタリングし、ドキュメントに含めるべきフィールドを決定します。この関数は、tinfo *typeInfo
という新しい引数を受け取るようになりました。tinfo
は、現在処理しているフィールドが属する型に関する情報を提供します。- 匿名フィールド(
len(f.Names) == 0
)の処理ロジックが拡張されました。以前は、匿名フィールドがエクスポートされている場合にのみkeepField = true
としていましたが、変更後は、匿名フィールドが非エクスポートであっても、その型が持つメソッドを収集するために、tinfo.addEmbeddedType(embedded, ptr)
を呼び出して、埋め込み型としてtypeInfo
に登録するようになりました。これは、非エクスポートの埋め込み型が持つエクスポートされたメソッドもドキュメントに反映させるための最も重要な変更点です。 filterType
関数のシグネチャもtinfo *typeInfo
引数を含むように変更されました。これにより、型をフィルタリングする際に、その型が属するtypeInfo
を利用できるようになり、特に構造体やインターフェースのフィールドを処理する際に、より正確な情報に基づいてフィルタリングが行えるようになりました。
-
src/pkg/go/doc/reader.go
:typeInfo
構造体には、型の基本名を示すname string
と、その型が構造体であるかを示すisStruct bool
が追加されました。これらは、ドキュメント生成時の内部処理で、型の識別と特性の判断に利用されます。docReader
構造体には、exportsOnly bool
フィールドが追加され、init
関数で初期化されるようになりました。これは、ドキュメント生成時にエクスポートされた要素のみを対象とするかどうかを制御します。addFunc
関数は、関数(特にメソッド)をdocReader
に追加する役割を担います。この関数内のレシーバ型処理が修正され、レシーバの型が非エクスポートであっても、それが埋め込み型として既にdoc.types
に認識されている場合は、そのメソッドを収集するようになりました。これにより、非エクスポートの埋め込み型に属するエクスポートされたメソッドも正しくドキュメントに反映されます。makeTypeDocs
関数は、typeInfo
マップからTypeDoc
のリストを生成します。この関数では、old.exported() || !doc.exportsOnly
という条件が追加され、非エクスポートの型であっても、それが埋め込み型である場合や、exportsOnly
がfalse
(つまり全ての型をドキュメント化するモード)である場合には、その型をドキュメントリストに追加するようになりました。これにより、非エクスポートの埋め込み型が持つメソッドもドキュメントに含めることができるようになりました。collectEmbeddedMethods
関数は、埋め込み型からメソッドを収集する再帰関数です。この関数では、埋め込み型がポインタ型である場合のレシーバ型の計算ロジックが改善され、embeddedIsPtr
という引数が追加されました。これにより、深くネストされた埋め込み型の場合でも、レシーba型が正しく解決されるようになりました。
-
src/pkg/testing/Makefile
とsrc/pkg/testing/testing.go
、src/pkg/testing/wrapper.go
:src/pkg/testing/wrapper.go
は、go/doc
のバグに対する一時的な回避策として存在していたファイルであり、このコミットでgo/doc
のバグが修正されたため、完全に削除されました。src/pkg/testing/Makefile
からwrapper.go
への参照が削除されました。src/pkg/testing/testing.go
のdecorate
関数では、runtime.Caller
の引数が4
から3
に変更されました。これは、wrapper.go
が削除されたことでコールスタックの深さが1つ減ったため、ログ出力時に正しいファイル名と行番号を取得するための調整です。
これらの変更は、Goのドキュメント生成の正確性を向上させ、Go言語の埋め込みのセマンティクスをより忠実に反映するようにgo/doc
の動作を修正するものです。
関連リンク
-
Go Issue #1000: godoc: don't ignore anonymous non-exported fields このコミットが修正したバグに関する公式のIssueトラッカーです。 https://github.com/golang/go/issues/1000
-
Go Code Review: go/doc: don't ignore anonymous non-exported fields このコミットのコードレビューページです。詳細な議論や変更の経緯を確認できます。 https://golang.org/cl/5502074
参考にした情報源リンク
-
Go言語の仕様 (The Go Programming Language Specification): Go言語の埋め込みやエクスポートルールに関する公式な定義。 https://go.dev/ref/spec
-
Go Doc (godoc): Goのドキュメント生成ツールに関する情報。 https://go.dev/blog/godoc
-
Goのソースコード (Go Source Code):
go/doc
やgo/ast
、go/token
、testing
パッケージの実際のソースコード。 https://github.com/golang/go