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

[インデックス 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言語および関連ツールの概念を理解しておく必要があります。

  1. go/doc パッケージ: Go言語の標準ライブラリの一部であり、Goのソースコードからドキュメンテーションを生成するためのツールです。go doc コマンドや godoc サーバーなどがこのパッケージを利用して、コードコメントや宣言からAPIドキュメントを生成します。正確なドキュメントを生成するためには、ソースコードの構造(特に型、メソッド、関数など)を正確に解析する必要があります。

  2. 抽象構文木(Abstract Syntax Tree: AST): プログラムのソースコードを抽象的な構文構造で表現した木構造のデータ構造です。Go言語では、go/ast パッケージがソースコードを解析してASTを構築する機能を提供します。コンパイラやリンター、コード分析ツール、そして go/doc のようなドキュメンテーションツールは、このASTを利用してコードの構造や意味を理解します。

  3. ast.NodePos() メソッド: go/ast パッケージのAST内の各ノード(例えば、識別子、式、宣言など)は、ast.Node インターフェースを実装しています。このインターフェースには Pos() メソッドが含まれており、これはソースコード内でのノードの開始位置(バイトオフセット)を返します。この位置情報は、エラー報告、コードの整形、そしてドキュメンテーション生成において、要素がソースコードのどこに存在するかを正確に特定するために非常に重要です。

  4. ast.Ident: go/ast パッケージにおける識別子(identifier)を表すASTノードです。変数名、関数名、型名などがこれに該当します。ast.Ident 構造体には、識別子の名前(Name フィールド)と、その名前がソースコード内で始まる位置(NamePos フィールド)が含まれます。

  5. ast.StarExpr: go/ast パッケージにおけるポインタ型(*T)を表すASTノードです。例えば、*MyType のような型は ast.StarExpr として表現されます。この構造体には、ポインタ演算子 * の位置(Star フィールド)と、ポインタが指す型(X フィールド)が含まれます。

  6. レシーバ型(Receiver Type)と埋め込み型(Embedded Types): Go言語では、メソッドは特定の型に関連付けられます。この型をレシーバ型と呼びます。例えば、func (r MyType) MethodName() {} の場合、MyType がレシーバ型です。 埋め込み型は、構造体内に他の型をフィールド名なしで宣言することで、その型のメソッドを「継承」するGoの機能です。例えば、type MyStruct struct { OtherStruct; int } の場合、MyStructOtherStruct のメソッドを直接呼び出すことができます。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.NewIdentast.StarExpr の作成時に、元のレシーバ型の位置情報を適切に引き継いでいませんでした。特に、ast.IdentNamePos フィールドや ast.StarExprStar フィールドがデフォルト値(0)のままになってしまう可能性がありました。

修正後のコードでは、以下の点が改善されています。

  1. 元の位置情報の保持: origPos := newField.Type.Pos() を追加し、元のレシーバ型のASTノードの開始位置を origPos に保存しています。これは、新しいASTノードを作成する際に、元のソースコード上の位置情報を引き継ぐために使用されます。

  2. ast.IdentNamePos の設定: newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName} 新しい ast.Ident を作成する際に、NamePos フィールドに origPos を明示的に設定しています。これにより、レシーバ型の名前(例: MyType)がソースコード上のどこから始まるかという情報が正確に保持されます。

  3. ポインタ型の場合の ast.Ident.NamePos の調整: if !embeddedIsPtr && origRecvIsPtr { newIdent.NamePos++ } この行は、元のレシーバがポインタ型(*MyType)であり、かつ埋め込み型がポインタではない(つまり、MyType から *MyType へとレシーバ型が変更される)場合に適用されます。ポインタ型の場合、* 記号がレシーバ型の名前の前に付くため、名前自体の開始位置は * の分だけ1文字後ろにずれます。この newIdent.NamePos++ は、そのずれを正確に反映させるための調整です。

  4. ast.StarExprStar フィールドの設定: 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言語の公式ドキュメント (go/doc, go/ast パッケージに関する情報)
  • Go言語のソースコード (src/pkg/go/doc/reader.go および関連ファイル)
  • 抽象構文木 (AST) に関する一般的な知識
  • Go言語のレシーバ型と埋め込み型に関する知識
  • Go言語の token.Pos の概念