Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.Me2.goldenではT5のメソッドとしてのみ表示されるべき
  • reader.go: 常にトップレベルのメソッドを含め、埋め込みメソッドの以前のロジックを反転(理解しやすくするためにswitch文に書き換え)

Fixes #2791.

変更の背景

Go言語のドキュメンテーションツールであるgo/docパッケージは、Goのソースコードからドキュメントを生成する際に、型に紐づくメソッド(レシーバーを持つ関数)をどのように表示するかを決定します。特に、Goの構造体埋め込み(struct embedding)の機能を使うと、ある構造体が別の構造体のフィールドとして埋め込まれ、埋め込まれた構造体のメソッドが外側の構造体のメソッドセットに昇格(promote)されることがあります。

このコミット以前は、go/docパッケージがメソッドセットを処理するロジックに不整合があり、特に埋め込み型から昇格されるメソッドの表示が意図通りに行われないケースがありました。具体的には、AllMethodsというフラグが存在していたにもかかわらず、それがデフォルトで有効になっており、ユーザーがその挙動を制御できない状態でした。また、メソッドの衝突解決や、エクスポートされていない型に紐づくメソッドの扱いなど、メソッド表示に関するロジック自体にも改善の余地がありました。

このコミットの目的は、以下の点を解決することです。

  1. AllMethodsフラグを適切に機能させ、ユーザーがドキュメントに表示するメソッドの範囲を制御できるようにする。
  2. 埋め込み型から昇格されるメソッドの表示ロジックを修正し、Goのメソッドセットのルールに厳密に従った正確なドキュメントを生成する。
  3. 特に、エクスポートされていない型に紐づくメソッドや、メソッドの衝突が発生した場合の挙動を明確にする。
  4. 関連するテストケースを追加・修正し、これらの変更が正しく機能することを保証する。

これにより、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フラグがあります。

  • AllMethodsfalse(デフォルト)の場合: ドキュメントには、エクスポートされた型に紐づくエクスポートされたメソッドのみが表示されます。埋め込みによって昇格されたメソッドも、その元の型がエクスポートされており、かつメソッド自体がエクスポートされている場合にのみ表示されます。また、衝突するメソッドは表示されません。
  • AllMethodstrueの場合: ドキュメントには、エクスポートされた型に紐づくすべてのメソッド(エクスポートされているか否かに関わらず)が表示されます。また、埋め込みによって昇格されたメソッドも、その元の型がエクスポートされているか否かに関わらず、すべて表示されます。これにより、内部的なメソッドや、通常は隠蔽されるメソッドもドキュメントに含めることが可能になります。

このコミットでは、以前は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文の条件は以下の通りです。

  1. m.Decl == nil: これはメソッドの衝突エントリ(conflict entry)を意味します。Goのメソッドセットの構築過程で、同じ名前のメソッドが複数存在する場合に発生し、これらのエントリはドキュメントに表示すべきではないため、除外されます。
  2. allMethods: AllMethodsフラグがtrueの場合、すべてのメソッド(エクスポートされているか否か、埋め込みレベルに関わらず)が強制的に含まれます。
  3. m.Level == 0: これはトップレベルのメソッド(埋め込みによって昇格されたものではない、直接型に定義されたメソッド)を意味します。トップレベルのメソッドは常にドキュメントに含まれるべきです。
  4. !ast.IsExported(removeStar(m.Orig)): これは、埋め込みによって昇格されたメソッドの場合に特に重要です。m.Origはメソッドが元々定義されていたレシーバーの型を表します。この条件は、「埋め込みによって昇格されたメソッドだが、その元のレシーバー型がエクスポートされていない場合」を意味します。以前のロジックでは、エクスポートされていない型から昇格されたメソッドは表示されない可能性がありましたが、この変更により、AllMethodsfalseの場合でも、エクスポートされていない型から昇格されたメソッドが適切に扱われるようになりました。特に、AllMethodsfalseの場合、エクスポートされた型に紐づくエクスポートされたメソッドのみが表示されるという原則がより厳密に適用されます。

この修正により、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.MT5に埋め込まれている場合に、AllMethodsの設定によってどのように表示が変化するかが検証されています。

コアとなるコードの変更箇所

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. 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ドキュメント生成においてはすべてのメソッドを表示することが望ましいという判断に基づいています。

  2. 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フラグが意図通りに機能するようになりました。

  3. 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が有効な場合のドキュメント生成がテストされるようになりました。

  4. 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フラグの状態、メソッドがトップレベルであるか、そして埋め込みメソッドの元のレシーバー型がエクスポートされているかどうかに基づいて判断します。

  5. src/pkg/go/doc/testdata/e.go および関連する.goldenファイル: これらのファイルは、AllMethodsフラグの挙動と埋め込みメソッドの表示ロジックを検証するための新しいテストケースと期待される出力(ゴールデンファイル)を含んでいます。特に、T4T5という構造体とそれらのメソッドMの定義が追加され、T4T5に埋め込まれている場合のMの表示がAllMethodsフラグによってどのように変化するかがテストされています。

コアとなるコードの解説

このコミットの核心は、go/docパッケージがGoの型に紐づくメソッドをどのように抽出し、ドキュメントに表示するかというロジックの改善にあります。特に、src/pkg/go/doc/reader.gosortedFuncs関数における変更が重要です。

src/pkg/go/doc/doc.go の変更

以前のdoc.goNew関数では、以下のように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.gosortedFuncs関数は、特定の型に属するメソッドのリストを生成し、ソートして返します。この関数は、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.Declnilの場合、それは同じ名前のメソッドが複数存在し、Goのルールによって隠蔽された(シャドーイングされた)メソッドであることを示します。これらのメソッドは通常、ドキュメントには表示されません。
  • allMethods: AllMethodsフラグがtrueの場合、この条件は常に真となり、衝突エントリでない限りすべてのメソッドが含まれます。
  • ast.IsExported(removeStar(m.Orig)): AllMethodsfalseの場合に評価される条件です。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は、メソッドを含めるための異なる条件を表しています。

  1. case m.Decl == nil:

    • これは以前のロジックと同じく、メソッドの衝突エントリを除外します。m.Declnilのメソッドは、Goのメソッドセットの解決ルールによって隠蔽されたメソッドであり、ドキュメントには表示すべきではありません。
  2. 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))は、「元のレシーバー型がエクスポートされていない」ことを意味します。
      • AllMethodsfalseの場合、通常はエクスポートされた型のエクスポートされたメソッドのみが表示されます。しかし、Goのメソッドセットのルールでは、エクスポートされていない型に埋め込まれたエクスポートされたメソッドも、外側の型がエクスポートされていれば昇格されます。この条件は、そのようなケースでメソッドが適切にドキュメントに含まれるように調整します。
      • 以前のロジックでは、ast.IsExported(removeStar(m.Orig))falseの場合(つまり、元の型がエクスポートされていない場合)、そのメソッドは含まれませんでした。しかし、新しいロジックでは、!ast.IsExported(removeStar(m.Orig))trueの場合(元の型がエクスポートされていない場合)に、この条件が真となり、メソッドが含まれる可能性があります。これは、AllMethodsfalseであっても、特定の埋め込みシナリオでメソッドが正しく表示されるようにするための重要な調整です。

この新しいswitch文は、Goのメソッドセットの複雑なルール、特に埋め込みとエクスポートの相互作用をより正確にモデル化し、go/docが生成するドキュメントの正確性を大幅に向上させました。

関連リンク

参考にした情報源リンク