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

[インデックス 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/#AssignStmtAssignStmtリンクをクリックした場合)。

また、不要な場合は偽の宣言を作成しないようにします。

変更の背景

このコミットは、Go言語のドキュメンテーションツールであるgodocの表示に関するユーザーエクスペリエンスの改善を目的としています。具体的には、Goのソースコードで複数の型がまとめて宣言されている「グループ化された型宣言」の扱いに関する問題に対処しています。

Go言語では、以下のように複数の型を括弧で囲んで一度に宣言することができます。

type (
    MyInt int
    MyString string
)

go/docパッケージは、このようなグループ宣言を解析する際に、内部的にMyIntMyStringそれぞれを独立した「偽の(fake)個別の宣言」として扱います。これは、各型が独立してドキュメント化され、godoc上で個別にリンク可能であるべきだからです。

しかし、この「偽の宣言」を生成する際に、その宣言のソースコード上の開始位置(TokPos)として、グループ宣言全体の開始位置、つまりtypeキーワードの位置を誤って使用していました。この結果、godocがWebページ上で各型のドキュメントを表示し、そのソースコードへのリンク(クリックすると該当コードがハイライトされる機能)を提供した際に、例えばMyIntのリンクをクリックしても、ハイライトされる範囲がtypeキーワードからMyStringの終わりまで、というように過度に広くなってしまう問題がありました。コミットメッセージでは、http://golang.org/pkg/go/ast/#AssignStmtの例が挙げられており、これはgo/astパッケージ内のAssignStmt型がグループ宣言の一部であったために、同様の問題が発生していたことを示唆しています。

この不正確なハイライトは、ユーザーが特定の型の定義を素早く見つけたい場合に混乱を招き、godocの利便性を損なっていました。また、単一の型宣言であっても、不要な場合に偽の宣言が生成されることも、処理の効率性やコードの正確性の観点から改善の余地がありました。

このコミットは、これらの問題を解決し、godocがより正確で直感的なソースコードのハイライトとナビゲーションを提供できるようにすることを目的としています。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の概念とツールに関する知識が必要です。

  1. Go言語の抽象構文木 (AST: Abstract Syntax Tree): Goコンパイラやツールは、Goのソースコードを解析して、その構造を木構造で表現したASTを生成します。このASTは、プログラムの論理的な構造を反映しており、go/astパッケージで定義されている構造体によって表現されます。例えば、ast.GenDeclは一般的な宣言(import, const, type, var)を表し、ast.TypeSpectype宣言における個々の型仕様を表します。

    • ast.GenDecl: type ( A int; B string ) のようなグループ宣言全体を表現するASTノード。
    • ast.TypeSpec: A intB string のような、グループ宣言内の個々の型定義を表現するASTノード。
    • token.Pos: ソースコード内の特定の開始位置を示す型。ASTノードは通常、ソースコード上の開始位置と終了位置を持ちます。TokPosは、そのノードの主要なトークン(例えばtypeキーワード)の開始位置を指します。
  2. go/docパッケージ: Goの標準ライブラリの一部であり、Goのソースコードからドキュメンテーションコメントや宣言情報を抽出し、構造化されたデータとして提供します。godocツールはこのパッケージを利用してドキュメントを生成します。go/docは、ASTを走査し、パッケージ、関数、型、変数などのドキュメントを構築します。

  3. godocツール: Go言語の公式ドキュメンテーションツールです。go/docパッケージの機能を利用して、Goのソースコードから自動的にドキュメントを生成し、Webサーバーとして提供したり、コマンドラインで表示したりできます。godocは、生成されたドキュメント内でソースコードへのリンクを提供し、クリックすると該当するコードがハイライトされる機能を持っています。このハイライトの正確性が、今回のコミットの主要な焦点です。

  4. 型宣言の構文: Go言語には、型を宣言するいくつかの方法があります。

    • 単一の型宣言: type MyType int
    • グループ化された型宣言:
      type (
          TypeA int
          TypeB string
      )
      

    このコミットは、特に後者のグループ化された型宣言の処理に焦点を当てています。

技術的詳細

このコミットの技術的な核心は、go/docパッケージがASTを解析し、ドキュメンテーションを生成する際に、型宣言のソースコード上の「範囲」をどのように決定するか、という点にあります。

src/pkg/go/doc/reader.goreadFileメソッドは、Goのソースファイル(*ast.File)を読み込み、そのASTを走査してドキュメンテーション情報を抽出します。このメソッド内には、token.TYPE(型宣言)を処理するcaseブロックがあります。

変更前の実装では、グループ化された型宣言(例: type ( A int; B string ))に遭遇すると、go/docABそれぞれに対して、あたかも独立した宣言であるかのように「偽のast.GenDecl」オブジェクトを内部的に作成していました。この偽のGenDeclTokPos(トークンの開始位置)には、元のグループ宣言全体のtypeキーワードの位置(d.Pos())が設定されていました。

この設計には以下の問題がありました。

  • godocがソースコードのハイライトを行う際、このTokPosを基に範囲を決定するため、個々の型(AB)のリンクをクリックしても、ハイライトされる範囲がtypeキーワードからグループ宣言の終わりまで、というように不必要に広くなってしまいました。これは、godocが内部的にposLink_urlFuncのような関数を使用して、TokPosからURLとハイライト範囲を生成していたためです。

このコミットでは、この問題を解決するために以下の2つの主要な変更が導入されました。

  1. 不要な偽の宣言の生成抑制:

    • 単一の型宣言であり、かつ括弧で囲まれていない一般的なケース(例: type MyType int)の場合、go/docはもはや偽のast.GenDeclを作成しません。
    • これは、if len(d.Specs) == 1 && !d.Lparen.IsValid() という条件で判定されます。d.SpecsGenDeclに含まれる宣言のリストであり、d.Lparen.IsValid()は宣言が括弧で囲まれているかどうかを示します。この条件が真の場合、直接r.readType(d, s)が呼び出され、偽の宣言の生成がスキップされます。
    • これにより、処理のオーバーヘッドが削減され、より自然なドキュメンテーションの処理が可能になります。ただし、単一の宣言であっても括弧で囲まれている場合(例: type ( MyType int ))は、go/docが型宣言を常に括弧なしで表示するという一貫性を保つために、引き続き偽の宣言が作成されます。
  2. 偽の宣言のTokPosの正確な設定:

    • グループ化された型宣言、または括弧で囲まれた単一の型宣言の場合、引き続き偽のast.GenDeclが作成されます。
    • しかし、この偽のGenDeclTokPosには、元のグループ宣言全体のtypeキーワードの位置(d.Pos())ではなく、個々のTypeSpec(例: A intB 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.goreadFile関数内の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)
        }
    }
  1. 新規追加された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が型宣言を常に括弧なしで表示するという一貫性を保つためです。
  2. 既存のループと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 intAB stringB)の開始位置を指します。
    • この変更により、godocfake宣言のソースコード範囲を決定する際に、個々の型名(AB)の開始位置を正確に参照できるようになります。これにより、godoc上でAをクリックした際にA intのみがハイライトされ、Bをクリックした際にB stringのみがハイライトされるようになり、ユーザーエクスペリエンスが大幅に向上します。
    • コメントには、この変更がsrc/cmd/godoc/godoc.goposLink_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" で検索)