[インデックス 12208] ファイルの概要
このコミットは、Go言語の標準ライブラリであるgo/doc
パッケージ内のsrc/pkg/go/doc/reader.go
ファイルに対する変更です。go/doc
パッケージは、Goのソースコードからドキュメンテーションを抽出し、godoc
ツールなどで利用可能な形式で提供する役割を担っています。reader.go
ファイルは、このパッケージにおいてソースコードの抽象構文木(AST)を読み込み、ドキュメンテーション情報を解析する主要なロジックを含んでいます。
コミット
- コミットハッシュ:
0a33b703e61c89cc883304eb627826c875aa4bf2
- 作者: Robert Griesemer gri@golang.org
- 日付: Fri Feb 24 13:44:22 2012 -0800
- コミットメッセージ:
go/doc, godoc: fix range of type declarations For grouped type declarations, go/doc introduces fake individual declarations. Don't use the original location of the "type" keyword because it will lead to an overly large source code range for that fake declaration, and thus an overly large selection shown via godoc (e.g.: click on the AssignStmt link for: http://golang.org/pkg/go/ast/#AssignStmt ). Also: Don't create a fake declaration if not needed. R=golang-dev, rsc CC=golang-dev https://golang.org/cl/5694061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0a33b703e61c89cc883304eb627826c875aa4bf2
元コミット内容
go/doc
およびgodoc
ツールにおける型宣言の範囲の修正。
グループ化された型宣言の場合、go/doc
は偽の個別の宣言を導入します。この偽の宣言に対して、元のtype
キーワードの位置を使用しないようにします。なぜなら、それが偽の宣言に対して過度に大きなソースコード範囲をもたらし、結果としてgodoc
を介して表示される選択範囲が過度に大きくなるためです(例: http://golang.org/pkg/go/ast/#AssignStmt
のAssignStmt
リンクをクリックした場合)。
また、不要な場合は偽の宣言を作成しないようにします。
変更の背景
このコミットは、Go言語のドキュメンテーションツールであるgodoc
の表示に関するユーザーエクスペリエンスの改善を目的としています。具体的には、Goのソースコードで複数の型がまとめて宣言されている「グループ化された型宣言」の扱いに関する問題に対処しています。
Go言語では、以下のように複数の型を括弧で囲んで一度に宣言することができます。
type (
MyInt int
MyString string
)
go/doc
パッケージは、このようなグループ宣言を解析する際に、内部的にMyInt
とMyString
それぞれを独立した「偽の(fake)個別の宣言」として扱います。これは、各型が独立してドキュメント化され、godoc
上で個別にリンク可能であるべきだからです。
しかし、この「偽の宣言」を生成する際に、その宣言のソースコード上の開始位置(TokPos
)として、グループ宣言全体の開始位置、つまりtype
キーワードの位置を誤って使用していました。この結果、godoc
がWebページ上で各型のドキュメントを表示し、そのソースコードへのリンク(クリックすると該当コードがハイライトされる機能)を提供した際に、例えばMyInt
のリンクをクリックしても、ハイライトされる範囲がtype
キーワードからMyString
の終わりまで、というように過度に広くなってしまう問題がありました。コミットメッセージでは、http://golang.org/pkg/go/ast/#AssignStmt
の例が挙げられており、これはgo/ast
パッケージ内のAssignStmt
型がグループ宣言の一部であったために、同様の問題が発生していたことを示唆しています。
この不正確なハイライトは、ユーザーが特定の型の定義を素早く見つけたい場合に混乱を招き、godoc
の利便性を損なっていました。また、単一の型宣言であっても、不要な場合に偽の宣言が生成されることも、処理の効率性やコードの正確性の観点から改善の余地がありました。
このコミットは、これらの問題を解決し、godoc
がより正確で直感的なソースコードのハイライトとナビゲーションを提供できるようにすることを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念とツールに関する知識が必要です。
-
Go言語の抽象構文木 (AST: Abstract Syntax Tree): Goコンパイラやツールは、Goのソースコードを解析して、その構造を木構造で表現したASTを生成します。このASTは、プログラムの論理的な構造を反映しており、
go/ast
パッケージで定義されている構造体によって表現されます。例えば、ast.GenDecl
は一般的な宣言(import
,const
,type
,var
)を表し、ast.TypeSpec
はtype
宣言における個々の型仕様を表します。ast.GenDecl
:type ( A int; B string )
のようなグループ宣言全体を表現するASTノード。ast.TypeSpec
:A int
やB string
のような、グループ宣言内の個々の型定義を表現するASTノード。token.Pos
: ソースコード内の特定の開始位置を示す型。ASTノードは通常、ソースコード上の開始位置と終了位置を持ちます。TokPos
は、そのノードの主要なトークン(例えばtype
キーワード)の開始位置を指します。
-
go/doc
パッケージ: Goの標準ライブラリの一部であり、Goのソースコードからドキュメンテーションコメントや宣言情報を抽出し、構造化されたデータとして提供します。godoc
ツールはこのパッケージを利用してドキュメントを生成します。go/doc
は、ASTを走査し、パッケージ、関数、型、変数などのドキュメントを構築します。 -
godoc
ツール: Go言語の公式ドキュメンテーションツールです。go/doc
パッケージの機能を利用して、Goのソースコードから自動的にドキュメントを生成し、Webサーバーとして提供したり、コマンドラインで表示したりできます。godoc
は、生成されたドキュメント内でソースコードへのリンクを提供し、クリックすると該当するコードがハイライトされる機能を持っています。このハイライトの正確性が、今回のコミットの主要な焦点です。 -
型宣言の構文: Go言語には、型を宣言するいくつかの方法があります。
- 単一の型宣言:
type MyType int
- グループ化された型宣言:
type ( TypeA int TypeB string )
このコミットは、特に後者のグループ化された型宣言の処理に焦点を当てています。
- 単一の型宣言:
技術的詳細
このコミットの技術的な核心は、go/doc
パッケージがASTを解析し、ドキュメンテーションを生成する際に、型宣言のソースコード上の「範囲」をどのように決定するか、という点にあります。
src/pkg/go/doc/reader.go
のreadFile
メソッドは、Goのソースファイル(*ast.File
)を読み込み、そのASTを走査してドキュメンテーション情報を抽出します。このメソッド内には、token.TYPE
(型宣言)を処理するcase
ブロックがあります。
変更前の実装では、グループ化された型宣言(例: type ( A int; B string )
)に遭遇すると、go/doc
はA
とB
それぞれに対して、あたかも独立した宣言であるかのように「偽のast.GenDecl
」オブジェクトを内部的に作成していました。この偽のGenDecl
のTokPos
(トークンの開始位置)には、元のグループ宣言全体のtype
キーワードの位置(d.Pos()
)が設定されていました。
この設計には以下の問題がありました。
godoc
がソースコードのハイライトを行う際、このTokPos
を基に範囲を決定するため、個々の型(A
やB
)のリンクをクリックしても、ハイライトされる範囲がtype
キーワードからグループ宣言の終わりまで、というように不必要に広くなってしまいました。これは、godoc
が内部的にposLink_urlFunc
のような関数を使用して、TokPos
からURLとハイライト範囲を生成していたためです。
このコミットでは、この問題を解決するために以下の2つの主要な変更が導入されました。
-
不要な偽の宣言の生成抑制:
- 単一の型宣言であり、かつ括弧で囲まれていない一般的なケース(例:
type MyType int
)の場合、go/doc
はもはや偽のast.GenDecl
を作成しません。 - これは、
if len(d.Specs) == 1 && !d.Lparen.IsValid()
という条件で判定されます。d.Specs
はGenDecl
に含まれる宣言のリストであり、d.Lparen.IsValid()
は宣言が括弧で囲まれているかどうかを示します。この条件が真の場合、直接r.readType(d, s)
が呼び出され、偽の宣言の生成がスキップされます。 - これにより、処理のオーバーヘッドが削減され、より自然なドキュメンテーションの処理が可能になります。ただし、単一の宣言であっても括弧で囲まれている場合(例:
type ( MyType int )
)は、go/doc
が型宣言を常に括弧なしで表示するという一貫性を保つために、引き続き偽の宣言が作成されます。
- 単一の型宣言であり、かつ括弧で囲まれていない一般的なケース(例:
-
偽の宣言の
TokPos
の正確な設定:- グループ化された型宣言、または括弧で囲まれた単一の型宣言の場合、引き続き偽の
ast.GenDecl
が作成されます。 - しかし、この偽の
GenDecl
のTokPos
には、元のグループ宣言全体のtype
キーワードの位置(d.Pos()
)ではなく、個々のTypeSpec
(例:A int
やB string
)の開始位置(s.Pos()
)が設定されるように変更されました。 - この変更により、
godoc
がソースコードのハイライトやリンクを生成する際に、個々の型宣言の正確な開始位置を参照できるようになります。結果として、ユーザーがgodoc
上で特定の型をクリックした際に、ハイライトされる範囲がその型宣言のみに限定され、より正確で直感的なナビゲーションが実現されます。
- グループ化された型宣言、または括弧で囲まれた単一の型宣言の場合、引き続き偽の
これらの変更は、go/doc
がASTを解析し、ドキュメンテーション情報を構造化する内部ロジックの改善であり、最終的にgodoc
のユーザーインターフェースにおけるソースコード表示の精度向上に貢献しています。
コアとなるコードの変更箇所
変更はsrc/pkg/go/doc/reader.go
ファイルに集中しています。
--- a/src/pkg/go/doc/reader.go
+++ b/src/pkg/go/doc/reader.go
@@ -432,6 +432,17 @@ func (r *reader) readFile(src *ast.File) {
r.readValue(d)
case token.TYPE:
// types are handled individually
+ if len(d.Specs) == 1 && !d.Lparen.IsValid() {
+ // common case: single declaration w/o parentheses
+ // (if a single declaration is parenthesized,
+ // create a new fake declaration below, so that
+ // go/doc type declarations always appear w/o
+ // parentheses)
+ if s, ok := d.Specs[0].(*ast.TypeSpec); ok {
+ r.readType(d, s)
+ }
+ break
+ }
for _, spec := range d.Specs {
if s, ok := spec.(*ast.TypeSpec); ok {
// use an individual (possibly fake) declaration
@@ -439,8 +450,13 @@ func (r *reader) readFile(src *ast.File) {
// gets to (re-)use the declaration documentation
// if there's none associated with the spec itself
fake := &ast.GenDecl{
- Doc: d.Doc,
- TokPos: d.Pos(),
+ Doc: d.Doc,
+ // don't use the existing TokPos because it
+ // will lead to the wrong selection range for
+ // the fake declaration if there are more
+ // than one type in the group (this affects
+ // src/cmd/godoc/godoc.go's posLink_urlFunc)
+ TokPos: s.Pos(),
Tok: token.TYPE,
Specs: []ast.Spec{s},
}
コアとなるコードの解説
変更されたsrc/pkg/go/doc/reader.go
のreadFile
関数内のcase token.TYPE:
ブロックについて解説します。
case token.TYPE:
// types are handled individually
// (1) 新規追加されたifブロック
if len(d.Specs) == 1 && !d.Lparen.IsValid() {
// common case: single declaration w/o parentheses
// (if a single declaration is parenthesized,
// create a new fake declaration below, so that
// go/doc type declarations always appear w/o
// parentheses)
if s, ok := d.Specs[0].(*ast.TypeSpec); ok {
r.readType(d, s) // 偽の宣言を生成せず、直接読み込む
}
break // このケースはここで処理を終了
}
// (2) 既存のループとfake宣言のTokPosの変更
for _, spec := range d.Specs {
if s, ok := spec.(*ast.TypeSpec); ok {
// use an individual (possibly fake) declaration
// gets to (re-)use the declaration documentation
// if there's none associated with the spec itself
fake := &ast.GenDecl{
Doc: d.Doc,
// don't use the existing TokPos because it
// will lead to the wrong selection range for
// the fake declaration if there are more
// than one type in the group (this affects
// src/cmd/godoc/godoc.go's posLink_urlFunc)
TokPos: s.Pos(), // ここが変更点: 個々のTypeSpecの開始位置を使用
Tok: token.TYPE,
Specs: []ast.Spec{s},
}
r.readType(fake, s)
}
}
-
新規追加された
if
ブロック:if len(d.Specs) == 1 && !d.Lparen.IsValid()
: この条件は、現在のGenDecl
d
が、単一の型宣言(len(d.Specs) == 1
)であり、かつ括弧で囲まれていない(!d.Lparen.IsValid()
)場合に真となります。これは、type MyType int
のような最も一般的な単一の型宣言のケースを指します。- この条件が真の場合、内部で
r.readType(d, s)
が直接呼び出されます。これは、偽のast.GenDecl
オブジェクトを新たに作成する手間を省き、元のGenDecl
d
と個々のTypeSpec
s
を直接readType
関数に渡すことを意味します。これにより、不要な偽の宣言の生成が抑制され、処理が効率化されます。 break
文により、このケースの処理はここで終了し、後続のループ処理はスキップされます。- コメントにあるように、単一の宣言であっても括弧で囲まれている場合(例:
type ( MyType int )
)は、このif
ブロックには入らず、後続のループで偽の宣言が作成されます。これは、go/doc
が型宣言を常に括弧なしで表示するという一貫性を保つためです。
-
既存のループと
fake
宣言のTokPos
の変更:- この
for
ループは、グループ化された型宣言(例:type ( A int; B string )
)や、上記if
ブロックの条件に合致しなかった単一の括弧付き型宣言を処理します。 - ループ内で、各
TypeSpec
s
に対して、新しいast.GenDecl
オブジェクト(fake
)が作成されます。これが「偽の個別の宣言」です。 TokPos: s.Pos(),
: ここがこのコミットの最も重要な変更点です。変更前はTokPos: d.Pos(),
でした。d.Pos()
は、元のGenDecl
(グループ宣言全体)のtype
キーワードの開始位置を指していました。s.Pos()
は、現在処理している個々のTypeSpec
(例:A int
のA
、B string
のB
)の開始位置を指します。
- この変更により、
godoc
がfake
宣言のソースコード範囲を決定する際に、個々の型名(A
やB
)の開始位置を正確に参照できるようになります。これにより、godoc
上でA
をクリックした際にA int
のみがハイライトされ、B
をクリックした際にB string
のみがハイライトされるようになり、ユーザーエクスペリエンスが大幅に向上します。 - コメントには、この変更が
src/cmd/godoc/godoc.go
のposLink_urlFunc
に影響すると明記されており、これはgodoc
がソースコードのリンクを生成する際の内部的な関数が、このTokPos
の値を参照していることを示しています。
- この
これらの変更により、go/doc
は型宣言のドキュメンテーション情報をより正確に抽出し、godoc
はユーザーに対してより精度の高いソースコードのハイライトとナビゲーションを提供できるようになりました。
関連リンク
- Gerrit Code Review:
https://golang.org/cl/5694061
このコミットの元のコードレビューページです。議論や追加のコンテキストが含まれている可能性があります。
参考にした情報源リンク
- Go言語の公式ドキュメント:
https://go.dev/doc/
go/ast
パッケージのドキュメント:https://pkg.go.dev/go/ast
go/doc
パッケージのドキュメント:https://pkg.go.dev/go/doc
godoc
ツールの情報:https://go.dev/blog/godoc
- Go言語のASTに関する一般的な情報源やチュートリアル(例: "Go AST" で検索)