[インデックス 11583] ファイルの概要
このコミットは、Go言語のドキュメンテーション生成ツールである go/doc
パッケージにおけるバグ修正です。具体的には、エクスポートされていない(非公開の)型に対するファクトリ関数が、生成されるドキュメントから失われてしまう問題を解決します。これにより、go/doc
がより正確で完全なドキュメントを生成できるようになります。
コミット
commit 212ba8076eb7f1c2efb00b83046da63f8ac75aba
Author: Robert Griesemer <gri@golang.org>
Date: Thu Feb 2 19:25:29 2012 -0800
go/doc: don't lose factory functions of non-exported types
Fixes #2824.
R=rsc, r
CC=golang-dev
https://golang.org/cl/5615043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/212ba8076eb7f1c2efb00b83046da63f8ac75aba
元コミット内容
go/doc
: エクスポートされていない型のファクトリ関数を失わないようにする。
Issue #2824 を修正。
変更の背景
Go言語の go/doc
ツールは、Goのソースコードからドキュメンテーションを自動生成する役割を担っています。このツールは、パッケージ、型、関数、メソッドなどの情報を抽出し、それらを構造化されたドキュメントとして出力します。
このコミット以前には、go/doc
がエクスポートされていない(つまり、パッケージ外部からは直接アクセスできない)型を返すファクトリ関数を適切に処理できないというバグが存在しました。具体的には、go/doc
は通常、エクスポートされたエンティティのみをドキュメントに含めますが、ファクトリ関数が非公開の型を生成する場合、そのファクトリ関数自体は公開されていても、その関連性がドキュメントから失われてしまうことがありました。
この問題は、go/doc
がファクトリ関数を関連付ける際に、その戻り値の型が「可視(visible)」であるかどうかを誤って判断していたことに起因します。結果として、ユーザーが go/doc
を使用して生成したドキュメントには、本来含まれるべきファクトリ関数が欠落し、コードベースの完全な理解を妨げる可能性がありました。このコミットは、この「ファクトリ関数が失われる」問題を修正し、go/doc
がより正確で包括的なドキュメントを生成できるようにすることを目的としています。コミットメッセージにある Fixes #2824
は、当時のGoプロジェクトの内部課題管理システムにおける特定のバグ報告に対応していることを示しています。
前提知識の解説
go/doc
パッケージ
go/doc
パッケージは、Goのソースコードからドキュメンテーションを抽出・生成するための標準ライブラリです。Goの公式ドキュメントサイト (pkg.go.dev) で表示されるドキュメントは、このパッケージによって生成されています。主な機能は以下の通りです。
- AST (Abstract Syntax Tree) の解析:
go/parser
パッケージなどを用いてGoのソースコードを解析し、ASTを構築します。 - ドキュメンテーションコメントの抽出: 関数、型、変数、定数などに関連付けられたドキュメンテーションコメント(
//
または/* ... */
で始まるコメント)を抽出します。 - エンティティの関連付け: メソッドをそのレシーバ型に関連付けたり、ファクトリ関数を生成する型に関連付けたりします。
- エクスポートされたエンティティの識別: Goの可視性ルール(大文字で始まる識別子はエクスポートされる)に基づいて、公開されるべきエンティティを識別します。
エクスポートされた型と非エクスポートされた型 (Exported vs. Non-exported Types)
Go言語では、識別子(変数名、関数名、型名など)の最初の文字が大文字であるか小文字であるかによって、その可視性(スコープ)が決定されます。
- エクスポートされた識別子 (Exported Identifiers): 最初の文字が大文字で始まる識別子(例:
MyType
,MyFunction
)は、そのパッケージの外部からアクセス可能です。これらは「公開」されたAPIの一部と見なされます。 - 非エクスポートされた識別子 (Non-exported Identifiers): 最初の文字が小文字で始まる識別子(例:
myType
,myFunction
)は、その識別子が宣言されたパッケージ内でのみアクセス可能です。これらは「非公開」であり、パッケージの内部実装の詳細と見なされます。
go/doc
は通常、エクスポートされたエンティティのみをドキュメントに含めますが、ファクトリ関数などの特定のケースでは、非エクスポートされた型との関連性も考慮する必要があります。
ファクトリ関数 (Factory Functions)
ファクトリ関数とは、特定の型の新しいインスタンスを生成して返す関数のことです。Goでは、コンストラクタの概念が明示的に存在しないため、慣習的に New
プレフィックスを持つ関数がファクトリ関数として使用されることが多いです(例: NewClient() *Client
)。
このコミットで問題となっていたのは、ファクトリ関数が非エクスポートされた型(例: func NewPrivate() private
)を返す場合です。ファクトリ関数自体はエクスポートされていても、その戻り値の型が非エクスポートであるために、go/doc
がそのファクトリ関数を適切にドキュメントに含めたり、関連付けたりできないという問題がありました。
技術的詳細
このコミットの技術的な核心は、go/doc
パッケージ内の reader
構造体がコードを解析し、関数と型を関連付けるロジックにあります。特に、ファクトリ関数をその戻り値の型に関連付ける際の「可視性」の判断基準が修正されました。
go/doc
は、ソースコードを読み込み、ASTを走査してドキュメント情報を収集します。このプロセスの中で、関数が特定の型に属するメソッドなのか、あるいは特定の型を生成するファクトリ関数なのかを判断します。
元の実装では、ファクトリ関数をその戻り値の型に関連付ける際、戻り値の型がエクスポートされているかどうか(ast.IsExported(name)
)のみをチェックしていました。しかし、go/doc
には AllDecls
というモードがあり、これはエクスポートされていない宣言もすべてドキュメントに含めることを指示するものです。この AllDecls
モードが有効な場合でも、ファクトリ関数の戻り値の型が非エクスポートであると、そのファクトリ関数がドキュメントから失われてしまうという問題がありました。
このバグは、reader
構造体の readFunc
メソッド内で、ファクトリ関数を処理する部分に存在していました。具体的には、戻り値の型が非エクスポートである場合に、そのファクトリ関数が関連付けの対象から外れてしまっていたのです。
修正は、この可視性チェックをより包括的に行うための isVisible
ヘルパー関数を導入し、既存の可視性チェックをこの新しい関数に置き換えることで行われました。isVisible
関数は、AllDecls
モードが有効であるか、または識別子がエクスポートされている場合に true
を返します。これにより、go/doc
は AllDecls
モードが有効な場合、非エクスポートの型を返すファクトリ関数も適切にドキュメントに含めることができるようになりました。
また、テストデータ (testdata/f.go
, testdata/f.0.golden
, testdata/f.1.golden
, testdata/f.2.golden
) が追加され、この修正が正しく機能することを確認しています。これらのテストケースは、非エクスポートの型を返すファクトリ関数が、go/doc
の異なるモード(例えば、AllDecls
が有効な場合とそうでない場合)でどのようにドキュメントされるべきかを示しています。
コアとなるコードの変更箇所
変更は主に src/pkg/go/doc/reader.go
ファイルに集中しています。
-
reader
構造体にisVisible
メソッドの追加:--- a/src/pkg/go/doc/reader.go +++ b/src/pkg/go/doc/reader.go @@ -154,6 +154,10 @@ type reader struct { funcs methodSet }\n +func (r *reader) isVisible(name string) bool { + return r.mode&AllDecls != 0 || ast.IsExported(name) +}\n + // lookupType returns the base type with the given name. // If the base type has not been encountered yet, a new // type with the given name but no associated declaration
-
readFunc
メソッド内のファクトリ関数処理ロジックの変更:--- a/src/pkg/go/doc/reader.go +++ b/src/pkg/go/doc/reader.go @@ -363,17 +367,16 @@ func (r *reader) readFunc(fun *ast.FuncDecl) { }\n \n -\t// perhaps a factory function -\t// determine result type, if any +\t// associate factory functions with the first visible result type, if any \tif fun.Type.Results.NumFields() >= 1 { \t\tres := fun.Type.Results.List[0] \t\tif len(res.Names) <= 1 {\n \t\t\t// exactly one (named or anonymous) result associated \t\t\t// with the first type in result signature (there may \t\t\t// be more than one result)\n -\t\t\tif n, imp := baseTypeName(res.Type); !imp {\n +\t\t\tif n, imp := baseTypeName(res.Type); !imp && r.isVisible(n) {\n \t\t\t\tif typ := r.lookupType(n); typ != nil {\n -\t\t\t\t\t// associate Func with typ +\t\t\t\t\t// associate function with typ \t\t\t\t\ttyp.funcs.set(fun)\n \t\t\t\t\treturn \t\t\t\t}\n
-
cleanupTypes
メソッド内の可視性チェックの変更:--- a/src/pkg/go/doc/reader.go +++ b/src/pkg/go/doc/reader.go @@ -580,7 +583,7 @@ func (r *reader) computeMethodSets() { // \n func (r *reader) cleanupTypes() { \tfor _, t := range r.types {\n -\t\tvisible := r.mode&AllDecls != 0 || ast.IsExported(t.name)\n +\t\tvisible := r.isVisible(t.name)\n \t\tif t.decl == nil && (predeclaredTypes[t.name] || t.isEmbedded && visible) {\n \t\t\t// t.name is a predeclared type (and was not redeclared in this package),\n \t\t\t// or it was embedded somewhere but its declaration is missing (because
コアとなるコードの解説
isVisible
メソッドの追加
reader
構造体に新しく追加された isVisible
メソッドは、与えられた識別子 name
が現在の go/doc
のモードにおいて「可視」であるべきかを判断します。
func (r *reader) isVisible(name string) bool {
return r.mode&AllDecls != 0 || ast.IsExported(name)
}
r.mode&AllDecls != 0
: これは、go/doc
がAllDecls
モードで実行されているかどうかをチェックします。AllDecls
モードは、エクスポートされていない宣言もすべてドキュメントに含めることを意味します。ast.IsExported(name)
: これは、Goのgo/ast
パッケージのヘルパー関数で、識別子name
がGoのルールに従ってエクスポートされている(つまり、大文字で始まる)かどうかをチェックします。
このメソッドは、「AllDecls
モードが有効である」か、または「識別子がエクスポートされている」のいずれかの条件が満たされれば true
を返します。これにより、ドキュメント生成の際に、非エクスポートの識別子であっても AllDecls
モードであれば適切に処理されるようになります。
readFunc
メソッド内の変更
readFunc
メソッドは、個々の関数宣言を読み込み、それを適切な型に関連付ける役割を担っています。変更された箇所は、ファクトリ関数をその戻り値の型に関連付けるロジックです。
元のコードでは、ファクトリ関数の戻り値の型 n
がインポートされた型でない場合に、単純に if n, imp := baseTypeName(res.Type); !imp {
という条件で処理を進めていました。この条件だけでは、n
が非エクスポートの型である場合に、そのファクトリ関数が typ.funcs.set(fun)
によって型に関連付けられるべきかどうかの判断が不十分でした。
修正後のコードでは、この条件に && r.isVisible(n)
が追加されました。
-\t\t\tif n, imp := baseTypeName(res.Type); !imp {\n
+\t\t\tif n, imp := baseTypeName(res.Type); !imp && r.isVisible(n) {\n
これにより、ファクトリ関数の戻り値の型 n
が、go/doc
の現在のモード(AllDecls
が有効か、または型がエクスポートされているか)において可視である場合にのみ、そのファクトリ関数が型に関連付けられるようになりました。この変更によって、AllDecls
モードが有効な場合、非エクスポートの型を返すファクトリ関数も正しくドキュメントに含められるようになります。
また、コメントもより正確な表現に修正されています。
-\t// perhaps a factory function
-\t// determine result type, if any
+\t// associate factory functions with the first visible result type, if any
そして、関連付けのコメントもより汎用的な表現に修正されました。
-\t\t\t\t\t// associate Func with typ
+\t\t\t\t\t// associate function with typ
cleanupTypes
メソッド内の変更
cleanupTypes
メソッドは、ドキュメント生成の最終段階で、不要な型情報をクリーンアップする役割を担っています。ここでも、型の可視性を判断するロジックが isVisible
メソッドを使用するように変更されました。
-\t\tvisible := r.mode&AllDecls != 0 || ast.IsExported(t.name)\n
+\t\tvisible := r.isVisible(t.name)\n
これにより、cleanupTypes
が型の可視性を判断する際にも、AllDecls
モードの考慮が統一的に行われるようになり、一貫性が保たれます。
これらの変更により、go/doc
は、エクスポートされていない型を返すファクトリ関数であっても、AllDecls
モードが有効な場合には適切にドキュメントに含め、関連付けることができるようになりました。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/5615043
参考にした情報源リンク
- コミットメッセージと差分 (
./commit_data/11583.txt
) - Go言語のドキュメンテーションに関する一般的な知識
- Go言語の可視性ルールに関する知識
- Go言語の
go/ast
パッケージに関する知識