[インデックス 11517] ファイルの概要
このコミットは、Go言語のドキュメンテーション生成ツールであるgo/doc
パッケージにおけるメソッド表示ロジックの改善と、AllMethods
フラグの有効化に関するものです。特に、埋め込み型(embedded types)のメソッドがどのようにドキュメントに表示されるかを制御するロジックが修正され、より正確なドキュメント生成が可能になりました。
コミット
commit 3c6bebf5a7b4a5678460abf2e48f21bc369e0d3a
Author: Robert Griesemer <gri@golang.org>
Date: Tue Jan 31 09:48:10 2012 -0800
go/doc: enable AllMethods flag (and fix logic)
- enable AllMethods flag (default: not set)
- fix logic determining which methods to show
- added respective test case in testdata/e.go for AllMethods = false
- added test case set for AllMethods = true
The critical changes/files to look at are:
- testdata/e{0,1,2}.golden: T4.M should only show up as method of T5 in e2.golden
- reader.go: always include top-level methods, and negate former logic for embedded methods
(rewrote as a switch for better comprehensability)
Fixes #2791.
R=rsc, rsc
CC=golang-dev
https://golang.org/cl/5576057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3c6bebf5a7b4a5678460abf2e48f21bc369e0d3a
元コミット内容
go/doc: enable AllMethods flag (and fix logic)
AllMethods
フラグを有効化(デフォルトでは設定されていなかった)- 表示するメソッドを決定するロジックを修正
AllMethods = false
の場合のテストケースをtestdata/e.go
に追加AllMethods = true
の場合のテストケースセットを追加
重要な変更ファイル:
testdata/e{0,1,2}.golden
:T4.M
はe2.golden
ではT5
のメソッドとしてのみ表示されるべきreader.go
: 常にトップレベルのメソッドを含め、埋め込みメソッドの以前のロジックを反転(理解しやすくするためにswitch
文に書き換え)
Fixes #2791.
変更の背景
Go言語のドキュメンテーションツールであるgo/doc
パッケージは、Goのソースコードからドキュメントを生成する際に、型に紐づくメソッド(レシーバーを持つ関数)をどのように表示するかを決定します。特に、Goの構造体埋め込み(struct embedding)の機能を使うと、ある構造体が別の構造体のフィールドとして埋め込まれ、埋め込まれた構造体のメソッドが外側の構造体のメソッドセットに昇格(promote)されることがあります。
このコミット以前は、go/doc
パッケージがメソッドセットを処理するロジックに不整合があり、特に埋め込み型から昇格されるメソッドの表示が意図通りに行われないケースがありました。具体的には、AllMethods
というフラグが存在していたにもかかわらず、それがデフォルトで有効になっており、ユーザーがその挙動を制御できない状態でした。また、メソッドの衝突解決や、エクスポートされていない型に紐づくメソッドの扱いなど、メソッド表示に関するロジック自体にも改善の余地がありました。
このコミットの目的は、以下の点を解決することです。
AllMethods
フラグを適切に機能させ、ユーザーがドキュメントに表示するメソッドの範囲を制御できるようにする。- 埋め込み型から昇格されるメソッドの表示ロジックを修正し、Goのメソッドセットのルールに厳密に従った正確なドキュメントを生成する。
- 特に、エクスポートされていない型に紐づくメソッドや、メソッドの衝突が発生した場合の挙動を明確にする。
- 関連するテストケースを追加・修正し、これらの変更が正しく機能することを保証する。
これにより、go/doc
が生成するドキュメントの正確性と柔軟性が向上し、開発者がより信頼性の高いAPIドキュメントを得られるようになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とgo/doc
パッケージの基本的な知識が必要です。
1. Go言語のメソッドとレシーバー
Go言語では、関数は特定の型に関連付けることができます。これを「メソッド」と呼びます。メソッドは、関数名の前にレシーバー引数を指定することで定義されます。
例: func (t MyType) MyMethod() {}
または func (t *MyType) MyMethod() {}
2. Go言語のメソッドセット
各型は「メソッドセット」を持ちます。これは、その型が持つすべてのメソッドの集合です。
- 値レシーバーのメソッド (
func (t T) M()
) は、型T
と型*T
の両方のメソッドセットに含まれます。 - ポインタレシーバーのメソッド (
func (t *T) M()
) は、型*T
のメソッドセットにのみ含まれます。ただし、型T
がアドレス可能(addressable)な場合、コンパイラは自動的に&T
に変換してポインタレシーバーのメソッドを呼び出すことができます。
3. 構造体の埋め込み(Struct Embedding)
Go言語の構造体は、他の構造体やインターフェースを匿名フィールドとして埋め込むことができます。これにより、埋め込まれた型のメソッドが外側の構造体のメソッドセットに「昇格」されます。 例:
type Inner struct {
Name string
}
func (i Inner) Greet() string {
return "Hello, " + i.Name
}
type Outer struct {
Inner // Inner型を埋め込み
Age int
}
// Outer型のインスタンスからInnerのGreetメソッドを直接呼び出せる
o := Outer{Inner: Inner{Name: "World"}}
fmt.Println(o.Greet()) // "Hello, World"
この場合、Outer
型のメソッドセットにはGreet
メソッドが含まれます。
4. メソッドの衝突とシャドーイング
埋め込みによって複数のメソッドが同じ名前で昇格される場合、または外側の構造体が埋め込まれた構造体と同じ名前のメソッドを定義している場合、Goのルールに従ってメソッドの衝突が解決されます。一般的に、より「近い」メソッド(外側の構造体で直接定義されたメソッド)が優先され、埋め込みによって昇格されるメソッドをシャドーイング(隠蔽)します。
5. go/doc
パッケージ
go/doc
パッケージは、Goのソースコードを解析し、そのドキュメンテーションを生成するためのツールです。Goの標準ライブラリのドキュメント(pkg.go.devなどで見られるもの)もこのパッケージによって生成されています。
ast
パッケージ(抽象構文木)を使用してソースコードを解析します。- 型、関数、定数、変数、そしてそれらに紐づくメソッドなどの情報を抽出し、構造化されたドキュメントデータとして提供します。
- このパッケージには、ドキュメント生成の挙動を制御するための
Mode
フラグ(例:AllDecls
,AllMethods
)があります。
6. ast
パッケージ
go/ast
パッケージは、Goのソースコードを抽象構文木(Abstract Syntax Tree, AST)として表現するためのデータ構造と関数を提供します。go/doc
パッケージは、このASTを走査してコードの構造や要素を理解し、ドキュメント情報を抽出します。
技術的詳細
このコミットの技術的な核心は、go/doc
パッケージがメソッドセットを構築し、ドキュメントに表示するメソッドを選択するロジックの改善にあります。特に、AllMethods
フラグの挙動と、埋め込み型から昇格されるメソッドの扱いが変更されました。
AllMethods
フラグの役割
go/doc.New
関数に渡されるMode
引数の一部としてAllMethods
フラグがあります。
AllMethods
がfalse
(デフォルト)の場合: ドキュメントには、エクスポートされた型に紐づくエクスポートされたメソッドのみが表示されます。埋め込みによって昇格されたメソッドも、その元の型がエクスポートされており、かつメソッド自体がエクスポートされている場合にのみ表示されます。また、衝突するメソッドは表示されません。AllMethods
がtrue
の場合: ドキュメントには、エクスポートされた型に紐づくすべてのメソッド(エクスポートされているか否かに関わらず)が表示されます。また、埋め込みによって昇格されたメソッドも、その元の型がエクスポートされているか否かに関わらず、すべて表示されます。これにより、内部的なメソッドや、通常は隠蔽されるメソッドもドキュメントに含めることが可能になります。
このコミットでは、以前はdoc.go
内でmode |= AllMethods
と強制的にAllMethods
が有効になっていた部分が削除され、このフラグがユーザーの指定通りに機能するようになりました。
reader.go
におけるメソッド選択ロジックの修正
最も重要な変更は、src/pkg/go/doc/reader.go
内のsortedFuncs
関数にあります。この関数は、特定の型のメソッドセットをソートして返す役割を担っています。以前のロジックは、埋め込みメソッドの表示に関して誤った挙動を示すことがありました。
変更前は、メソッドをリストに含めるかどうかの条件がm.Decl != nil && (allMethods || ast.IsExported(removeStar(m.Orig)))
でした。これは、衝突エントリを除外し、AllMethods
が有効な場合、または元のレシーバー型がエクスポートされている場合にメソッドを含めるという意図でした。
変更後、このロジックはswitch
文に書き換えられ、より明確かつ正確になりました。
switch {
case m.Decl == nil:
// exclude conflict entry
case allMethods, m.Level == 0, !ast.IsExported(removeStar(m.Orig)):
// forced inclusion, method not embedded, or method
// embedded but original receiver type not exported
list[i] = m
i++
}
この新しいswitch
文の条件は以下の通りです。
m.Decl == nil
: これはメソッドの衝突エントリ(conflict entry)を意味します。Goのメソッドセットの構築過程で、同じ名前のメソッドが複数存在する場合に発生し、これらのエントリはドキュメントに表示すべきではないため、除外されます。allMethods
:AllMethods
フラグがtrue
の場合、すべてのメソッド(エクスポートされているか否か、埋め込みレベルに関わらず)が強制的に含まれます。m.Level == 0
: これはトップレベルのメソッド(埋め込みによって昇格されたものではない、直接型に定義されたメソッド)を意味します。トップレベルのメソッドは常にドキュメントに含まれるべきです。!ast.IsExported(removeStar(m.Orig))
: これは、埋め込みによって昇格されたメソッドの場合に特に重要です。m.Orig
はメソッドが元々定義されていたレシーバーの型を表します。この条件は、「埋め込みによって昇格されたメソッドだが、その元のレシーバー型がエクスポートされていない場合」を意味します。以前のロジックでは、エクスポートされていない型から昇格されたメソッドは表示されない可能性がありましたが、この変更により、AllMethods
がfalse
の場合でも、エクスポートされていない型から昇格されたメソッドが適切に扱われるようになりました。特に、AllMethods
がfalse
の場合、エクスポートされた型に紐づくエクスポートされたメソッドのみが表示されるという原則がより厳密に適用されます。
この修正により、go/doc
はGoのメソッドセットのルール、特に埋め込みとエクスポートの概念をより正確に反映したドキュメントを生成できるようになりました。
テストケースの追加
この変更の正しさを検証するために、testdata/e.go
とそのゴールデンファイル(e.0.golden
, e.1.golden
, e.2.golden
)が追加・修正されました。これらのテストケースは、特に埋め込み型とメソッドの可視性に関する複雑なシナリオをカバーしており、AllMethods
フラグの挙動が期待通りであることを確認します。
e.0.golden
:AllMethods = false
の場合の期待される出力。e.2.golden
:AllMethods = true
の場合の期待される出力。
特に、T4.M
がT5
に埋め込まれている場合に、AllMethods
の設定によってどのように表示が変化するかが検証されています。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/cmd/api/goapi.go
:--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -264,7 +264,7 @@ func (w *Walker) WalkPackage(name string) { // (functions and methods). This is done here because // go/doc is destructive. We can't use the // *ast.Package after this. - dpkg := doc.New(apkg, name, 0) + dpkg := doc.New(apkg, name, doc.AllMethods) for _, t := range dpkg.Types { // Move funcs up to the top-level, not hiding in the Types.
goapi.go
はGoのAPIを生成するツールの一部であり、go/doc
パッケージを利用しています。ここでは、doc.New
を呼び出す際に、明示的にdoc.AllMethods
フラグを渡すように変更されています。これは、APIドキュメント生成においてはすべてのメソッドを表示することが望ましいという判断に基づいています。 -
src/pkg/go/doc/doc.go
:--- a/src/pkg/go/doc/doc.go +++ b/src/pkg/go/doc/doc.go @@ -78,7 +78,6 @@ const ( // New takes ownership of the AST pkg and may edit or overwrite it. // func New(pkg *ast.Package, importPath string, mode Mode) *Package { - mode |= AllMethods // TODO(gri) remove this to enable flag var r reader r.readPackage(pkg, mode) r.computeMethodSets()
doc.go
では、New
関数内で強制的にAllMethods
フラグを有効にしていた行が削除されました。これにより、New
関数に渡されるmode
引数(ユーザーが指定するフラグ)が尊重されるようになり、AllMethods
フラグが意図通りに機能するようになりました。 -
src/pkg/go/doc/doc_test.go
:--- a/src/pkg/go/doc/doc_test.go +++ b/src/pkg/go/doc/doc_test.go @@ -118,4 +118,5 @@ func test(t *testing.T, mode Mode) { func Test(t *testing.T) { test(t, 0) test(t, AllDecls) + test(t, AllMethods) }
テストファイルでは、
Test
関数にAllMethods
フラグを渡してtest
関数を呼び出す行が追加されました。これにより、AllMethods
が有効な場合のドキュメント生成がテストされるようになりました。 -
src/pkg/go/doc/reader.go
:--- a/src/pkg/go/doc/reader.go +++ b/src/pkg/go/doc/reader.go @@ -708,8 +708,13 @@ func sortedFuncs(m methodSet, allMethods bool) []*Func { list := make([]*Func, len(m)) i := 0 for _, m := range m { - // exclude conflict entries - if m.Decl != nil && (allMethods || ast.IsExported(removeStar(m.Orig))) { + // determine which methods to include + switch { + case m.Decl == nil: + // exclude conflict entry + case allMethods, m.Level == 0, !ast.IsExported(removeStar(m.Orig)): + // forced inclusion, method not embedded, or method + // embedded but original receiver type not exported list[i] = m i++ }
これが最も重要な変更箇所です。
sortedFuncs
関数内のメソッド選択ロジックが、if
文からswitch
文に書き換えられました。この新しいswitch
文は、メソッドをドキュメントに含めるべきかどうかをより正確に判断します。特に、メソッドの衝突、AllMethods
フラグの状態、メソッドがトップレベルであるか、そして埋め込みメソッドの元のレシーバー型がエクスポートされているかどうかに基づいて判断します。 -
src/pkg/go/doc/testdata/e.go
および関連する.golden
ファイル: これらのファイルは、AllMethods
フラグの挙動と埋め込みメソッドの表示ロジックを検証するための新しいテストケースと期待される出力(ゴールデンファイル)を含んでいます。特に、T4
とT5
という構造体とそれらのメソッドM
の定義が追加され、T4
がT5
に埋め込まれている場合のM
の表示がAllMethods
フラグによってどのように変化するかがテストされています。
コアとなるコードの解説
このコミットの核心は、go/doc
パッケージがGoの型に紐づくメソッドをどのように抽出し、ドキュメントに表示するかというロジックの改善にあります。特に、src/pkg/go/doc/reader.go
のsortedFuncs
関数における変更が重要です。
src/pkg/go/doc/doc.go
の変更
以前のdoc.go
のNew
関数では、以下のようにAllMethods
フラグが強制的に有効になっていました。
func New(pkg *ast.Package, importPath string, mode Mode) *Package {
mode |= AllMethods // TODO(gri) remove this to enable flag
// ...
}
この行が削除されたことで、New
関数に渡されるmode
引数(つまり、go/doc
パッケージの利用者が指定するフラグ)がそのまま尊重されるようになりました。これにより、AllMethods
フラグが実際にユーザーの意図通りにドキュメント生成の挙動を制御できるようになりました。TODO
コメントが示唆するように、これは元々一時的な措置であり、このコミットで正式にフラグが機能するようになったことを意味します。
src/pkg/go/doc/reader.go
の変更
reader.go
のsortedFuncs
関数は、特定の型に属するメソッドのリストを生成し、ソートして返します。この関数は、go/doc
がドキュメントを生成する際に、どのメソッドを表示するかを決定する中心的なロジックを含んでいます。
変更前のロジックは以下のようになっていました。
for _, m := range m {
// exclude conflict entries
if m.Decl != nil && (allMethods || ast.IsExported(removeStar(m.Orig))) {
list[i] = m
i++
}
}
このif
文は、メソッドm
をリストに含める条件を定義していました。
m.Decl != nil
: これは、メソッドが衝突エントリではないことを確認します。m.Decl
がnil
の場合、それは同じ名前のメソッドが複数存在し、Goのルールによって隠蔽された(シャドーイングされた)メソッドであることを示します。これらのメソッドは通常、ドキュメントには表示されません。allMethods
:AllMethods
フラグがtrue
の場合、この条件は常に真となり、衝突エントリでない限りすべてのメソッドが含まれます。ast.IsExported(removeStar(m.Orig))
:AllMethods
がfalse
の場合に評価される条件です。m.Orig
はメソッドが元々定義されていたレシーバーの型を表します。removeStar
はポインタ型の場合に*
を取り除きます。この条件は、「メソッドがエクスポートされた型に紐づいているか」をチェックしていました。
このロジックは、特に埋め込み型から昇格されるメソッドの扱いで問題がありました。エクスポートされていない型に埋め込まれたエクスポートされたメソッドが、意図せず表示されない、またはその逆のケースが発生する可能性がありました。
変更後のロジックは、より堅牢なswitch
文に書き換えられました。
for _, m := range m {
// determine which methods to include
switch {
case m.Decl == nil:
// exclude conflict entry
case allMethods, m.Level == 0, !ast.IsExported(removeStar(m.Orig)):
// forced inclusion, method not embedded, or method
// embedded but original receiver type not exported
list[i] = m
i++
}
}
このswitch
文の各case
は、メソッドを含めるための異なる条件を表しています。
-
case m.Decl == nil:
- これは以前のロジックと同じく、メソッドの衝突エントリを除外します。
m.Decl
がnil
のメソッドは、Goのメソッドセットの解決ルールによって隠蔽されたメソッドであり、ドキュメントには表示すべきではありません。
- これは以前のロジックと同じく、メソッドの衝突エントリを除外します。
-
case allMethods, m.Level == 0, !ast.IsExported(removeStar(m.Orig)):
- この
case
は、論理OR (||
) で結合された3つの条件のいずれかが真であれば、メソッドをリストに含めます。 allMethods
:AllMethods
フラグがtrue
の場合、衝突エントリでない限り、すべてのメソッドが強制的に含まれます。これは、詳細なAPIドキュメントを生成したい場合に有用です。m.Level == 0
:m.Level
は、メソッドが型に直接定義されている(トップレベルのメソッド)か、埋め込みによって昇格されたかを示します。m.Level == 0
はトップレベルのメソッドであることを意味し、これらのメソッドは常にドキュメントに含まれるべきです。!ast.IsExported(removeStar(m.Orig))
: この条件は、埋め込みメソッドの扱いに特化しています。m.Orig
はメソッドが元々定義されていたレシーバーの型です。!ast.IsExported(removeStar(m.Orig))
は、「元のレシーバー型がエクスポートされていない」ことを意味します。AllMethods
がfalse
の場合、通常はエクスポートされた型のエクスポートされたメソッドのみが表示されます。しかし、Goのメソッドセットのルールでは、エクスポートされていない型に埋め込まれたエクスポートされたメソッドも、外側の型がエクスポートされていれば昇格されます。この条件は、そのようなケースでメソッドが適切にドキュメントに含まれるように調整します。- 以前のロジックでは、
ast.IsExported(removeStar(m.Orig))
がfalse
の場合(つまり、元の型がエクスポートされていない場合)、そのメソッドは含まれませんでした。しかし、新しいロジックでは、!ast.IsExported(removeStar(m.Orig))
がtrue
の場合(元の型がエクスポートされていない場合)に、この条件が真となり、メソッドが含まれる可能性があります。これは、AllMethods
がfalse
であっても、特定の埋め込みシナリオでメソッドが正しく表示されるようにするための重要な調整です。
- この
この新しいswitch
文は、Goのメソッドセットの複雑なルール、特に埋め込みとエクスポートの相互作用をより正確にモデル化し、go/doc
が生成するドキュメントの正確性を大幅に向上させました。
関連リンク
- Go言語のドキュメンテーション: https://go.dev/doc/
go/doc
パッケージのドキュメンテーション: https://pkg.go.dev/go/doc- Go言語のメソッドセットに関する公式ブログ記事 (英語): https://go.dev/blog/go-and-generics (メソッドセットの概念が説明されています)
- Go言語の構造体埋め込みに関する公式ドキュメント (英語): https://go.dev/tour/methods/10
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/3c6bebf5a7b4a5678460abf2e48f21bc369e0d3a
- Go言語のIssue #2791: https://github.com/golang/go/issues/2791 (このコミットが修正した問題)
- Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/5576057
- Go言語の
ast
パッケージのドキュメンテーション: https://pkg.go.dev/go/ast - Go言語の仕様 (メソッドセットに関するセクション): https://go.dev/ref/spec#Method_sets