[インデックス 13868] ファイルの概要
このコミットは、Go言語のドキュメンテーションツール (go/doc
パッケージ) において、Goのコード例 (Example) をスタンドアロンで実行可能なプログラムとして合成する機能を追加するものです。具体的には、Example
関数から package main
と func main
を持つ完全なGoプログラムを生成し、Go Playgroundなどで実行できるようにするための変更が含まれています。
コミット
- コミットハッシュ:
7e525928d33bdd48142a2664dc03664c0034354b
- 作者: Andrew Gerrand adg@golang.org
- コミット日時: 2012年9月18日 火曜日 14:13:34 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e525928d33bdd48142a2664dc03664c0034354b
元コミット内容
go/doc: synthesize "package main" for examples
R=gri
CC=golang-dev
https://golang.org/cl/6525046
変更の背景
Go言語のドキュメンテーションシステムは、コード例 (Example functions) をドキュメントに含めることで、コードの動作を具体的に示す強力な機能を提供しています。これらのExample関数は通常、テストファイル (_test.go
サフィックスを持つファイル) 内に記述され、go test
コマンドによって実行・検証されます。
しかし、これらのExample関数は、それ単体では完全な実行可能プログラムではありません。Go Playgroundのようなオンラインツールや、ユーザーがローカルでExampleコードを試す際には、package main
と func main()
が必要となります。
このコミットの背景には、go/doc
パッケージが生成するドキュメントから、Exampleコードをよりインタラクティブに、そして独立して実行できるようにするという目的があります。これにより、ユーザーはドキュメントを読みながら、その場でコード例を試すことが容易になり、学習体験が向上します。
前提知識の解説
このコミットを理解するためには、以下のGo言語および関連ツールの概念を理解しておく必要があります。
-
Go言語のExample関数: Go言語では、
Example
というプレフィックスを持つ関数を記述することで、パッケージのドキュメンテーションにコード例を含めることができます。これらの関数は通常、_test.go
ファイル内に定義され、go test
コマンドによって実行され、その出力が期待される出力 (// Output:
) と一致するかどうかが検証されます。これにより、ドキュメントのコード例が常に最新かつ正確であることが保証されます。例:
package mypackage import "fmt" func ExampleHello() { fmt.Println("Hello") // Output: Hello }
-
package main
とfunc main()
: Go言語において、実行可能なプログラムは必ずpackage main
パッケージに属し、エントリポイントとしてfunc main()
関数を持っていなければなりません。go run
コマンドやgo build
コマンドで実行されるのは、このmain
パッケージのmain
関数です。 -
go/doc
パッケージ: Go標準ライブラリの一部であり、Goのソースコードからドキュメンテーションを抽出・生成するためのパッケージです。go doc
コマンドやgodoc
ツールはこのパッケージを利用して、GoのコードからコメントやExample関数を解析し、整形されたドキュメントを生成します。 -
go/ast
パッケージ: Go標準ライブラリの一部であり、Goのソースコードの抽象構文木 (Abstract Syntax Tree: AST) を表現するためのパッケージです。go/parser
パッケージでソースコードを解析すると、ASTが生成されます。このASTを操作することで、プログラムの構造を分析したり、コードを変換したりすることができます。ast.File
: Goの単一のソースファイルを表すASTノード。ast.FuncDecl
: 関数宣言を表すASTノード。ast.BlockStmt
: コードブロック (中括弧{}
で囲まれた部分) を表すASTノード。ast.ImportSpec
: インポート宣言を表すASTノード。ast.GenDecl
: 一般的な宣言 (import, const, type, var) を表すASTノード。ast.Ident
: 識別子 (変数名、関数名など) を表すASTノード。ast.SelectorExpr
: セレクタ式 (例:fmt.Println
のfmt.Println
) を表すASTノード。ast.Inspect
: ASTを再帰的に走査するためのヘルパー関数。
-
Go Playground: Go言語の公式ウェブサイトで提供されているオンラインツールで、Goのコードをブラウザ上で記述・実行し、その結果を確認できます。Go Playgroundで実行されるコードは、完全な
package main
プログラムである必要があります。
技術的詳細
このコミットの主要な変更は、go/doc/example.go
ファイルに playExample
という新しい関数が追加されたことです。この関数は、既存のExample関数のASTを受け取り、それを package main
と func main()
を持つ完全なGoプログラムのASTに変換します。
playExample
関数の主な処理は以下の通りです。
-
Example関数の識別:
Examples
関数内で、各Example関数 (ast.FuncDecl
) が処理される際に、新しく追加されたPlay
フィールドにplayExample
関数の結果が格納されます。Play
フィールドは*ast.File
型であり、合成された完全なプログラムのASTを保持します。 -
テストファイルであることの確認:
playExample
関数は、まずExample関数が定義されているファイルがテストファイル (_test.go
で終わるファイル名) であることを確認します。これは、Example関数が通常テストファイル内に記述されるためであり、現時点ではパッケージの一部であるExampleはサポートしていません。 -
未解決の識別子の特定とインポートの解決: Example関数の本体 (
body *ast.BlockStmt
) を走査し、未解決の識別子 (例:fmt.Println
のfmt
のようなパッケージ修飾子) を特定します。 その後、元のファイルに存在するインポート宣言を調べ、特定された未解決の識別子に対応するインポートパスを解決します。この際、パッケージ名がインポートパスのベース名と一致するというヒューリスティックが使用されます。ドットインポート (. "path/to/pkg"
) は現時点ではサポートされていません。 -
新しいインポート宣言の合成: 解決されたインポートパスに基づいて、新しい
ast.GenDecl
(一般的な宣言) を作成し、token.IMPORT
型の宣言として、必要なインポートスペック (ast.ImportSpec
) を追加します。これにより、合成されたプログラムに必要なパッケージがインポートされます。 -
main
関数の合成: Example関数の本体 (body
) をそのまま新しいmain
関数の本体として使用し、ast.FuncDecl
(関数宣言) を作成します。関数名はmain
、型は引数なし・戻り値なしのfunc()
となります。 -
コメントのフィルタリング: 元のファイルに存在するコメントのうち、Example関数の本体の範囲外にあるコメントは除外されます。これにより、合成されたプログラムにはExample関数に関連するコメントのみが含まれるようになります。
-
新しいファイルのASTの合成: 最終的に、
package main
を表すast.Ident
、合成されたインポート宣言 (importDecl
)、合成されたmain
関数宣言 (funcDecl
)、およびフィルタリングされたコメント (comments
) を含む新しい*ast.File
が作成され、返されます。
このプロセスにより、Example関数はGo Playgroundなどで実行可能な独立したGoプログラムとして表現できるようになります。
コアとなるコードの変更箇所
このコミットによって変更された主要なファイルは以下の通りです。
src/pkg/go/doc/example.go
このファイルに92行の追加が行われています。
コアとなるコードの解説
src/pkg/go/doc/example.go
の変更点に焦点を当てて解説します。
-
Example
構造体の変更:Example
構造体に新しいフィールドPlay *ast.File
が追加されました。type Example struct { Name string // name of the item being exemplified Doc string // example function doc string Code ast.Node Play *ast.File // a whole program version of the example Comments []*ast.CommentGroup Output string // expected output }
この
Play
フィールドは、Example関数から合成されたpackage main
プログラムの抽象構文木 (AST) を保持します。これにより、go/doc
パッケージの利用者は、Exampleの元のコード (Code
) だけでなく、実行可能な完全なプログラム (Play
) にもアクセスできるようになります。 -
Examples
関数内のPlay
フィールドへの値の割り当て:Examples
関数内でExample関数が解析される際に、新しく追加されたplayExample
関数が呼び出され、その結果がExample
構造体のPlay
フィールドに割り当てられます。// ... Examples = append(Examples, &Example{ Name: name[len("Example"):], Doc: doc, Code: f.Body, Play: playExample(file, f.Body), // ここでplayExampleが呼び出される Comments: file.Comments, Output: exampleOutput(f, file.Comments), }) // ...
-
playExample
関数の追加: これがこのコミットの核心となる新しい関数です。// playExample synthesizes a new *ast.File based on the provided // file with the provided function body as the body of main. func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { // ... (上記「技術的詳細」で説明したロジック) }
この関数は、Example関数の本体 (
body
) と、それが含まれる元のファイル (file
) のASTを受け取ります。そして、このExample関数の本体をmain
関数として、必要なインポート文を合成し、最終的にpackage main
を持つ完全なGoプログラムのAST (*ast.File
) を生成して返します。-
インポートの解決ロジック:
ast.Inspect
を使用してExample関数の本体を走査し、fmt.Println
のfmt
のようなセレクタ式 (ast.SelectorExpr
) の中で、未解決の識別子 (id.Obj == nil
) を探します。 その後、元のファイルのインポート宣言 (file.Imports
) を調べ、未解決の識別子名 (id.Name
) とインポートパスのベース名 (path.Base(p)
) が一致するかどうかで、必要なインポートパスを特定します。 -
ASTの合成:
ast.GenDecl
(import宣言用) とast.FuncDecl
(main関数用) を手動で構築し、それらを新しいast.File
のDecls
(宣言リスト) に追加します。Lparen
とRparen
を非ゼロに設定することで、go/printer
パッケージがこのインポート宣言をファクタリングされた形式 (複数のインポートが括弧で囲まれる形式) で出力するようにします。 -
コメントの処理: Example関数の本体の範囲外にあるコメントは、合成されるASTから除外されます。これは、合成されたプログラムがExample関数に直接関連するコードのみを含むようにするためです。
-
この変更により、go/doc
パッケージは、Example関数を単なるコードスニペットとしてだけでなく、Go Playgroundなどで直接実行可能な完全なプログラムとして提供できるようになりました。
関連リンク
- Gerrit Change-ID:
https://golang.org/cl/6525046
これはGoプロジェクトのコードレビューシステムであるGerritの変更リストへのリンクです。このリンクを辿ることで、このコミットがマージされるまでの議論や、関連する変更履歴を確認できます。
参考にした情報源リンク
- Go言語のExample関数に関する公式ドキュメントやチュートリアル (一般的な知識として)
- Go言語の
go/ast
パッケージのドキュメント (AST操作の理解のため) - Go Playgroundの動作原理 (合成されたコードの利用目的の理解のため)
- Go言語の
go/doc
パッケージのドキュメント (パッケージの役割の理解のため) - Go言語の
package main
とfunc main
に関する基本概念 (一般的な知識として) strconv.Unquote
およびstrconv.Quote
のGoドキュメント (文字列リテラルの処理のため)path.Base
のGoドキュメント (インポートパスのベース名取得のため)