[インデックス 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は直接参照できない場合があります。)