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

[インデックス 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言語では、識別子の可視性(スコープ)は、その名前の先頭文字が大文字か小文字かによって決まります。

    • 大文字で始まる識別子: パッケージ外からアクセス可能です。これを「エクスポートされている」と呼びます。例えば、MyStructMyFunctionMyMethodなど。
    • 小文字で始まる識別子: パッケージ内でのみアクセス可能です。これを「エクスポートされていない」と呼びます。例えば、myStructmyFunctionmyMethodなど。 このルールは、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)に追加する役割を担っています。

変更の核心は、メソッド(レシーバを持つ関数)の処理方法にあります。

  1. メソッドの識別: if fun.Recv != nilの条件によって、現在のast.FuncDeclがメソッドであるかどうかが判断されます。fun.Recvはレシーバの情報を保持しており、これがnilでなければメソッドです。

  2. レシーバ型の検索: メソッドの場合、doc.lookupTypeDoc(fun.Recv.Type)が呼び出され、メソッドのレシーバ型に対応するtypeDoc(ドキュメント構造における型を表すオブジェクト)を検索します。

    • lookupTypeDocは、その型がエクスポートされている場合にのみtypeDocを返すと推測されます。エクスポートされていない型の場合、nilを返します。
  3. 変更前のロジック: 変更前は、if typ != nil(レシーバ型がエクスポートされている場合)のブロック内で、メソッドがtyp.methods[name] = fdocによってtypeDocのメソッドマップに追加された後、すぐにreturnしていました。これは、エクスポートされた型のメソッドのみをドキュメントに追加し、それ以外の処理は行わないという意図があったように見えます。しかし、このロジックでは、エクスポートされていない型に属するメソッドが、何らかの理由でtyp != nilの条件を満たしてしまった場合に、意図せずドキュメントに追加されてしまう可能性がありました。

  4. 変更後のロジック:

    • if typ != nilブロック内のreturn文が削除されました。これにより、レシーバ型がエクスポートされている場合でも、メソッドがtyp.methodsに追加された後、処理は継続されます。
    • 重要なのは、// if the type wasn't found, it wasn't exportedという新しいコメントが追加された点です。これは、lookupTypeDocnilを返した場合(つまり、レシーバ型がエクスポートされていない場合)、そのメソッドは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が追加され、lookupTypeDocnilを返した場合(レシーバ型がエクスポートされていない場合)には、typ.methods[name] = fdocの行が実行されないため、そのメソッドはドキュメントに追加されないという意図が明確にされています。

    また、メソッド処理ブロックの後にあった通常の関数処理ロジック(doc.funcs[name] = fdoc;)が、elseブロック内に移動され、メソッドと通常の関数の処理がより明確に分離されました。これにより、メソッドとして処理されたものは、通常の関数としては扱われないというロジックが強化されています。

この変更により、ドキュメンテーション生成ツールは、Go言語のエクスポートルールに厳密に従い、エクスポートされていない型に属するメソッドは、たとえそのメソッド自体がエクスポートされていても、ドキュメントには含めないようになりました。これは、GoのAPI設計とカプセル化の原則を尊重した、より堅牢なドキュメンテーション生成を実現するための修正です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go.dev)
  • Go言語のソースコード (GitHub: golang/go)
  • Go言語におけるエクスポートルールとカプセル化に関する一般的な知識
  • 抽象構文木 (AST) に関する一般的なプログラミング知識
  • コミットメッセージと差分から読み取れる情報