[インデックス 10946] ファイルの概要
このコミットは、Go言語のドキュメンテーションツールである go/doc
パッケージにおいて、埋め込み型(embedded types)のメソッドを収集するための初期段階の変更を導入します。具体的には、匿名フィールドとして埋め込まれた型のメソッドを go/doc
が認識し、ドキュメントに含めるための基盤を構築しています。現時点では外部から見える変更はありませんが、将来的にこれらのメソッドがドキュメントに表示されるようにするための重要なステップです。
コミット
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
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/97853b46a08e984048e65f1d9c359bb48b8f22e4
元コミット内容
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
変更の背景
この変更の背景には、Go言語のドキュメンテーションツール godoc
が、埋め込み型(特に匿名フィールドとして埋め込まれた型)のメソッドを適切に収集・表示できないという課題がありました。
従来の godoc
の動作モードによっては、go/doc
パッケージへの入力として、エクスポートされていない(非公開の)ノードがすべて削除された抽象構文木(AST)が渡されることがありました。この「事前フィルタリング」のプロセスにより、非公開の匿名フィールドは go/doc
から完全に隠されてしまい、結果として、たとえその匿名フィールドの型が公開されており、公開されたメソッドを持っていたとしても、go/doc
はそれらのメソッドを認識し、ドキュメントに含めることができませんでした。
このコミットは、この問題を根本的に解決するための「一時的な(stop-gap)」アプローチとして、まず go/doc
が埋め込み型を認識し、その情報を内部的に保持できるようにするための基盤を導入しています。最終的な解決策としては、ASTのフィルタリングを go/doc
の内部で行うなど、より大規模な再設計が必要であると認識されていますが、このコミットはその第一歩となります。
前提知識の解説
Go言語の埋め込み型(Embedded Types)と匿名フィールド(Anonymous Fields)
Go言語には、構造体(struct)内に他の型を「埋め込む」機能があります。これは、構造体のフィールドとして型名のみを指定し、フィールド名を省略することで実現されます。これを「匿名フィールド」と呼びます。匿名フィールドとして型を埋め込むと、その埋め込まれた型のメソッドが、外側の構造体のメソッドであるかのように直接呼び出せるようになります。これは、Goにおける「コンポジションによるコードの再利用」の主要なメカニズムの一つです。
例:
type Base struct {
Name string
}
func (b Base) Greet() string {
return "Hello, " + b.Name
}
type User struct {
Base // Base型を匿名フィールドとして埋め込み
Email string
}
func main() {
u := User{Base: Base{Name: "Alice"}, Email: "alice@example.com"}
println(u.Greet()) // User型からBase型のGreetメソッドを直接呼び出し
}
go/doc
パッケージ
go/doc
パッケージは、Goのソースコードからドキュメンテーションを生成するための標準ライブラリです。godoc
コマンドやGoの公式ドキュメントサイト(pkg.go.devなど)のバックエンドとして利用されています。このパッケージは、Goのソースコードを解析してASTを構築し、そのASTからパッケージ、型、関数、変数などのドキュメンテーションコメントやシグネチャを抽出し、構造化されたデータとして提供します。
抽象構文木(Abstract Syntax Tree: AST)
ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタ、コード分析ツールなどがソースコードを処理する際に、字句解析(トークン化)と構文解析(パース)を経て生成されます。Go言語では、go/ast
パッケージがASTの表現と操作を提供します。go/doc
はこのASTを読み込み、ドキュメンテーション情報を抽出します。
エクスポートされた(Exported)識別子と非エクスポートされた(Unexported)識別子
Go言語では、識別子(変数名、関数名、型名、フィールド名など)の最初の文字が大文字である場合、その識別子はパッケージ外からアクセス可能(エクスポートされている)になります。小文字で始まる場合は、パッケージ内でのみアクセス可能(エクスポートされていない、非公開)です。godoc
は通常、エクスポートされた識別子のみをドキュメント化の対象とします。
技術的詳細
このコミットは、src/pkg/go/doc/doc.go
ファイルに対して変更を加えています。主な目的は、go/doc
が埋め込み型、特に匿名フィールドとして埋め込まれた型の情報を内部的に追跡できるようにすることです。
変更の核心は、typeDoc
および docReader
という内部構造体の定義の拡張と、それらに関連するヘルパー関数の修正です。
-
typeDoc
構造体の拡張:typeDoc
は、特定の型に関連するドキュメンテーション情報を保持する構造体です。- 新たに
embedded []*typeDoc
フィールドが追加されました。これは、その型が埋め込んでいる他の型のtypeDoc
リストを保持するためのものです。これにより、go/doc
は型の階層構造と埋め込み関係を内部的に表現できるようになります。
-
docReader
構造体の拡張:docReader
は、単一のパッケージのドキュメンテーションを収集する際に使用される構造体です。- 新たに
embedded map[string]*typeDoc
フィールドが追加されました。これは、パッケージ内で見つかった埋め込み型(エクスポートされているかどうかにかかわらず)を名前でルックアップできるようにするためのマップです。
-
lookupEmbeddedDoc
関数の追加:docReader
にlookupEmbeddedDoc
という新しいヘルパー関数が追加されました。この関数は、与えられた型名に対応するtypeDoc
をdocReader.embedded
マップから検索し、存在しない場合は新しいtypeDoc
を作成してマップに追加します。これにより、埋め込み型が初めて検出されたときにその情報を登録できるようになります。
-
baseTypeName
関数の変更:baseTypeName
関数は、ASTノードから基本となる型名を抽出するために使用されます。- この関数に
allTypes
という新しいブール引数が追加されました。この引数がtrue
の場合、型がエクスポートされているかどうかにかかわらず、その名前を返します。これは、非エクスポートの匿名フィールドの型名も取得できるようにするために重要です。
-
addDecl
関数の変更:addDecl
関数は、パッケージ内の宣言(型宣言、変数宣言など)を処理し、docReader
に追加する役割を担います。- 型宣言を処理する部分で、構造体やインターフェースのフィールドを走査し、匿名フィールド(
len(field.Names) == 0
で識別)を検出するロジックが追加されました。 - 匿名フィールドが見つかった場合、
baseTypeName
をallTypes=true
で呼び出してその型名を取得し、lookupEmbeddedDoc
を使用して対応するtypeDoc
を取得します。 - 取得した
typeDoc
は、親の型のtypeDoc.embedded
リストに追加されます。
この変更により、go/doc
は、ソースコードを解析する際に、構造体やインターフェースがどのような型を匿名フィールドとして埋め込んでいるかを内部的に記録できるようになります。コミットメッセージにもあるように、現時点ではこれらの埋め込み型のメソッドが実際にドキュメントとして表示されるわけではありませんが、そのためのデータ収集の基盤が整えられました。
「stop-gap approach」という表現は、この変更が完全な解決策ではないことを示唆しています。特に、godoc
の動作モードによっては、go/doc
に渡されるASTが既に非エクスポートノードをフィルタリングしているため、非エクスポートの匿名フィールド自体が go/doc
から見えないという根本的な問題が残っています。この問題に対処するには、ASTフィルタリングのタイミングや方法を再検討する必要がある、とコミットメッセージは述べています。
コアとなるコードの変更箇所
src/pkg/go/doc/doc.go
ファイルにおける主要な変更箇所は以下の通りです。
typeDoc
構造体の変更
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -18,10 +18,11 @@ 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
- // values, factory functions, and methods associated with the type
+ // 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
構造体の変更
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -32,17 +33,19 @@ type typeDoc struct {
// printing the corresponding AST node).\n //
type docReader struct {
- doc *ast.CommentGroup // package documentation, if any
- pkgName string
- values []*ast.GenDecl // consts and vars
- types map[string]*typeDoc
- funcs map[string]*ast.FuncDecl
- bugs []*ast.CommentGroup
+ doc *ast.CommentGroup // package documentation, if any
+ pkgName string
+ values []*ast.GenDecl // consts and vars
+ types map[string]*typeDoc
+ embedded map[string]*typeDoc // embedded types, possibly not exported
+ funcs map[string]*ast.FuncDecl
+ bugs []*ast.CommentGroup
}
func (doc *docReader) init(pkgName string) {
doc.pkgName = pkgName
doc.types = make(map[string]*typeDoc)
+ doc.embedded = make(map[string]*typeDoc)
doc.funcs = make(map[string]*ast.FuncDecl)
}
lookupEmbeddedDoc
関数の追加
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -87,21 +84,35 @@ func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
\t\treturn tdoc
\t}\n \t// type wasn\'t found - add one without declaration
-\ttdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl)}\n+\ttdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl), nil}\n \tdoc.types[name] = tdoc\n \treturn tdoc\n }\n \n-func baseTypeName(typ ast.Expr) string {\n+func (doc *docReader) lookupEmbeddedDoc(name string) *typeDoc {\n+\tif name == "" {\n+\t\treturn nil\n+\t}\n+\tif tdoc, found := doc.embedded[name]; found {\n+\t\treturn tdoc\n+\t}\n+\t// type wasn\'t found - add one without declaration\n+\t// note: embedded types only have methods associated with them\n+\ttdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl), nil}\n+\tdoc.embedded[name] = tdoc\n+\treturn tdoc\n+}\n+\n+func baseTypeName(typ ast.Expr, allTypes bool) string {\n \tswitch t := typ.(type) {\n \tcase *ast.Ident:\n \t\t// if the type is not exported, the effect to\n \t\t// a client is as if there were no type name
-\t\tif t.IsExported() {\n+\t\tif t.IsExported() || allTypes {\n \t\t\treturn t.Name\n \t\t}\n \tcase *ast.StarExpr:\
-\t\treturn baseTypeName(t.X)\n+\t\treturn baseTypeName(t.X, allTypes)\n \t}\n \treturn ""\n }\
baseTypeName
関数の変更(引数追加)
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -99,10 +115,10 @@ func (doc *docReader) lookupEmbeddedDoc(name string) *typeDoc {
return tdoc
}
-func baseTypeName(typ ast.Expr) string {
+func baseTypeName(typ ast.Expr, allTypes bool) string {
switch t := typ.(type) {
case *ast.Ident:
// if the type is not exported, the effect to
// a client is as if there were no type name
- if t.IsExported() {
+ if t.IsExported() || allTypes {
return t.Name
}
case *ast.StarExpr:
- return baseTypeName(t.X)
+ return baseTypeName(t.X, allTypes)
}
return ""
}
addValue
, addFunc
関数の baseTypeName
呼び出しの変更
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -120,7 +131,7 @@ func (doc *docReader) addValue(decl *ast.GenDecl) {
\t\tswitch {\n \t\t\tcase v.Type != nil:\n \t\t\t\t// a type is present; determine its name
-\t\t\t\tname = baseTypeName(v.Type)\n+\t\t\t\tname = baseTypeName(v.Type, false)\n \t\t\tcase decl.Tok == token.CONST:\n \t\t\t\t// no type is present but we have a constant declaration;\n \t\t\t\t// use the previous type name (w/o more type information
@@ -178,7 +189,7 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) {
// determine if it should be associated with a type
if fun.Recv != nil {
// method
-\t\ttyp := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type))\n+\t\ttyp := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type, false))\n \t\tif typ != nil {\n \t\t\t// exported receiver type\n \t\t\tsetFunc(typ.methods, fun)\
@@ -199,7 +210,7 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) {
\t\t\t// exactly one (named or anonymous) result associated\n \t\t\t// with the first type in result signature (there may\n \t\t\t// be more than one result)
-\t\t\ttname := baseTypeName(res.Type)\n+\t\t\ttname := baseTypeName(res.Type, false)\n \t\t\ttyp := doc.lookupTypeDoc(tname)\n \t\t\tif typ != nil {\n \t\t\t\t// named and exported result type
addDecl
関数の変更(匿名フィールドの処理追加)
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -235,8 +246,30 @@ func (doc *docReader) addDecl(decl ast.Decl) {
\t\t\t\t\t// makeTypeDocs below). Simpler data structures, but\n \t\t\t\t\t// would lose GenDecl documentation if the TypeSpec\n \t\t\t\t\t// has documentation as well.
-\t\t\t\t\tdoc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})\n+\t\t\t\t\ttdoc := doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})\n \t\t\t\t\t// A new GenDecl node is created, no need to nil out d.Doc.\n+\t\t\t\t\tif tdoc == nil {\n+\t\t\t\t\t\tcontinue // some error happened; ignore\n+\t\t\t\t\t}\n+\t\t\t\t\tvar fields *ast.FieldList\n+\t\t\t\t\tswitch typ := spec.(*ast.TypeSpec).Type.(type) {\n+\t\t\t\t\tcase *ast.StructType:\n+\t\t\t\t\t\tfields = typ.Fields\n+\t\t\t\t\tcase *ast.InterfaceType:\n+\t\t\t\t\t\tfields = typ.Methods\n+\t\t\t\t\t}\n+\t\t\t\t\tif fields == nil {\n+\t\t\t\t\t\tfor _, field := range fields.List {\n+\t\t\t\t\t\t\tif len(field.Names) == 0 {\n+\t\t\t\t\t\t\t\t// anonymous field\n+\t\t\t\t\t\t\t\tname := baseTypeName(field.Type, true)\n+\t\t\t\t\t\t\t\tedoc := doc.lookupEmbeddedDoc(name)\n+\t\t\t\t\t\t\t\tif edoc != nil {\n+\t\t\t\t\t\t\t\t\ttdoc.embedded = append(tdoc.embedded, edoc)\n+\t\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n \t\t\t\t}\n \t\t\t}\n \t\t}\
TypeDoc
構造体の変更(Embedded
フィールド追加)
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -408,6 +441,7 @@ type TypeDoc struct {
Vars []*ValueDoc
Factories []*FuncDoc
Methods []*FuncDoc
+ Embedded []*FuncDoc
Decl *ast.GenDecl
order int
}
makeTypeDocs
関数の変更(TODOコメント追加)
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -452,6 +486,7 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
\t\t\tt.Vars = makeValueDocs(old.values, token.VAR)\n \t\t\t\tt.Factories = makeFuncDocs(old.factories)\n \t\t\t\tt.Methods = makeFuncDocs(old.methods)\n+\t\t\t// TODO(gri) compute list of embedded methods \n \t\t\t\tt.Decl = old.decl\n \t\t\t\tt.order = i\n \t\t\t\td[i] = t\
コアとなるコードの解説
typeDoc
構造体への embedded []*typeDoc
の追加
これは、ある型が匿名フィールドとして他の型を埋め込んでいる場合に、その埋め込まれた型の typeDoc
への参照を保持するためのリストです。これにより、go/doc
は型の埋め込み関係を内部的に追跡できるようになります。将来的には、このリストを使って埋め込み型のメソッドを収集し、ドキュメントに含めることが可能になります。
docReader
構造体への embedded map[string]*typeDoc
の追加と init
メソッドでの初期化
docReader
はパッケージ全体のドキュメンテーション収集を管理する構造体です。embedded
マップは、パッケージ内で見つかったすべての埋め込み型(エクスポートされているかどうかにかかわらず)を、その型名をキーとして typeDoc
にマッピングするために使用されます。これにより、go/doc
は埋め込み型を一元的に管理し、必要に応じてルックアップできるようになります。init
メソッドでの初期化は、マップが使用される前に適切に準備されることを保証します。
lookupEmbeddedDoc
関数の追加
この新しいヘルパー関数は、docReader.embedded
マップから特定の名前の埋め込み型に対応する typeDoc
を取得します。もしその名前の typeDoc
がまだマップに存在しない場合は、新しい typeDoc
を作成してマップに追加し、それを返します。これにより、埋め込み型が初めて検出されたときに、その情報を docReader
に登録するメカニズムが提供されます。
baseTypeName
関数の allTypes
引数の追加と使用箇所の変更
baseTypeName
関数は、ASTノードから型の基本名を抽出します。元の実装では、型がエクスポートされていない場合、空文字列を返していました。しかし、埋め込み型のメソッドを収集するためには、非エクスポートの匿名フィールドの型名も知る必要があります。
allTypes
引数が true
の場合、t.IsExported()
のチェックをスキップし、型がエクスポートされているかどうかにかかわらずその名前を返します。これにより、go/doc
は非公開の匿名フィールドの型名も取得できるようになり、その型に関連するメソッドを追跡する可能性が開かれます。
addValue
, addFunc
関数内での baseTypeName
の呼び出しでは、allTypes
を false
に設定しています。これは、これらのコンテキストでは通常、エクスポートされた型のみが関連するためです。
addDecl
関数における匿名フィールドの処理ロジックの追加
この変更は、型宣言(token.TYPE
)を処理する際に、構造体やインターフェースのフィールドを走査し、匿名フィールドを識別するものです。
spec.(*ast.TypeSpec).Type
から、それが*ast.StructType
または*ast.InterfaceType
であるかを判断し、それぞれのFields
またはMethods
を取得します。- 取得したフィールドリストをループし、
len(field.Names) == 0
で匿名フィールドを検出します。 - 匿名フィールドが見つかった場合、
baseTypeName(field.Type, true)
を呼び出して、その匿名フィールドの型名(エクスポートされているかどうかにかかわらず)を取得します。 - 取得した型名を使って
doc.lookupEmbeddedDoc(name)
を呼び出し、対応するtypeDoc
を取得します。 - 最後に、取得した
edoc
(embedded typeDoc) を、現在の型のtdoc.embedded
リストに追加します。
このロジックにより、go/doc
は、構造体やインターフェースがどのような型を匿名フィールドとして埋め込んでいるかを、ASTを解析する過程で正確に記録できるようになります。
TypeDoc
構造体への Embedded []*FuncDoc
の追加
これは、go/doc
パッケージが最終的に生成する公開APIの一部である TypeDoc
構造体への変更です。Embedded
フィールドは、その型が埋め込んでいる型のメソッドを保持するためのものです。現時点では、このフィールドは makeTypeDocs
関数内で // TODO(gri) compute list of embedded methods
というコメントと共に空のままですが、将来的に埋め込み型のメソッドが収集され、ここに格納されることを意図しています。
これらの変更は、go/doc
がGoの埋め込み型のセマンティクスをより深く理解し、最終的にそれらのメソッドをドキュメントに含めるための重要な内部的なステップです。
関連リンク
- Go Change-Id:
5500055
- このコミットに対応するGoの変更リスト(Gerrit)のIDです。
参考にした情報源リンク
- GitHub: golang/go commit 97853b46a08e984048e65f1d9c359bb48b8f22e4
- Go言語の埋め込み型に関する公式ドキュメントやチュートリアル (一般的なGoの知識として参照)
- GoのASTに関する公式ドキュメントやチュートリアル (一般的なGoの知識として参照)
go/doc
パッケージのドキュメント (一般的なGoの知識として参照)