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

[インデックス 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.IdentObj フィールドを通じて、その識別子が参照している宣言にアクセスできます。

技術的詳細

このコミットの主要な変更は、src/pkg/go/doc/example.go ファイル内の playExample 関数にあります。この関数は、Example 関数から実行可能なコードを合成する役割を担っています。

変更の核心は、サンプルコードのボディ(ast.BlockStmt)を走査し、その中で使用されている識別子が、そのサンプルコードが属するファイルのトップレベルで宣言されている他のオブジェクトを参照しているかどうかを検出するロジックの追加です。

具体的には、以下のステップが導入されています。

  1. トップレベル宣言の収集: まず、file.Decls を走査して、現在のファイル内で宣言されているすべてのトップレベルの関数、型、変数を topDecls マップに収集します。これにより、どの ast.Object がトップレベル宣言であるかを効率的に判断できるようになります。
  2. 未解決の識別子とトップレベル宣言の使用の検出: ast.Inspect を使用して、Example 関数のボディ(body *ast.BlockStmt)を再帰的に走査します。
    • 以前と同様に、id.Obj == nil である識別子(つまり、未解決の識別子)は unresolved マップに記録されます。
    • 新しく追加されたロジックとして、id.Obj != nil であり、かつその id.ObjtopDecls マップに存在する場合(つまり、サンプルコードがファイル内の他のトップレベル宣言を使用している場合)、usesTopDecl フラグを true に設定します。
  3. 自己完結性のチェック: ast.Inspect の走査が完了した後、usesTopDecltrue であれば、そのサンプルコードは自己完結型ではないと判断されます。この場合、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 {

コアとなるコードの解説

  1. topDecls := make(map[*ast.Object]bool) の追加:

    • この行は、現在のファイル内で宣言されているすべてのトップレベルのオブジェクト(関数、型、変数)を追跡するためのマップを初期化します。
    • file.Decls をループし、各宣言(ast.FuncDeclast.GenDecl)から対応する ast.Object を抽出し、topDecls マップに true として追加します。これにより、後で識別子がトップレベル宣言を参照しているかどうかを効率的にチェックできます。
  2. usesTopDecl := false の追加:

    • このブール変数は、Example 関数のボディが、ファイル内の他のトップレベル宣言を使用しているかどうかを示すフラグとして導入されました。
  3. 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 マップに存在する場合(つまり、トップレベル宣言を参照している場合)、usesTopDecltrue に設定します。
  4. if usesTopDecl { ... return nil } の追加:

    • ast.Inspect の走査が完了した後、usesTopDecltrue であれば、そのサンプルコードは自己完結型ではないと判断されます。
    • この場合、playExample 関数は nil を返します。これは、このサンプルコードに対して実行可能なコードを合成しないことを意味します。コメント // We don't support examples that are not self-contained (yet). が、この決定の理由を明確にしています。

これらの変更により、go/doc は、Example 関数がそのファイル内の他のトップレベル宣言に依存している場合に、そのサンプルコードのコード合成を適切にスキップするようになります。

関連リンク

参考にした情報源リンク

  • コミット情報: /home/orange/Project/comemo/commit_data/14699.txt
  • Go言語の go/doc パッケージのドキュメンテーション (一般的な情報源として)
  • Go言語の go/ast パッケージのドキュメンテーション (一般的な情報源として)
  • Go言語の Example 関数のドキュメンテーション (一般的な情報源として)
  • Go言語のIssueトラッカー (Issue #4309 の詳細については、当時のGoプロジェクトのIssueトラッカーを参照する必要がありますが、現在のGitHubリポジトリでは古いIssueは直接参照できない場合があります。)