[インデックス 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
の実装には、以下のような問題がありました。
- アンエクスポートされた型への関連付け:
go/doc
は、定数や変数をその型に基づいてグループ化し、ドキュメントに表示する機能を持っています。しかし、もしエクスポートされた定数や変数が、アンエクスポートされた型(例えばtype notExported int
)をその型として持っていた場合、go/doc
はそのアンエクスポートされた型を「ドキュメントに表示すべきではない」と判断し、結果としてその型に関連付けられたエクスポートされた定数や変数もドキュメントから漏れてしまうというバグがありました。 - 可視性の誤判定:
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のドキュメンテーション生成プロセスは以下のようになります。
- ソースコードの準備: ドキュメントを生成したいGoのソースファイルを用意します。
- コメントの記述: ドキュメントに含めたい情報(関数の説明、型の用途など)をGoのコメント規約に従って記述します。
go/doc
による解析:go/doc
パッケージがソースコードを読み込み、ASTを構築し、コメントとコード要素を関連付けます。この際、エクスポートされた識別子のみが対象となります。- ドキュメントの出力:
go/doc
が提供する構造化された情報に基づいて、godoc
などのツールがHTML、プレーンテキストなどの形式でドキュメントを生成します。
このコミットで修正された問題は、ステップ3の「可視性の考慮」の部分で発生していました。
技術的詳細
このコミットの技術的な核心は、src/pkg/go/doc/reader.go
ファイル内の readValue
メソッドにおける可視性チェックの追加です。
go/doc
パッケージの内部では、reader
構造体がGoのソースコードを読み込み、ドキュメント情報を構築する役割を担っています。readValue
メソッドは、ast.GenDecl
(汎用宣言、例えば const
や var
ブロック)を処理し、その中の定数や変数を適切な型に関連付けてドキュメント構造に格納します。
問題の箇所は、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)
は、以下のすべてが真である場合にのみ、定数や変数をその型に関連付けるようにします。
domName
が空ではない(関連付けるべき型名が存在する)。domName
で示される型がエクスポートされている(公開されている)。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/doc
がb.go
を処理した際に期待される出力(ゴールデンファイル)です。これらのファイルが更新されているのは、修正によってドキュメントの出力内容が変わったことを示しています。具体的には、notExported
型に関連付けられていたエクスポートされた定数や変数が、パッケージレベルのCONSTANTS
やVARIABLES
セクションに正しく表示されるようになっています。
これらのテストファイルの変更は、バグが修正され、go/doc
が期待通りに動作することを確認するための重要な要素です。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/5650078
- GitHub コミットページ: https://github.com/golang/go/commit/0a2ffb26385104613ed29bf80da56053566cdb21
参考にした情報源リンク
- コミットメッセージと差分 (
/home/orange/Project/comemo/commit_data/11855.txt
) - Go言語の公式ドキュメンテーション(Goの可視性ルール、
go/doc
パッケージの一般的な知識) - Go言語のソースコード(
src/pkg/go/doc/reader.go
の実装詳細)