[インデックス 14013] ファイルの概要
このコミットは、Go言語のドキュメント生成ツールである go/doc
パッケージにおいて、Go Playgroundで実行可能な「ファイル全体を対象とするExample(例)」の処理方法を根本的に見直すものです。具体的には、ExampleコードがGo Playgroundの要件(package main
とmain
関数)に適合するように変換するロジックが強化され、未解決の識別子(importが必要なもの)の検出と、Goの組み込み識別子(事前宣言された型、関数、定数)の適切な除外が改善されています。これにより、Goの公式ドキュメントやgodocで表示されるExampleが、Go Playgroundでより正確かつ確実に動作するようになります。
コミット
commit 81ae666f16bb0b747d1dddef0be4f7dd8b285c49
Author: Andrew Gerrand <adg@golang.org>
Date: Thu Oct 4 07:55:24 2012 +1000
go/doc: rewrite whole file examples for playground
R=gri
CC=gobot, golang-dev
https://golang.org/cl/6592061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/81ae666f16bb0b747d1dddef0be4f7dd8b285c49
元コミット内容
go/doc: rewrite whole file examples for playground
R=gri
CC=gobot, golang-dev
https://golang.org/cl/6592061
変更の背景
Go言語のドキュメントシステムでは、コード例(Example)を記述し、それが実際に動作することを示すことができます。これらのExampleは、go test
コマンドによってテストされるだけでなく、godoc
ツールやGo Playgroundといったオンライン環境で実行可能にすることで、ユーザーがコードを試したり理解を深めたりするのに役立ちます。
しかし、Go Playgroundは特定の制約(package main
であること、エントリポイントとしてmain
関数を持つことなど)の下で動作します。従来のgo/doc
パッケージでは、Exampleコードがファイル全体を占める場合(例えば、ExampleF
のような関数ではなく、main
関数を含む完全なプログラムとして記述された場合)に、Go Playgroundの要件に適切に変換するロジックが不十分でした。特に、Exampleコード内で使用されているが明示的にimportされていないパッケージ(Goの組み込み型や関数など)の扱いが課題となっていました。
このコミットは、これらの「ファイル全体を対象とするExample」をGo Playgroundでより正確に、かつ堅牢に実行できるようにするための改善を目的としています。これにより、ユーザーはドキュメントに記載されたExampleをGo Playgroundでシームレスに試すことができ、学習体験が向上します。
前提知識の解説
このコミットの理解には、以下のGo言語および関連ツールの知識が役立ちます。
- Go Playground: Goコードをブラウザ上で実行できるオンラインサービス。通常、
package main
で始まり、func main()
を持つコードを期待します。 go/doc
パッケージ: Goのソースコードからドキュメントを生成するための標準ライブラリ。Exampleコードの解析と整形も担当します。go/ast
パッケージ: Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として表現するための標準ライブラリ。このコミットでは、ASTを操作してExampleコードを変換しています。ast.File
: Goのソースファイル全体を表すASTノード。ast.BlockStmt
: コードブロック({ ... }
)を表すASTノード。関数の本体などがこれに該当します。ast.FuncDecl
: 関数宣言を表すASTノード。ast.GenDecl
: 一般的な宣言(import、const、type、var)を表すASTノード。ast.ImportSpec
: import文の個々のインポートパスを表すASTノード。ast.Ident
: 識別子(変数名、関数名、パッケージ名など)を表すASTノード。ast.Inspect
: ASTを走査するためのユーティリティ関数。- Example関数: Goのドキュメントでコード例を示すために使用される特別な関数。
Example
で始まる関数名を持つ。ExampleHello
のように特定の関数や型のExampleを示すものと、Example
単独でファイル全体のExampleを示すものがある。 Output:
コメント: GoのExampleコードの末尾に記述される特別なコメント。Exampleの実行結果の期待値を指定するために使用されます。go test
はこれと実際の出力を比較します。- 事前宣言された識別子 (Predeclared Identifiers): Go言語に組み込まれている型(
int
,string
,bool
など)、関数(len
,cap
,make
など)、定数(true
,false
,iota
など)のこと。これらは明示的なimport
文なしで使用できます。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
playExampleFile
関数の導入:- ファイル全体をExampleとして扱うための新しい関数
playExampleFile
がsrc/pkg/go/doc/example.go
に追加されました。 - この関数は、入力された
*ast.File
をGo Playgroundで実行可能な形式(package main
とfunc main()
を持つファイル)に変換します。 - 具体的には、Example関数(
ExampleXxx
)をmain
関数にリネームし、その関数本体からOutput:
コメントを削除する処理を行います。 - ファイルの先頭にある著作権コメント("Copyright"で始まるもの)も削除されます。
- ファイル全体をExampleとして扱うための新しい関数
-
未解決の識別子検出の改善:
playExample
関数(Exampleの関数本体をGo Playground用に変換する関数)内で、未解決の識別子(つまり、import
が必要なパッケージ名)を検出するロジックが改善されました。- 以前は
[]*ast.Ident
(スライス)で管理されていた未解決の識別子が、map[string]bool
(マップ)に変更されました。これにより、識別子の重複チェックや削除が効率的になります。 ast.Inspect
を使用してASTを走査し、SelectorExpr
(例:fmt.Println
のfmt
)や単独のIdent
(例:Println
がfmt
パッケージからインポートされている場合)から未解決の識別子を収集します。
-
事前宣言された識別子の適切な除外:
- 未解決の識別子リストから、Go言語に組み込まれている事前宣言された識別子(
nil
、bool
などの型、len
などの関数、true
などの定数)を明示的に除外するロジックが追加されました。 - これは、
src/pkg/go/doc/reader.go
に新しく定義されたpredeclaredFuncs
とpredeclaredConstants
マップ、および既存のpredeclaredTypes
マップを使用して行われます。これらの識別子はimport
を必要としないため、Go Playground用のコード生成時に誤ってimport
を合成しないようにします。
- 未解決の識別子リストから、Go言語に組み込まれている事前宣言された識別子(
-
Output:
コメント処理の共通化とリファクタリング:- Exampleコードの末尾にある
Output:
コメントを検出・削除し、関数のブロックの終了位置を調整するロジックが、stripOutputComment
という新しいヘルパー関数として抽出されました。 - また、ブロック内の最後のコメントを見つけるための
lastComment
ヘルパー関数も導入され、コードの重複が排除され、可読性が向上しました。
- Exampleコードの末尾にある
-
堅牢性の向上:
playExample
関数は、すべての未解決の識別子を解決できなかった場合(つまり、必要なimport
を特定できなかった場合)にnil
を返すようになりました。これにより、Go Playgroundでビルドできない不正なコードが生成されるのを防ぎます。
これらの変更により、go/doc
パッケージはExampleコードのASTをより正確に分析し、Go Playgroundの実行環境に合わせた適切なASTを合成できるようになりました。
コアとなるコードの変更箇所
src/pkg/go/doc/example.go
-
Examples
関数:--- a/src/pkg/go/doc/example.go +++ b/src/pkg/go/doc/example.go @@ -69,6 +69,7 @@ func Examples(files ...*ast.File) []*Example { // other top-level declarations, and no tests or // benchmarks, use the whole file as the example. flist[0].Code = file + flist[0].Play = playExampleFile(file) } list = append(list, flist...)\n
- ファイル全体がExampleとして扱われる場合に、
Play
フィールドにplayExampleFile
の戻り値を設定するようになりました。
- ファイル全体がExampleとして扱われる場合に、
-
playExample
関数:- 未解決の識別子を
map[string]bool
で管理するように変更。 - 事前宣言された識別子を未解決リストから削除するロジックを追加。
stripOutputComment
ヘルパー関数を呼び出すように変更。- 未解決の識別子が残っている場合に
nil
を返すように変更。
- 未解決の識別子を
-
playExampleFile
関数 (新規追加):func playExampleFile(file *ast.File) *ast.File { // Strip copyright comment if present. comments := file.Comments if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") { comments = comments[1:] } // Copy declaration slice, rewriting the ExampleX function to main. var decls []ast.Decl for _, d := range file.Decls { if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") { // Copy the FuncDecl, as it may be used elsewhere. newF := *f newF.Name = ast.NewIdent("main") newF.Body, comments = stripOutputComment(f.Body, comments) d = &newF } decls = append(decls, d) } // Copy the File, as it may be used elsewhere. f := *file f.Name = ast.NewIdent("main") f.Decls = decls f.Comments = comments return &f }
- ファイル全体のExampleをGo Playground用に変換する主要なロジック。
ExampleXxx
関数をmain
にリネームし、Output:
コメントを処理します。
- ファイル全体のExampleをGo Playground用に変換する主要なロジック。
-
stripOutputComment
関数 (新規追加):func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) { // Do nothing if no "Output:" comment found. i, last := lastComment(body, comments) if last == nil || !outputPrefix.MatchString(last.Text()) { return body, comments } // Copy body and comments, as the originals may be used elsewhere. newBody := &ast.BlockStmt{ Lbrace: body.Lbrace, List: body.List, Rbrace: last.Pos(), } newComments := make([]*ast.CommentGroup, len(comments)-1) copy(newComments, comments[:i]) copy(newComments[i:], comments[i+1:]) return newBody, newComments }
Output:
コメントを削除し、ブロックの終了位置を調整する共通ロジック。
-
lastComment
関数 (新規追加):func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) { pos, end := b.Pos(), b.End() for j, cg := range c { if cg.Pos() < pos { continue } if cg.End() > end { break } i, last = j, cg } return }
- 指定されたブロック内の最後のコメントを見つけるヘルパー関数。
src/pkg/go/doc/reader.go
- 事前宣言された識別子マップの追加と移動:
--- a/src/pkg/go/doc/reader.go +++ b/src/pkg/go/doc/reader.go @@ -515,29 +515,6 @@ func (r *reader) readPackage(pkg *ast.Package, mode Mode) { // ---------------------------------------------------------------------------- // Types -var predeclaredTypes = map[string]bool{ -... (既存のpredeclaredTypesが移動) -} - func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func { if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 { return f // shouldn't happen, but be safe @@ -772,3 +749,53 @@ func sortedFuncs(m methodSet, allMethods bool) []*Func { ) return list } + +// ---------------------------------------------------------------------------- +// Predeclared identifiers (minus "nil") + +var predeclaredTypes = map[string]bool{ + "bool": true, + "byte": true, + "complex64": true, + "complex128": true, + "error": true, + "float32": true, + "float64": true, + "int": true, + "int8": true, + "int16": true, + "int32": true, + "int64": true, + "rune": true, + "string": true, + "uint": true, + "uint8": true, + "uint16": true, + "uint32": true, + "uint64": true, + "uintptr": true, +} + +var predeclaredFuncs = map[string]bool{ + "append": true, + "cap": true, + "close": true, + "complex": true, + "copy": true, + "delete": true,\n + "imag": true, + "len": true, + "make": true, + "new": true, + "panic": true, + "print": true, + "println": true, + "real": true, + "recover": true, +} + +var predeclaredConstants = map[string]bool{ + "iota": true, + "true": true, + "false": true, +}
predeclaredTypes
マップがファイルの後半に移動し、predeclaredFuncs
とpredeclaredConstants
という新しいマップが追加されました。これらはGoの組み込み識別子を網羅的にリストアップし、go/doc
パッケージがExampleコードの依存関係をより正確に判断するために使用されます。
コアとなるコードの解説
このコミットの核心は、Go Playgroundの制約に合わせてExampleコードのASTを動的に書き換える能力の向上にあります。
-
playExampleFile
の役割:go/doc
は、Exampleコードがファイル全体を構成していると判断した場合、このplayExampleFile
関数を呼び出します。- この関数は、元のExampleファイル(
package foo
やfunc ExampleFoo()
などを含むかもしれない)を、Go Playgroundが期待するpackage main
とfunc main()
を持つ形式に変換します。 - 特に重要なのは、
ExampleXxx
という名前の関数をmain
関数にリネームする部分です。これにより、Go PlaygroundがExampleコードをエントリポイントとして認識し、実行できるようになります。 - 著作権コメントの削除は、Go Playgroundに送信されるコードの冗長性を減らすためのものです。
-
未解決識別子と事前宣言された識別子の処理:
playExample
関数は、Exampleコード内で使用されているが、そのAST上ではどのパッケージに属するかが解決されていない識別子(例:Println
がfmt
パッケージからインポートされている場合、fmt
は解決済みだがPrintln
は未解決)を特定します。- これらの未解決識別子の中から、Go言語に組み込まれている事前宣言された識別子(
len
,make
,int
など)を除外します。これは、これらの識別子がimport
文を必要としないためです。 - 残った未解決識別子に基づいて、必要な
import
文を動的に合成し、Go Playgroundに送信されるASTに追加します。これにより、ExampleコードがGo Playground上で正しくコンパイル・実行されるために必要な依存関係が自動的に満たされます。 - 未解決の識別子が残ってしまった場合(つまり、必要な
import
を特定できなかった場合)には、nil
を返すことで、Go Playgroundでビルドエラーになる可能性のあるコードの生成を避けます。
-
Output:
コメント処理の洗練:Output:
コメントは、Exampleのテスト時に期待される出力を指定するためのものです。Go Playgroundで実行する際には、このコメント自体はコードの一部ではないため、削除する必要があります。stripOutputComment
関数は、この削除処理を共通化し、ExampleのASTからOutput:
コメントを安全に取り除きます。これにより、Go Playgroundに送信されるコードがクリーンになり、実行結果に影響を与えません。
これらの変更は、GoのドキュメントシステムとGo Playgroundの連携を強化し、Go言語のExampleがよりインタラクティブで信頼性の高いものになるように貢献しています。
関連リンク
参考にした情報源リンク
- golang/go GitHubリポジトリ
- Go Code Review Comments: Example functions
- Go Playground Internals (このコミットの時点では存在しない可能性もありますが、Go Playgroundの仕組みを理解する上で参考になります)
- Go AST Cheatsheet (ASTの構造を理解する上で参考になります)