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

[インデックス 11855] ファイルの概要

このコミットは、Go言語のドキュメンテーション生成ツールである go/doc パッケージにおけるバグ修正に関するものです。具体的には、エクスポートされた定数や変数が、アンエクスポートされた(非公開の)型に関連付けられている場合に、ドキュメンテーションから漏れてしまう問題を解決しています。

コミット

commit 0a2ffb26385104613ed29bf80da56053566cdb21
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Feb 13 12:24:02 2012 -0800

    go/doc: don't lose exported consts/vars with unexported type
    
    Fixes #2998.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5650078
---
 src/pkg/go/doc/reader.go           |  2 +--
 src/pkg/go/doc/testdata/b.0.golden | 28 ++++++++++++++++++++++++++++
 src/pkg/go/doc/testdata/b.1.golden | 36 +++++++++++++++++++++++++++++++++++-
 src/pkg/go/doc/testdata/b.2.golden | 28 ++++++++++++++++++++++++++++
 src/pkg/go/doc/testdata/b.go       | 28 ++++++++++++++++++++++++++++
 5 files changed, 120 insertions(+), 2 deletions(-)

diff --git a/src/pkg/go/doc/reader.go b/src/pkg/go/doc/reader.go
index 13b465bbd7..5f0643caa3 100644
--- a/src/pkg/go/doc/reader.go
+++ b/src/pkg/go/doc/reader.go
@@ -274,7 +274,7 @@ func (r *reader) readValue(decl *ast.GenDecl) {
 	// determine values list with which to associate the Value for this decl
 	values := &r.values
 	const threshold = 0.75
-	if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
+	if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) {
 		// typed entries are sufficiently frequent
 		if typ := r.lookupType(domName); typ != nil {
 			values = &typ.values // associate with that type

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/0a2ffb26385104613ed29bf80da56053566cdb21

元コミット内容

このコミットの元の内容は以下の通りです。

go/doc: don't lose exported consts/vars with unexported type

Fixes #2998.

R=rsc
CC=golang-dev
https://golang.org/cl/5650078

これは、go/doc パッケージが、アンエクスポートされた型に関連付けられたエクスポートされた定数や変数を適切にドキュメントに含めないというバグを修正するものです。Fixes #2998 は、Goプロジェクトの内部課題追跡システムにおける特定のバグ報告に対応していることを示しています。

変更の背景

Go言語では、識別子(変数、定数、関数、型など)の最初の文字が大文字である場合、その識別子はパッケージ外にエクスポートされ、小文字である場合はパッケージ内でのみ利用可能なアンエクスポートされた識別子となります。

go/doc パッケージは、Goのソースコードを解析し、そのドキュメンテーションを生成する役割を担っています。このツールは、godoc コマンドなどで利用され、Goの標準ライブラリやユーザーが作成したパッケージのドキュメントをHTML形式などで表示するために不可欠です。

このコミット以前の go/doc の実装には、以下のような問題がありました。

  1. アンエクスポートされた型への関連付け: go/doc は、定数や変数をその型に基づいてグループ化し、ドキュメントに表示する機能を持っています。しかし、もしエクスポートされた定数や変数が、アンエクスポートされた型(例えば type notExported int)をその型として持っていた場合、go/doc はそのアンエクスポートされた型を「ドキュメントに表示すべきではない」と判断し、結果としてその型に関連付けられたエクスポートされた定数や変数もドキュメントから漏れてしまうというバグがありました。
  2. 可視性の誤判定: go/doc の内部ロジックでは、ドキュメントに含めるべき要素を判断する際に、その要素が「可視(visible)」であるかどうかをチェックします。この「可視性」の判断が、アンエクスポートされた型に関連付けられたエクスポートされた識別子に対して正しく行われていなかったため、本来ドキュメントに含めるべき情報が欠落していました。

このバグは、特にライブラリ開発において、公開APIの一部であるにもかかわらず、内部的な型定義に依存しているためにドキュメントに現れないという問題を引き起こしていました。これにより、ユーザーはAPIの完全な情報を得ることができず、ライブラリの利用が困難になる可能性がありました。

前提知識の解説

Go言語の可視性(Exported vs. Unexported)

Go言語における識別子の可視性(visibility)は、その識別子の最初の文字が大文字か小文字かによって決定されます。

  • エクスポートされた識別子 (Exported Identifiers): 識別子の最初の文字が大文字の場合、その識別子はパッケージ外からアクセス可能です。これは、他のパッケージから利用できる公開APIの一部となります。例: MyFunction, MaxInt, ErrorType
  • アンエクスポートされた識別子 (Unexported Identifiers): 識別子の最初の文字が小文字の場合、その識別子は定義されたパッケージ内でのみアクセス可能です。これは、パッケージの内部実装の詳細であり、外部からは直接利用できません。例: myVariable, helperFunc, internalStruct

この可視性のルールは、Goのモジュール性(modularity)とカプセル化(encapsulation)を促進し、パッケージの内部実装が外部に漏れるのを防ぎます。

go/doc パッケージ

go/doc パッケージは、Goのソースコードからドキュメンテーションを抽出・生成するための標準ライブラリです。このパッケージは、Goの抽象構文木(AST: Abstract Syntax Tree)を解析し、コメント、関数、型、変数、定数などの情報を構造化された形式で提供します。godoc コマンドやGoの公式ドキュメントサイト(pkg.go.devなど)は、この go/doc パッケージを利用してドキュメントを生成しています。

go/doc の主な機能は以下の通りです。

  • ASTの解析: go/parser パッケージなどを用いてGoのソースファイルを解析し、ASTを構築します。
  • ドキュメントの抽出: ASTから、///* */ で記述されたコメントを抽出し、対応するコード要素(関数、型など)に関連付けます。
  • 構造化された情報の提供: 抽出された情報を doc.Package, doc.Type, doc.Func などの構造体に格納し、プログラム的にアクセスしやすい形式で提供します。
  • 可視性の考慮: エクスポートされた識別子のみをドキュメントに含めるようにフィルタリングします。

Goのドキュメンテーション生成プロセス

一般的なGoのドキュメンテーション生成プロセスは以下のようになります。

  1. ソースコードの準備: ドキュメントを生成したいGoのソースファイルを用意します。
  2. コメントの記述: ドキュメントに含めたい情報(関数の説明、型の用途など)をGoのコメント規約に従って記述します。
  3. go/doc による解析: go/doc パッケージがソースコードを読み込み、ASTを構築し、コメントとコード要素を関連付けます。この際、エクスポートされた識別子のみが対象となります。
  4. ドキュメントの出力: go/doc が提供する構造化された情報に基づいて、godoc などのツールがHTML、プレーンテキストなどの形式でドキュメントを生成します。

このコミットで修正された問題は、ステップ3の「可視性の考慮」の部分で発生していました。

技術的詳細

このコミットの技術的な核心は、src/pkg/go/doc/reader.go ファイル内の readValue メソッドにおける可視性チェックの追加です。

go/doc パッケージの内部では、reader 構造体がGoのソースコードを読み込み、ドキュメント情報を構築する役割を担っています。readValue メソッドは、ast.GenDecl(汎用宣言、例えば constvar ブロック)を処理し、その中の定数や変数を適切な型に関連付けてドキュメント構造に格納します。

問題の箇所は、readValue メソッド内で、定数や変数が特定の型に関連付けられるべきかどうかを判断するロジックにありました。

// src/pkg/go/doc/reader.go (修正前)
if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
    // typed entries are sufficiently frequent
    if typ := r.lookupType(domName); typ != nil {
        values = &typ.values // associate with that type

このコードスニペットでは、domName は定数または変数が関連付けられている可能性のある型の名前を表し、domFreq はその型が宣言内でどれだけ頻繁に出現するかを示します。threshold は、その型が宣言の主要な型であると見なすための頻度のしきい値です。

問題は、domName が空でなく、かつその型が十分に頻繁に出現する場合に、その型に関連付けを行うというロジック自体にはありませんでした。問題は、domName で示される型がアンエクスポートされた型である場合に、その型に関連付けられたエクスポートされた定数や変数がドキュメントから漏れてしまう点にありました。

go/doc は、ドキュメントに含めるべき要素を判断する際に、その要素が「可視(visible)」であるかどうかを r.isVisible() メソッドでチェックします。しかし、上記の修正前のコードでは、domName(型名)が可視であるかどうかのチェックが欠落していました。

その結果、domName がアンエクスポートされた型名であっても、domFreq がしきい値を超えていれば、そのアンエクスポートされた型に定数や変数が関連付けられてしまい、最終的に go/doc がそのアンエクスポートされた型(およびそれに紐づく定数や変数)をドキュメントに含めないという判断を下していました。

コアとなるコードの変更箇所

変更は src/pkg/go/doc/reader.go ファイルの1行に集約されています。

--- a/src/pkg/go/doc/reader.go
+++ b/src/pkg/go/doc/reader.go
@@ -274,7 +274,7 @@ func (r *reader) readValue(decl *ast.GenDecl) {
 	// determine values list with which to associate the Value for this decl
 	values := &r.values
 	const threshold = 0.75
-	if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
+	if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) {
 		// typed entries are sufficiently frequent
 		if typ := r.lookupType(domName); typ != nil {
 			values = &typ.values // associate with that type

追加されたのは r.isVisible(domName) という条件です。

コアとなるコードの解説

この変更は、readValue メソッドが定数や変数を特定の型に関連付ける際に、その型名 (domName) が**エクスポートされている(可視である)**ことを追加の条件として課すものです。

  • r.isVisible(domName): このメソッドは、与えられた名前 domName がGoの可視性ルールに従ってエクスポートされているかどうかをチェックします。つまり、domName の最初の文字が大文字であるかどうかを判断します。

修正後の条件式 if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) は、以下のすべてが真である場合にのみ、定数や変数をその型に関連付けるようにします。

  1. domName が空ではない(関連付けるべき型名が存在する)。
  2. domName で示される型がエクスポートされている(公開されている)
  3. domFreq が、その型が宣言の主要な型であると見なすためのしきい値を超えている。

この変更により、アンエクスポートされた型(例: notExported)には、たとえその型が宣言内で頻繁に出現したとしても、エクスポートされた定数や変数が関連付けられなくなります。その代わりに、これらのエクスポートされた定数や変数は、パッケージレベルの定数/変数リストに適切に配置されるようになります。

これにより、go/doc は、アンエクスポートされた型に関連付けられているために誤ってドキュメントから漏れていたエクスポートされた定数や変数を、正しくドキュメントに含めることができるようになりました。

テストケースの変更

このコミットには、src/pkg/go/doc/testdata/ ディレクトリ内の複数のテストファイルも含まれています。これらのファイルは、修正が正しく機能することを確認するためのものです。

  • src/pkg/go/doc/testdata/b.go: これは、テスト対象となるGoのソースファイルです。このファイルには、エクスポートされた定数や変数(例: C1, V, U1)が、アンエクスポートされた型 notExported に関連付けられているケースが含まれています。

    type notExported int
    
    const C notExported = 0
    
    const (
    	C1 notExported = iota
    	C2
    	c3
    	C4
    	C5
    )
    
    var V notExported
    var V1, V2, v3, V4, V5 notExported
    
    var (
    	U1, U2, u3, U4, U5 notExported
    	u6                 notExported
    	U7                 notExported = 7
    )
    
    func F1() notExported {}
    func f2() notExported {}
    

    このファイルでは、C1, C2, C4, C5 (定数)、V, V1, V2, V4, V5, U1, U2, U4, U5, U7 (変数)、F1 (関数) がエクスポートされており、これらが notExported 型に関連付けられています。修正前はこれらがドキュメントから漏れていましたが、修正後は適切にドキュメントに表示されるようになります。

  • src/pkg/go/doc/testdata/b.0.golden, src/pkg/go/doc/testdata/b.1.golden, src/pkg/go/doc/testdata/b.2.golden: これらは、go/docb.go を処理した際に期待される出力(ゴールデンファイル)です。これらのファイルが更新されているのは、修正によってドキュメントの出力内容が変わったことを示しています。具体的には、notExported 型に関連付けられていたエクスポートされた定数や変数が、パッケージレベルの CONSTANTSVARIABLES セクションに正しく表示されるようになっています。

これらのテストファイルの変更は、バグが修正され、go/doc が期待通りに動作することを確認するための重要な要素です。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分 (/home/orange/Project/comemo/commit_data/11855.txt)
  • Go言語の公式ドキュメンテーション(Goの可視性ルール、go/doc パッケージの一般的な知識)
  • Go言語のソースコード(src/pkg/go/doc/reader.go の実装詳細)