[インデックス 14699] ファイルの概要
このコミットは、Go言語のドキュメンテーションツール go/doc
における、自己完結型ではない(つまり、外部の宣言に依存している)サンプルコードに対して、実行可能なコードを合成しないようにする変更です。これにより、不完全なサンプルコードが誤って実行可能なものとして扱われることを防ぎ、ドキュメンテーションの正確性を向上させます。
コミット
commit ff27cdb625c7870d3a0b846dae70fc339b021b62
Author: Andrew Gerrand <adg@golang.org>
Date: Fri Dec 21 07:06:38 2012 +1100
go/doc: don't synthesize code for examples that are not self-contained
Fixes #4309.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6974045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ff27cdb625c7870d3a0b846dae70fc339b021b62
元コミット内容
go/doc: don't synthesize code for examples that are not self-contained
このコミットは、go/doc
パッケージが、自己完結型ではない(外部のトップレベル宣言に依存している)Goのサンプルコードに対して、実行可能なコードを合成しないように修正します。これにより、go/doc
が生成するドキュメンテーション内のサンプルコードの正確性と信頼性が向上します。
変更の背景
Go言語のドキュメンテーションツール go/doc
は、パッケージのドキュメントを生成する際に、Example
関数として記述されたコードスニペットを抽出し、それを実行可能な形式に変換して表示する機能を持っています。これは、ユーザーがドキュメントを読みながら、実際にコードがどのように動作するかを試せるようにするための非常に便利な機能です。
しかし、以前の実装では、Example
関数内のコードが、そのファイル内で定義されているトップレベルの変数、関数、型などに依存している場合でも、go/doc
はその依存関係を適切に処理せず、単にコードスニペットを抽出して実行可能な形式に合成しようとしていました。この結果、自己完結型ではないサンプルコードが、go/doc
によって生成されたドキュメント上でコンパイルエラーや実行時エラーを引き起こす可能性がありました。
この問題は、Issue #4309 として報告されており、このコミットはその問題を解決するために導入されました。具体的には、サンプルコードがファイル内の他のトップレベル宣言を使用している場合、go/doc
はそのサンプルコードを実行可能な形式に合成するのをやめ、代わりにそのサンプルコードが自己完結型ではないことを示すようにします(または、単に合成をスキップします)。これにより、ユーザーはドキュメント上で不完全なサンプルコードに遭遇することがなくなります。
前提知識の解説
go/doc
パッケージ: Go言語の標準ライブラリの一部であり、Goのソースコードからドキュメンテーションを生成するためのツールを提供します。特に、go doc
コマンドやgodoc
サーバーのバックエンドとして機能します。Example
関数: Goのパッケージドキュメンテーションにおいて、コードの利用例を示すために使用される特別な関数です。func ExampleF()
のような命名規則に従い、go test
コマンドによってテストとして実行され、その出力がドキュメンテーションに表示されます。go/ast
パッケージ (Abstract Syntax Tree): Goのソースコードを抽象構文木(AST)として表現するためのデータ構造と関数を提供します。コンパイラやコード分析ツールがGoのコードを解析する際に使用されます。このコミットでは、ast.Inspect
を使用してASTを走査し、識別子の使用状況を分析しています。- 自己完結型 (Self-contained): プログラムやコードスニペットが、外部の依存関係(この場合は同じファイル内の他のトップレベル宣言)なしに単独でコンパイルおよび実行できる状態を指します。
- 識別子 (Identifier): プログラム内で変数、関数、型などを識別するために使用される名前です。
ast.Object
: AST内で宣言されたエンティティ(変数、関数、型など)を表すオブジェクトです。ast.Ident
のObj
フィールドを通じて、その識別子が参照している宣言にアクセスできます。
技術的詳細
このコミットの主要な変更は、src/pkg/go/doc/example.go
ファイル内の playExample
関数にあります。この関数は、Example
関数から実行可能なコードを合成する役割を担っています。
変更の核心は、サンプルコードのボディ(ast.BlockStmt
)を走査し、その中で使用されている識別子が、そのサンプルコードが属するファイルのトップレベルで宣言されている他のオブジェクトを参照しているかどうかを検出するロジックの追加です。
具体的には、以下のステップが導入されています。
- トップレベル宣言の収集: まず、
file.Decls
を走査して、現在のファイル内で宣言されているすべてのトップレベルの関数、型、変数をtopDecls
マップに収集します。これにより、どのast.Object
がトップレベル宣言であるかを効率的に判断できるようになります。 - 未解決の識別子とトップレベル宣言の使用の検出:
ast.Inspect
を使用して、Example
関数のボディ(body *ast.BlockStmt
)を再帰的に走査します。- 以前と同様に、
id.Obj == nil
である識別子(つまり、未解決の識別子)はunresolved
マップに記録されます。 - 新しく追加されたロジックとして、
id.Obj != nil
であり、かつそのid.Obj
がtopDecls
マップに存在する場合(つまり、サンプルコードがファイル内の他のトップレベル宣言を使用している場合)、usesTopDecl
フラグをtrue
に設定します。
- 以前と同様に、
- 自己完結性のチェック:
ast.Inspect
の走査が完了した後、usesTopDecl
がtrue
であれば、そのサンプルコードは自己完結型ではないと判断されます。この場合、playExample
関数はnil
を返し、実行可能なコードの合成をスキップします。これにより、不完全なサンプルコードがドキュメントに表示されるのを防ぎます。
この変更により、go/doc
はより賢明になり、自己完結型ではないサンプルコードに対して不適切なコード合成を行わなくなります。
コアとなるコードの変更箇所
src/pkg/go/doc/example.go
ファイルの playExample
関数に以下の変更が加えられています。
--- a/src/pkg/go/doc/example.go
+++ b/src/pkg/go/doc/example.go
@@ -119,8 +119,29 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
return nil
}
- // Find unresolved identifiers
+ // Find top-level declarations in the file.
+ topDecls := make(map[*ast.Object]bool)
+ for _, decl := range file.Decls {
+ switch d := decl.(type) {
+ case *ast.FuncDecl:
+ topDecls[d.Name.Obj] = true
+ case *ast.GenDecl:
+ for _, spec := range d.Specs {
+ switch s := spec.(type) {
+ case *ast.TypeSpec:
+ topDecls[s.Name.Obj] = true
+ case *ast.ValueSpec:
+ for _, id := range s.Names {
+ topDecls[id.Obj] = true
+ }
+ }
+ }
+ }
+ }
+
+ // Find unresolved identifiers and uses of top-level declarations.
unresolved := make(map[string]bool)
+ usesTopDecl := false
ast.Inspect(body, func(n ast.Node) bool {
// For an expression like fmt.Println, only add "fmt" to the
// set of unresolved names.
@@ -130,11 +151,19 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
}
return false
}
- if id, ok := n.(*ast.Ident); ok && id.Obj == nil {
- unresolved[id.Name] = true
+ if id, ok := n.(*ast.Ident); ok {
+ if id.Obj == nil {
+ unresolved[id.Name] = true
+ } else if topDecls[id.Obj] {
+ usesTopDecl = true
+ }
}
return true
})
+ if usesTopDecl {
+ // We don't support examples that are not self-contained (yet).
+ return nil
+ }
// Remove predeclared identifiers from unresolved list.
for n := range unresolved {
コアとなるコードの解説
-
topDecls := make(map[*ast.Object]bool)
の追加:- この行は、現在のファイル内で宣言されているすべてのトップレベルのオブジェクト(関数、型、変数)を追跡するためのマップを初期化します。
file.Decls
をループし、各宣言(ast.FuncDecl
、ast.GenDecl
)から対応するast.Object
を抽出し、topDecls
マップにtrue
として追加します。これにより、後で識別子がトップレベル宣言を参照しているかどうかを効率的にチェックできます。
-
usesTopDecl := false
の追加:- このブール変数は、
Example
関数のボディが、ファイル内の他のトップレベル宣言を使用しているかどうかを示すフラグとして導入されました。
- このブール変数は、
-
ast.Inspect
内の変更:- 以前は
if id, ok := n.(*ast.Ident); ok && id.Obj == nil
という条件で未解決の識別子のみをチェックしていました。 - 変更後、
if id, ok := n.(*ast.Ident); ok
となり、すべての識別子をチェックするようになりました。 - その内部で、以下の2つの条件分岐が追加されました。
if id.Obj == nil
: これは以前のロジックと同じで、未解決の識別子をunresolved
マップに追加します。else if topDecls[id.Obj]
: これは新しいロジックで、識別子が解決されており(id.Obj != nil
)、かつその解決されたオブジェクトがtopDecls
マップに存在する場合(つまり、トップレベル宣言を参照している場合)、usesTopDecl
をtrue
に設定します。
- 以前は
-
if usesTopDecl { ... return nil }
の追加:ast.Inspect
の走査が完了した後、usesTopDecl
がtrue
であれば、そのサンプルコードは自己完結型ではないと判断されます。- この場合、
playExample
関数はnil
を返します。これは、このサンプルコードに対して実行可能なコードを合成しないことを意味します。コメント// We don't support examples that are not self-contained (yet).
が、この決定の理由を明確にしています。
これらの変更により、go/doc
は、Example
関数がそのファイル内の他のトップレベル宣言に依存している場合に、そのサンプルコードのコード合成を適切にスキップするようになります。
関連リンク
- コミットのGitHub URL: https://github.com/golang/go/commit/ff27cdb625c7870d3a0b846dae70fc339b021b62
- Gerrit Change-ID:
https://golang.org/cl/6974045
(Goプロジェクトでは、GitHubにミラーリングされる前にGerritでコードレビューが行われていました。このリンクはGerrit上の変更セットを指します。)
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/14699.txt
- Go言語の
go/doc
パッケージのドキュメンテーション (一般的な情報源として) - Go言語の
go/ast
パッケージのドキュメンテーション (一般的な情報源として) - Go言語の
Example
関数のドキュメンテーション (一般的な情報源として) - Go言語のIssueトラッカー (Issue #4309 の詳細については、当時のGoプロジェクトのIssueトラッカーを参照する必要がありますが、現在のGitHubリポジトリでは古いIssueは直接参照できない場合があります。)