[インデックス 15727] ファイルの概要
このコミットは、Go言語のドキュメンテーション生成ツール go/doc
パッケージにおけるバグ修正に関するものです。具体的には、埋め込み型(embedded types)のメソッドのレシーバ型(receiver type)の位置情報(position)が正しく設定されていなかった問題に対処しています。このバグは、以前の変更(CL 7674044)が導入されるまでは顕在化していませんでしたが、その変更によって go/doc
のテストが失敗するようになったため、この修正が必要となりました。
コミット
commit d825320550b87158286ec772baddab81d07079e0
Author: Robert Griesemer <gri@golang.org>
Date: Tue Mar 12 13:06:55 2013 -0700
go/doc: set receiver type position for embedded methods
This was a bug that didn't manifest itself before CL 7674044;
but with that CL and without this fix, the go/doc tests fail.
(The bug fixed by 7674044 and the bug fixed here cancelled
each other out w/ respect to the go/doc tests).
R=rsc
CC=golang-dev
https://golang.org/cl/7628045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d825320550b87158286ec772baddab81d07079e0
元コミット内容
go/doc: set receiver type position for embedded methods
これは、CL 7674044 が導入されるまでは顕在化しなかったバグでした。しかし、その CL が適用され、この修正がない場合、go/doc
のテストが失敗します。(7674044 で修正されたバグと、ここで修正されたバグは、go/doc
のテストに関して互いに打ち消し合っていました)。
変更の背景
この変更の背景には、Go言語のドキュメンテーション生成ツール go/doc
の内部的な動作と、抽象構文木(AST)の処理に関する特定のバグが存在します。コミットメッセージによると、このバグは以前から存在していたものの、別の変更(CL 7674044)が導入されるまでは表面化していませんでした。
CL 7674044 は、おそらく go/doc
がメソッドの情報をどのように処理するか、またはASTからどのように抽出するかに関する変更であったと推測されます。この変更が適用された結果、これまで隠れていた「埋め込み型メソッドのレシーバ型位置情報の不正確さ」というバグが露呈し、go/doc
のテストが失敗するようになりました。
コミットメッセージの「The bug fixed by 7674044 and the bug fixed here cancelled each other out w/ respect to the go/doc tests」という記述は非常に重要です。これは、2つの異なるバグが偶然にも互いの影響を相殺し、結果的にテストがパスしていた状態を示唆しています。CL 7674044 が一方のバグを修正したことで、もう一方のバグ(このコミットで修正されるもの)が単独でテスト結果に影響を与えるようになり、テストが失敗するようになった、という経緯が考えられます。
したがって、このコミットは、go/doc
の正確性と堅牢性を向上させるために、以前から存在していたが顕在化していなかったバグを修正することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語および関連ツールの概念を理解しておく必要があります。
-
go/doc
パッケージ: Go言語の標準ライブラリの一部であり、Goのソースコードからドキュメンテーションを生成するためのツールです。go doc
コマンドやgodoc
サーバーなどがこのパッケージを利用して、コードコメントや宣言からAPIドキュメントを生成します。正確なドキュメントを生成するためには、ソースコードの構造(特に型、メソッド、関数など)を正確に解析する必要があります。 -
抽象構文木(Abstract Syntax Tree: AST): プログラムのソースコードを抽象的な構文構造で表現した木構造のデータ構造です。Go言語では、
go/ast
パッケージがソースコードを解析してASTを構築する機能を提供します。コンパイラやリンター、コード分析ツール、そしてgo/doc
のようなドキュメンテーションツールは、このASTを利用してコードの構造や意味を理解します。 -
ast.Node
とPos()
メソッド:go/ast
パッケージのAST内の各ノード(例えば、識別子、式、宣言など)は、ast.Node
インターフェースを実装しています。このインターフェースにはPos()
メソッドが含まれており、これはソースコード内でのノードの開始位置(バイトオフセット)を返します。この位置情報は、エラー報告、コードの整形、そしてドキュメンテーション生成において、要素がソースコードのどこに存在するかを正確に特定するために非常に重要です。 -
ast.Ident
:go/ast
パッケージにおける識別子(identifier)を表すASTノードです。変数名、関数名、型名などがこれに該当します。ast.Ident
構造体には、識別子の名前(Name
フィールド)と、その名前がソースコード内で始まる位置(NamePos
フィールド)が含まれます。 -
ast.StarExpr
:go/ast
パッケージにおけるポインタ型(*T
)を表すASTノードです。例えば、*MyType
のような型はast.StarExpr
として表現されます。この構造体には、ポインタ演算子*
の位置(Star
フィールド)と、ポインタが指す型(X
フィールド)が含まれます。 -
レシーバ型(Receiver Type)と埋め込み型(Embedded Types): Go言語では、メソッドは特定の型に関連付けられます。この型をレシーバ型と呼びます。例えば、
func (r MyType) MethodName() {}
の場合、MyType
がレシーバ型です。 埋め込み型は、構造体内に他の型をフィールド名なしで宣言することで、その型のメソッドを「継承」するGoの機能です。例えば、type MyStruct struct { OtherStruct; int }
の場合、MyStruct
はOtherStruct
のメソッドを直接呼び出すことができます。go/doc
は、これらの埋め込み型メソッドも適切にドキュメント化する必要があります。
このコミットは、go/doc
が埋め込み型メソッドのレシーバ型を処理する際に、特にポインタ型の場合に、そのレシーバ型のASTノードの位置情報(Pos()
)を正確に設定していなかったという問題に対処しています。位置情報が不正確だと、生成されるドキュメントの内部的な整合性が損なわれたり、特定のツールが期待するAST構造と異なったりする可能性があります。
技術的詳細
このコミットは、src/pkg/go/doc/reader.go
ファイル内の customizeRecv
関数における変更です。この関数は、埋め込み型から「昇格」されたメソッドのレシーバ型をカスタマイズする役割を担っています。
問題の核心は、レシーバ型がポインタ型(例: *T
)である場合に、そのレシーバ型を表すASTノード(ast.StarExpr
やその内部の ast.Ident
)のソースコード上の位置情報(Pos()
)が正しく設定されていなかった点にあります。
元のコードでは、レシーバ型がポインタ型であるかどうかを origRecvIsPtr
で判断し、もしポインタ型でなければ ast.NewIdent(recvTypeName)
で新しい識別子を作成し、必要に応じて &ast.StarExpr{X: typ}
でポインタ式を構築していました。しかし、この ast.NewIdent
や ast.StarExpr
の作成時に、元のレシーバ型の位置情報を適切に引き継いでいませんでした。特に、ast.Ident
の NamePos
フィールドや ast.StarExpr
の Star
フィールドがデフォルト値(0)のままになってしまう可能性がありました。
修正後のコードでは、以下の点が改善されています。
-
元の位置情報の保持:
origPos := newField.Type.Pos()
を追加し、元のレシーバ型のASTノードの開始位置をorigPos
に保存しています。これは、新しいASTノードを作成する際に、元のソースコード上の位置情報を引き継ぐために使用されます。 -
ast.Ident
のNamePos
の設定:newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}
新しいast.Ident
を作成する際に、NamePos
フィールドにorigPos
を明示的に設定しています。これにより、レシーバ型の名前(例:MyType
)がソースコード上のどこから始まるかという情報が正確に保持されます。 -
ポインタ型の場合の
ast.Ident.NamePos
の調整:if !embeddedIsPtr && origRecvIsPtr { newIdent.NamePos++ }
この行は、元のレシーバがポインタ型(*MyType
)であり、かつ埋め込み型がポインタではない(つまり、MyType
から*MyType
へとレシーバ型が変更される)場合に適用されます。ポインタ型の場合、*
記号がレシーバ型の名前の前に付くため、名前自体の開始位置は*
の分だけ1文字後ろにずれます。このnewIdent.NamePos++
は、そのずれを正確に反映させるための調整です。 -
ast.StarExpr
のStar
フィールドの設定:typ = &ast.StarExpr{Star: origPos, X: newIdent}
ポインタ式ast.StarExpr
を作成する際に、Star
フィールドにorigPos
を設定しています。これにより、ポインタ演算子*
がソースコード上のどこに位置するかという情報が正確に保持されます。
これらの変更により、go/doc
が埋め込み型メソッドのレシーバ型を処理する際に生成するASTノードが、ソースコード上の正確な位置情報を持つようになります。これにより、go/doc
の内部的な整合性が保たれ、テストの失敗が解消されました。コミットメッセージにあるように、この修正は以前のCL 7674044 との相互作用によって顕在化したバグを修正するものであり、ASTノードの位置情報の正確性がGoツールチェインの様々な部分でいかに重要であるかを示しています。
コアとなるコードの変更箇所
--- a/src/pkg/go/doc/reader.go
+++ b/src/pkg/go/doc/reader.go
@@ -533,10 +533,13 @@ func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int)\
// copy existing receiver field and set new type
newField := *f.Decl.Recv.List[0]
+ origPos := newField.Type.Pos()
_, origRecvIsPtr := newField.Type.(*ast.StarExpr)
- var typ ast.Expr = ast.NewIdent(recvTypeName)
+ newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}
+ var typ ast.Expr = newIdent
if !embeddedIsPtr && origRecvIsPtr {
- typ = &ast.StarExpr{X: typ}
+ newIdent.NamePos++ // '*' is one character
+ typ = &ast.StarExpr{Star: origPos, X: newIdent}
}
newField.Type = typ
コアとなるコードの解説
この変更は、src/pkg/go/doc/reader.go
ファイル内の customizeRecv
関数にあります。この関数は、Goの構造体に埋め込まれた型(embedded type)のメソッドを処理する際に、そのメソッドのレシーバ型を適切に調整するために使用されます。
変更の目的は、調整されたレシーバ型が、元のソースコードにおける正確な位置情報(token.Pos
)を持つようにすることです。これは、go/ast
パッケージが提供する抽象構文木(AST)のノードが、ソースコード内の対応する要素の開始位置を記録するために重要です。
以下に、変更された各行の解説を示します。
変更前:
// copy existing receiver field and set new type
newField := *f.Decl.Recv.List[0]
_, origRecvIsPtr := newField.Type.(*ast.StarExpr)
var typ ast.Expr = ast.NewIdent(recvTypeName)
if !embeddedIsPtr && origRecvIsPtr {
typ = &ast.StarExpr{X: typ}
}
newField.Type = typ
newField := *f.Decl.Recv.List[0]
: 既存のレシーバフィールドのコピーを作成します。_, origRecvIsPtr := newField.Type.(*ast.StarExpr)
: 元のレシーバ型がポインタ型(*T
)であるかどうかをチェックします。var typ ast.Expr = ast.NewIdent(recvTypeName)
: 新しいレシーバ型名(recvTypeName
)に基づいて、新しい識別子(ast.Ident
)を作成します。ast.NewIdent
は、位置情報を持たない新しい識別子を作成します。if !embeddedIsPtr && origRecvIsPtr { typ = &ast.StarExpr{X: typ} }
: もし埋め込み型がポインタではなく、元のレシーバがポインタ型であった場合、新しいレシーバ型をポインタ型に変換します。この際、ast.StarExpr
を作成しますが、このノードもポインタ演算子*
の位置情報を持っていません。
変更後:
// copy existing receiver field and set new type
newField := *f.Decl.Recv.List[0]
+ origPos := newField.Type.Pos()
_, origRecvIsPtr := newField.Type.(*ast.StarExpr)
- var typ ast.Expr = ast.NewIdent(recvTypeName)
+ newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}
+ var typ ast.Expr = newIdent
if !embeddedIsPtr && origRecvIsPtr {
- typ = &ast.StarExpr{X: typ}
+ newIdent.NamePos++ // '*' is one character
+ typ = &ast.StarExpr{Star: origPos, X: newIdent}
}
newField.Type = typ
-
origPos := newField.Type.Pos()
: この行が追加されました。元のレシーバ型(newField.Type
)のASTノードが持つ開始位置情報を取得し、origPos
に保存します。この位置情報は、新しいASTノードを作成する際に、元のソースコード上の位置を正確に引き継ぐために使用されます。 -
newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}
:ast.NewIdent
の代わりに、直接ast.Ident
構造体を初期化しています。ここで、NamePos
フィールドに先ほど取得したorigPos
を明示的に設定しています。これにより、新しいレシーバ型の名前(例:MyType
)がソースコード上のどこから始まるかという情報が正確に保持されます。 -
newIdent.NamePos++ // '*' is one character
: この行は、if !embeddedIsPtr && origRecvIsPtr
の条件ブロック内に追加されました。この条件は、元のレシーバがポインタ型(例:*T
)であり、かつ埋め込み型がポインタではない(つまり、T
から*T
へとレシーバ型が変更される)場合に真となります。 ポインタ型の場合、*
記号がレシーバ型の名前の前に付くため、名前自体の開始位置は*
の分だけ1文字後ろにずれます。このnewIdent.NamePos++
は、そのずれを正確に反映させるための調整です。 -
typ = &ast.StarExpr{Star: origPos, X: newIdent}
: ポインタ式ast.StarExpr
を作成する際に、Star
フィールドにorigPos
を設定しています。これにより、ポインタ演算子*
がソースコード上のどこに位置するかという情報が正確に保持されます。
これらの変更により、go/doc
が埋め込み型メソッドのレシーバ型を処理する際に生成するASTノードが、ソースコード上の正確な位置情報を持つようになります。これは、go/doc
が生成するドキュメントの正確性を保証し、Goツールチェインの他の部分との整合性を保つ上で非常に重要です。
関連リンク
- Go CL 7628045: https://golang.org/cl/7628045
参考にした情報源リンク
- Go言語の公式ドキュメント (
go/doc
,go/ast
パッケージに関する情報) - Go言語のソースコード (
src/pkg/go/doc/reader.go
および関連ファイル) - 抽象構文木 (AST) に関する一般的な知識
- Go言語のレシーバ型と埋め込み型に関する知識
- Go言語の
token.Pos
の概念