[インデックス 1956] ファイルの概要
このコミットは、usr/gri/pretty/docprinter.go
ファイルに対する変更です。このファイルは、Go言語のソースコードからドキュメンテーションを生成するツールの一部であると推測されます。具体的には、関数やメソッドの情報をドキュメント構造に追加するロジックが含まれています。
コミット
- don't show methods of non-exported types
(even if the methods are exported)
R=rsc
OCL=27056
CL=27056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bfea141ca847e00119a2d897288996dc09289563
元コミット内容
- don't show methods of non-exported types
(even if the methods are exported)
R=rsc
OCL=27056
CL=27056
変更の背景
このコミットの背景には、Go言語における「エクスポート」の概念と、ドキュメンテーション生成の整合性に関する設計思想があります。Go言語では、識別子(変数、関数、型、メソッドなど)の名前が大文字で始まる場合、その識別子はパッケージ外からアクセス可能(エクスポートされている)になります。小文字で始まる場合はパッケージ内でのみアクセス可能(エクスポートされていない)です。
この変更が行われる前は、エクスポートされていない(つまり、パッケージ内部でのみ使用されることを意図された)型に属するメソッドであっても、そのメソッド自体がエクスポートされていれば、ドキュメンテーションに表示されてしまう可能性がありました。これは、Goのカプセル化の原則に反する挙動です。パッケージの内部実装の詳細が、外部に公開されるドキュメントを通じて漏洩してしまうことになります。
このコミットは、この不整合を修正し、エクスポートされていない型に属するメソッドは、たとえそのメソッド自体がエクスポートされていても、ドキュメンテーションには表示しないという厳格なルールを適用するために導入されました。これにより、Goのエクスポートルールとカプセル化の原則がドキュメンテーション生成においても一貫して適用されるようになりました。
前提知識の解説
-
Go言語におけるエクスポートの概念: Go言語では、識別子の可視性(スコープ)は、その名前の先頭文字が大文字か小文字かによって決まります。
- 大文字で始まる識別子: パッケージ外からアクセス可能です。これを「エクスポートされている」と呼びます。例えば、
MyStruct
、MyFunction
、MyMethod
など。 - 小文字で始まる識別子: パッケージ内でのみアクセス可能です。これを「エクスポートされていない」と呼びます。例えば、
myStruct
、myFunction
、myMethod
など。 このルールは、Goのモジュール性、カプセル化、そしてAPI設計のシンプルさを促進するために非常に重要です。
- 大文字で始まる識別子: パッケージ外からアクセス可能です。これを「エクスポートされている」と呼びます。例えば、
-
Go言語のメソッド: メソッドは、特定の型に関連付けられた関数です。Goでは、レシーバ引数(
func (r ReceiverType) MethodName(...)
のように定義されるr ReceiverType
の部分)を持つことで、その型の値に対して操作を行うことができます。メソッドも関数と同様に、名前の先頭文字によってエクスポートされるかどうかが決まります。 -
Go言語のドキュメンテーション生成: Go言語には、ソースコードから自動的にドキュメンテーションを生成する仕組みがあります。最も一般的なツールは
go doc
コマンドです。このツールは、ソースコードを解析し、エクスポートされた識別子(パッケージ、関数、型、メソッドなど)に関するコメントやシグネチャを抽出し、整形されたドキュメントとして表示します。このコミットが2009年のものであることから、これはGo言語の初期段階におけるドキュメンテーション生成ツール(現在のgo doc
コマンドの前身や、それに類する内部ツール)の挙動に関する変更であると推測されます。 -
抽象構文木 (AST): コンパイラやリンター、ドキュメンテーションツールなどは、ソースコードを直接テキストとして扱うのではなく、その構造を表現する抽象構文木(AST: Abstract Syntax Tree)に変換して処理します。Goの
go/ast
パッケージは、GoプログラムのASTを表現するための型を提供します。ast.FuncDecl
は、ASTにおける関数宣言(メソッド宣言も含む)を表すノードです。
技術的詳細
このコミットは、usr/gri/pretty/docprinter.go
ファイル内のPackageDoc
構造体のaddFunc
メソッドのロジックを変更しています。addFunc
メソッドは、Goのソースコードから解析された関数宣言(ast.FuncDecl
)を受け取り、それをパッケージのドキュメント構造(PackageDoc
)に追加する役割を担っています。
変更の核心は、メソッド(レシーバを持つ関数)の処理方法にあります。
-
メソッドの識別:
if fun.Recv != nil
の条件によって、現在のast.FuncDecl
がメソッドであるかどうかが判断されます。fun.Recv
はレシーバの情報を保持しており、これがnil
でなければメソッドです。 -
レシーバ型の検索: メソッドの場合、
doc.lookupTypeDoc(fun.Recv.Type)
が呼び出され、メソッドのレシーバ型に対応するtypeDoc
(ドキュメント構造における型を表すオブジェクト)を検索します。lookupTypeDoc
は、その型がエクスポートされている場合にのみtypeDoc
を返すと推測されます。エクスポートされていない型の場合、nil
を返します。
-
変更前のロジック: 変更前は、
if typ != nil
(レシーバ型がエクスポートされている場合)のブロック内で、メソッドがtyp.methods[name] = fdoc
によってtypeDoc
のメソッドマップに追加された後、すぐにreturn
していました。これは、エクスポートされた型のメソッドのみをドキュメントに追加し、それ以外の処理は行わないという意図があったように見えます。しかし、このロジックでは、エクスポートされていない型に属するメソッドが、何らかの理由でtyp != nil
の条件を満たしてしまった場合に、意図せずドキュメントに追加されてしまう可能性がありました。 -
変更後のロジック:
if typ != nil
ブロック内のreturn
文が削除されました。これにより、レシーバ型がエクスポートされている場合でも、メソッドがtyp.methods
に追加された後、処理は継続されます。- 重要なのは、
// if the type wasn't found, it wasn't exported
という新しいコメントが追加された点です。これは、lookupTypeDoc
がnil
を返した場合(つまり、レシーバ型がエクスポートされていない場合)、そのメソッドはtyp.methods
に追加されず、結果としてドキュメントにも表示されないというロジックを明確にしています。 - この変更により、
typ != nil
のチェックが、メソッドをドキュメントに追加するかどうかの最終的な判断基準となります。レシーバ型がエクスポートされていない限り、そのメソッドはドキュメントに追加されません。
この修正は、Go言語のドキュメンテーション生成が、言語のエクスポートルールとカプセル化の原則に厳密に従うようにするための重要なステップでした。これにより、ユーザーは公開されたAPIのみをドキュメントを通じて確認できるようになり、内部実装の詳細に惑わされることがなくなります。
コアとなるコードの変更箇所
--- a/usr/gri/pretty/docprinter.go
+++ b/usr/gri/pretty/docprinter.go
@@ -129,11 +129,14 @@ func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) {
var typ *typeDoc;
if fun.Recv != nil {
// method
+ // (all receiver types must be declared before they are used)
typ = doc.lookupTypeDoc(fun.Recv.Type);
if typ != nil {
+ // type found (i.e., exported)
typ.methods[name] = fdoc;
- return;
}
+ // if the type wasn't found, it wasn't exported
+
} else {
// perhaps a factory function
// determine result type, if any
@@ -148,11 +151,10 @@ func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) {
}
}
}
+
+ // ordinary function
+ doc.funcs[name] = fdoc;
}
- // TODO other heuristics (e.g. name is "NewTypename"?)
-
- // ordinary function
- doc.funcs[name] = fdoc;
}
コアとなるコードの解説
上記の差分は、addFunc
メソッド内のメソッド処理ロジックに焦点を当てています。
-
旧コード (
-
で始まる行):if typ != nil { typ.methods[name] = fdoc; return; // ここで処理が終了していた }
この部分では、レシーバ型(
typ
)がnil
でない(つまり、エクスポートされている)場合に、メソッドをtyp.methods
マップに追加し、すぐにaddFunc
メソッドからreturn
していました。これにより、エクスポートされた型のメソッドのみが処理され、それ以外の関数やメソッドは後続のロジックで処理されるという意図があったと考えられます。しかし、このreturn
の存在が、エクスポートされていない型に属するメソッドが誤ってドキュメントに追加される可能性を残していました。 -
新コード (
+
で始まる行):// (all receiver types must be declared before they are used) typ = doc.lookupTypeDoc(fun.Recv.Type); if typ != nil { // type found (i.e., exported) typ.methods[name] = fdoc; } // if the type wasn't found, it wasn't exported
変更点として、
if typ != nil
ブロック内のreturn;
が削除されています。これにより、レシーバ型がエクスポートされている場合でも、メソッドがtyp.methods
に追加された後、addFunc
メソッドの処理は継続されます。 そして、新しいコメント// if the type wasn't found, it wasn't exported
が追加され、lookupTypeDoc
がnil
を返した場合(レシーバ型がエクスポートされていない場合)には、typ.methods[name] = fdoc
の行が実行されないため、そのメソッドはドキュメントに追加されないという意図が明確にされています。また、メソッド処理ブロックの後にあった通常の関数処理ロジック(
doc.funcs[name] = fdoc;
)が、else
ブロック内に移動され、メソッドと通常の関数の処理がより明確に分離されました。これにより、メソッドとして処理されたものは、通常の関数としては扱われないというロジックが強化されています。
この変更により、ドキュメンテーション生成ツールは、Go言語のエクスポートルールに厳密に従い、エクスポートされていない型に属するメソッドは、たとえそのメソッド自体がエクスポートされていても、ドキュメントには含めないようになりました。これは、GoのAPI設計とカプセル化の原則を尊重した、より堅牢なドキュメンテーション生成を実現するための修正です。
関連リンク
- Go言語のパッケージとエクスポートのルールに関する公式ドキュメント(Go言語のバージョンによって内容が異なる可能性がありますが、基本的な概念は共通です):
- Go言語のメソッドに関する公式ドキュメント:
- Go言語のドキュメンテーションツール
go doc
に関する情報:
参考にした情報源リンク
- Go言語の公式ドキュメント (go.dev)
- Go言語のソースコード (GitHub: golang/go)
- Go言語におけるエクスポートルールとカプセル化に関する一般的な知識
- 抽象構文木 (AST) に関する一般的なプログラミング知識
- コミットメッセージと差分から読み取れる情報