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

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

このコミットは、Go言語のドキュメンテーション生成ツール(当時のgodocの前身、あるいはそれに類するツールの一部と推測される)における機能改善に関するものです。具体的には、型に関連するファクトリ関数(その型を生成する関数)もドキュメントに適切に表示されるように、ドキュメント生成ロジックが変更されています。

変更されたファイルは以下の通りです。

  • usr/gri/pretty/docprinter.go: ドキュメントの構造を定義し、Goのソースコードから情報を抽出し、HTML形式で整形して出力する主要なロジックが含まれています。
  • usr/gri/pretty/template.html: 生成されるドキュメントのHTMLテンプレートです。

コミット

commit 78f7063d9da264f98ccd4c4f4d1ae28b26c510bd
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Mar 31 18:46:21 2009 -0700

    - also associate factory methods to a type in documentation
    
    R=r
    OCL=26974
    CL=26976

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

https://github.com/golang/go/commit/78f7063d9da264f98ccd4c4f4d1ae28b26c510bd

元コミット内容

diff --git a/usr/gri/pretty/docprinter.go b/usr/gri/pretty/docprinter.go
index 87449b3db8..f724f7d5f6 100644
--- a/usr/gri/pretty/docprinter.go
+++ b/usr/gri/pretty/docprinter.go
@@ -56,6 +56,7 @@ type funcDoc struct {
 
 type typeDoc struct {
 	decl *ast.TypeDecl;
+	factories map[string] *funcDoc;
 	methods map[string] *funcDoc;
 }
 
@@ -84,6 +85,61 @@ func (doc *PackageDoc) Init(name string) {
 }
 
 
+func baseTypeName(typ ast.Expr) string {
+	switch t := typ.(type) {
+	case *ast.Ident:
+		return string(t.Lit);
+	case *ast.StarExpr:
+		return baseTypeName(t.X);
+	}
+	return "";
+}
+
+
+func (doc *PackageDoc) lookupTypeDoc(typ ast.Expr) *typeDoc {
+	tdoc, found := doc.types[baseTypeName(typ)];
+	if found {
+		return tdoc;
+	}
+	return nil;
+}
+
+
+func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) {
+	name := string(fun.Name.Lit);
+	fdoc := &funcDoc{fun};
+	
+	// determine if it should be associated with a type
+	var typ *typeDoc;
+	if fun.Recv != nil {
+		// method
+		typ = doc.lookupTypeDoc(fun.Recv.Type);
+		if typ != nil {
+			typ.methods[name] = fdoc;
+			return;
+		}
+	} else {
+		// perhaps a factory function
+		// determine result type, if any
+		if len(fun.Type.Results) >= 1 {
+			res := fun.Type.Results[0];
+			if len(res.Names) <= 1 {
+				// exactly one (named or anonymous) result type
+				typ = doc.lookupTypeDoc(res.Type);
+				if typ != nil {
+					typ.factories[name] = fdoc;
+					return;
+				}
+			}
+		}
+	}
+	// TODO other heuristics (e.g. name is "NewTypename"?)
+	
+	// ordinary function
+	doc.funcs[name] = fdoc;
+}
+
+
 func (doc *PackageDoc) addDecl(decl ast.Decl) {
 	switch d := decl.(type) {
 	case *ast.ImportDecl:
@@ -95,7 +151,7 @@ func (doc *PackageDoc) addDecl(decl ast.Decl) {
 		if isExported(d.Name) {
 			// TODO only add if not there already - or ignore?
 			name := string(d.Name.Lit);
-			tdoc := &typeDoc{d, make(map[string] *funcDoc)};
+			tdoc := &typeDoc{d, make(map[string] *funcDoc), make(map[string] *funcDoc)};
 			doc.types[name] = tdoc;
 		}
 
@@ -105,28 +161,7 @@ func (doc *PackageDoc) addDecl(decl ast.Decl) {
 
 	case *ast.FuncDecl:
 		if isExported(d.Name) {
-			if d.Recv != nil {
-				// method
-				// determine receiver type name
-				var name string;
-				switch t := d.Recv.Type.(type) {
-				case *ast.Ident:
-					name = string(t.Lit);
-				case *ast.StarExpr:
-					// recv must be of the form *name
-					name = string(t.X.(*ast.Ident).Lit)
-				}
-				typ, found := doc.types[name];
-				if found {
-					fdoc := &funcDoc{d};
-					typ.methods[string(d.Name.Lit)] = fdoc;
-				}
-				// otherwise ignore
-			} else {
-				// ordinary function
-				fdoc := &funcDoc{d};
-				doc.funcs[string(d.Name.Lit)] = fdoc;
-			}
+			doc.addFunc(d);
 		}
 
 	case *ast.DeclList:
@@ -236,14 +271,14 @@ func (c *constDoc) printConsts(p *astPrinter.Printer) {
 }
 
 
-func (f *funcDoc) print(p *astPrinter.Printer) {
+func (f *funcDoc) print(p *astPrinter.Printer, hsize int) {
 	d := f.decl;
 	if d.Recv != nil {
-		p.Printf("<h3>func (");
+		p.Printf("<h%d>func (", hsize);
 		p.Expr(d.Recv.Type);
-		p.Printf(") %s</h3>\n", d.Name.Lit);
+		p.Printf(") %s</h%d>\n", d.Name.Lit, hsize);
 	} else {
-		p.Printf("<h2>func %s</h2>\n", d.Name.Lit);
+		p.Printf("<h%d>func %s</h%d>\n", hsize, d.Name.Lit, hsize);
 	}\n p.Printf("<code>\");
  p.DoFuncDecl(d);
 @@ -265,8 +300,12 @@ func (t *typeDoc) print(p *astPrinter.Printer) {
  	}
  	
  	// print associated methods, if any
+	for name, m := range t.factories {
+		m.print(p, 3);
+	}
+
  	for name, m := range t.methods {
-		m.print(p);
+		m.print(p, 3);
  	}
  }
  
@@ -348,9 +387,9 @@ func (doc *PackageDoc) Print(writer io.Write) {
  
  		"FUNCTIONS-->" :
  			func() {
+				p.Printf("<hr />\n");
  				for name, f := range doc.funcs {
-					p.Printf("<hr />\n");
-					f.print(&p);\n+					f.print(&p, 2);\n  				}\n  			},\n  	});\ndiff --git a/usr/gri/pretty/template.html b/usr/gri/pretty/template.html\nindex e4a7550dd7..ab415c9272 100644\n--- a/usr/gri/pretty/template.html\n+++ b/usr/gri/pretty/template.html\n@@ -13,14 +13,6 @@\n \n <!--FUNCTIONS-->\n \n-<hr />\n-<h1>Implementation</h1>\n-<font color=grey>Comments are currently not shown in the source.</font>\n-\n-<pre>\n-<!--PACKAGE_BODY-->\n-</pre>\n-\n </div>  <!-- content -->\n </body>\n </html>\n```

## 変更の背景

このコミットの主な目的は、Go言語のドキュメンテーション生成において、特定の型に関連する「ファクトリ関数」もその型のドキュメントの一部として表示されるようにすることです。

Go言語では、特定の型の新しいインスタンスを生成するための関数を「ファクトリ関数」と呼ぶ慣習があります(例: `NewType()`)。これらは厳密にはその型のメソッドではありませんが、その型と密接に関連しており、ドキュメント上で一緒に表示されることで、ユーザーがその型をどのようにインスタンス化すればよいかを理解しやすくなります。

この変更以前は、ドキュメント生成ツールはメソッド(レシーバを持つ関数)のみを型に関連付けていました。このコミットにより、戻り値の型に基づいてファクトリ関数を識別し、関連する型のドキュメントに含めることで、生成されるドキュメントの網羅性と利便性が向上しました。

## 前提知識の解説

### Go言語の基本的な型と関数

Go言語では、`struct`キーワードを使ってカスタムのデータ型を定義できます。関数は`func`キーワードで定義され、引数と戻り値を持つことができます。

### Go言語におけるメソッドとレシーバ

Go言語のメソッドは、特定の型に関連付けられた関数です。メソッドは、関数名の前にレシーバ引数を指定することで定義されます。レシーバは、そのメソッドがどの型の値に対して呼び出されるかを示します。

```go
type MyType struct {
    Value int
}

// MyTypeのメソッド
func (m MyType) GetValue() int {
    return m.Value
}

ファクトリ関数

Go言語におけるファクトリ関数は、特定の型の新しいインスタンスを生成し、それを返す通常の関数です。メソッドとは異なり、レシーバを持ちません。慣習的に、Newプレフィックスを付けて命名されることが多いです(例: NewMyType())。

type MyType struct {
    Value int
}

// MyTypeのファクトリ関数
func NewMyType(value int) *MyType {
    return &MyType{Value: value}
}

Goのドキュメンテーションツール(godoc

Go言語には、ソースコードから自動的にドキュメントを生成するgodocというツールがあります。godocは、Goのソースコードを解析し、コメントや宣言から情報を抽出し、HTML形式で整形されたドキュメントを生成します。このコミットが行われた2009年当時、godocはまだ初期段階にあり、その機能は継続的に改善されていました。usr/gri/prettyというパスは、Goの初期開発者の一人であるRobert Griesemer氏が開発していた、ドキュメント生成に関連する実験的な、あるいは初期のコードベースの一部であった可能性が高いです。

ast (Abstract Syntax Tree) パッケージ

Goコンパイラやツールは、ソースコードを直接扱うのではなく、その抽象構文木(AST)を生成して解析します。go/astパッケージは、GoのソースコードのASTを表現するためのデータ構造を提供します。ドキュメンテーションツールは、このASTを走査することで、型、関数、メソッドなどの情報をプログラム的に抽出します。

技術的詳細

このコミットは、docprinter.go内のドキュメント生成ロジックを大幅に拡張しています。

  1. typeDoc構造体の拡張: typeDoc構造体は、特定の型に関するドキュメント情報を保持します。このコミットにより、factoriesという新しいmap[string]*funcDocフィールドが追加されました。これにより、型に関連するファクトリ関数を、その型のメソッドとは別に管理できるようになります。

    type typeDoc struct {
    	decl *ast.TypeDecl;
    	factories map[string] *funcDoc; // 新しく追加
    	methods map[string] *funcDoc;
    }
    
  2. ファクトリ関数を識別し関連付けるロジックの追加: addFuncという新しい関数が導入されました。この関数は、ast.FuncDecl(関数のASTノード)を受け取り、それが通常の関数なのか、メソッドなのか、あるいはファクトリ関数なのかを判断し、適切なPackageDocまたはtypeDocに関連付けます。

    • メソッドの識別: fun.Recv != nil(レシーバが存在する)の場合、その関数はメソッドと判断されます。lookupTypeDocを使ってレシーバの型に対応するtypeDocを探し、見つかればそのmethodsマップに登録します。
    • ファクトリ関数の識別: fun.Recv == nil(レシーバが存在しない)の場合、通常の関数またはファクトリ関数と判断されます。ファクトリ関数であるかどうかを判断するために、関数の戻り値の型がチェックされます。
      • len(fun.Type.Results) >= 1:戻り値が1つ以上あるか。
      • len(res.Names) <= 1:戻り値が1つ(名前付きまたは匿名)であるか。
      • これらの条件を満たし、かつ戻り値の型が既存の型と一致する場合、その関数はファクトリ関数と見なされ、対応するtypeDocfactoriesマップに登録されます。
    • 通常の関数: 上記のいずれにも該当しない場合、その関数は通常のパッケージレベルの関数としてPackageDocfuncsマップに登録されます。

    このロジックは、Goのソースコードを解析し、関数のシグネチャ(特にレシーバの有無と戻り値の型)に基づいて、その関数がドキュメント上でどのカテゴリに属するかを自動的に分類するものです。

  3. 型名抽出ヘルパー関数の追加:

    • baseTypeName(typ ast.Expr) string: ASTの型表現(ast.Expr)から、その基本となる型名(例: *MyTypeからMyType)を抽出するためのヘルパー関数です。ポインタ型の場合も適切に基底の型名を返します。
    • lookupTypeDoc(typ ast.Expr) *typeDoc: baseTypeNameを使って型名を取得し、PackageDoc内のtypesマップから対応するtypeDocを検索します。
  4. ドキュメント出力の調整:

    • funcDoc.printメソッドにhsize(HTMLの見出しレベル)パラメータが追加されました。これにより、関数やメソッドの表示において、より柔軟な見出しレベル(<h2>, <h3>など)を使用できるようになりました。これにより、ファクトリ関数やメソッドが型のドキュメント内で適切な階層で表示されるようになります。
    • typeDoc.printメソッド内で、methodsをプリントする前にfactoriesマップをイテレートし、それぞれのファクトリ関数をhsize=3でプリントするロジックが追加されました。
    • PackageDoc.Print内のFUNCTIONSセクションの出力ロジックが変更され、不要な<hr />タグの出力が削除され、関数がhsize=2でプリントされるようになりました。
    • template.htmlから、<h1>Implementation</h1>などの静的なHTML要素が削除されました。これは、docprinter.goがより動的にHTML構造を生成するようになったため、テンプレートから冗長な要素を削除したものです。

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

usr/gri/pretty/docprinter.go

  1. typeDoc構造体へのfactoriesフィールドの追加:

    --- a/usr/gri/pretty/docprinter.go
    +++ b/usr/gri/pretty/docprinter.go
    @@ -56,6 +56,7 @@ type funcDoc struct {
     
     type typeDoc struct {
     	decl *ast.TypeDecl;
    +	factories map[string] *funcDoc;
     	methods map[string] *funcDoc;
     }
    
  2. baseTypeName, lookupTypeDoc, addFunc関数の新規追加: これらの関数は、コミットの差分で新規に追加されたブロック全体です。特にaddFuncは、関数がメソッドかファクトリ関数かを判別する中心的なロジックを含んでいます。

  3. PackageDoc.addDeclにおけるast.FuncDeclの処理の変更: 以前はaddDecl内で直接メソッドと通常の関数を分類していましたが、新しいaddFunc関数を呼び出すように変更されました。

    --- a/usr/gri/pretty/docprinter.go
    +++ b/usr/gri/pretty/docprinter.go
    @@ -105,28 +161,7 @@ func (doc *PackageDoc) addDecl(decl ast.Decl) {
     
     	case *ast.FuncDecl:
     		if isExported(d.Name) {
    -			if d.Recv != nil {
    -				// method
    -				// determine receiver type name
    -				var name string;
    -				switch t := d.Recv.Type.(type) {
    -				case *ast.Ident:
    -					name = string(t.Lit);
    -				case *ast.StarExpr:
    -					// recv must be of the form *name
    -					name = string(t.X.(*ast.Ident).Lit)
    -				}
    -				typ, found := doc.types[name];
    -				if found {
    -					fdoc := &funcDoc{d};
    -					typ.methods[string(d.Name.Lit)] = fdoc;
    -				}
    -				// otherwise ignore
    -			} else {
    -				// ordinary function
    -				fdoc := &funcDoc{d};
    -				doc.funcs[string(d.Name.Lit)] = fdoc;
    -			}
    +			doc.addFunc(d);
     		}
    
  4. funcDoc.printメソッドのシグネチャ変更とHTML出力の調整: hsizeパラメータが追加され、見出しタグのレベルが動的に設定されるようになりました。

    --- a/usr/gri/pretty/docprinter.go
    +++ b/usr/gri/pretty/docprinter.go
    @@ -236,14 +271,14 @@ func (c *constDoc) printConsts(p *astPrinter.Printer) {
     }
     
     
    -func (f *funcDoc) print(p *astPrinter.Printer) {
    +func (f *funcDoc) print(p *astPrinter.Printer, hsize int) {
     	d := f.decl;
     	if d.Recv != nil {
    -		p.Printf("<h3>func (");
    +		p.Printf("<h%d>func (", hsize);
     		p.Expr(d.Recv.Type);
    -		p.Printf(") %s</h3>\n", d.Name.Lit);
    +		p.Printf(") %s</h%d>\n", d.Name.Lit, hsize);
     	} else {
    -		p.Printf("<h2>func %s</h2>\n", d.Name.Lit);
    +		p.Printf("<h%d>func %s</h%d>\n", hsize, d.Name.Lit, hsize);
     	}
      p.Printf("<code>\");
      p.DoFuncDecl(d);
    
  5. typeDoc.printにおけるファクトリ関数の出力ロジックの追加:

    --- a/usr/gri/pretty/docprinter.go
    +++ b/usr/gri/pretty/docprinter.go
    @@ -265,8 +300,12 @@ func (t *typeDoc) print(p *astPrinter.Printer) {
      	}
      	
      	// print associated methods, if any
    +	for name, m := range t.factories {
    +		m.print(p, 3);
    +	}
    +
      	for name, m := range t.methods {
    -		m.print(p);
    +		m.print(p, 3);
      	}
      }
    

usr/gri/pretty/template.html

  1. 不要なHTML要素の削除:
    --- a/usr/gri/pretty/template.html
    +++ b/usr/gri/pretty/template.html
    @@ -13,14 +13,6 @@
     
     <!--FUNCTIONS-->
     
    -<hr />
    -<h1>Implementation</h1>
    -<font color=grey>Comments are currently not shown in the source.</font>
    -
    -<pre>
    -<!--PACKAGE_BODY-->
    -</pre>
    -
     </div>  <!-- content -->
      </body>
      </html>
    

コアとなるコードの解説

このコミットの核心は、Goのドキュメンテーション生成プロセスが、単にメソッドだけでなく、特定の型を返すファクトリ関数もその型のドキュメントに含めるように賢くなった点にあります。

  • typeDocの拡張: typeDocfactoriesマップが追加されたことで、ドキュメント生成ツールは、ある型に関連するファクトリ関数を内部的に追跡・管理できるようになりました。これは、メソッド(methodsマップ)と同様に、型と関数の関連性を表現するための重要なデータ構造です。

  • addFuncによるインテリジェントな分類: addFunc関数は、GoのASTを解析する際に、関数のシグネチャを詳細に検査します。

    • レシーバの有無によって、その関数がメソッドであるか、通常の関数であるかを区別します。
    • レシーバがない場合(通常の関数である可能性)、さらに戻り値の型をチェックします。もし戻り値が1つだけで、その型が既存の型と一致する場合、その関数を「ファクトリ関数」と見なし、対応するtypeDocfactoriesマップに登録します。このロジックにより、NewMyTypeのような関数がMyTypeのドキュメントに自動的に関連付けられるようになります。
  • ドキュメント出力の改善: funcDoc.printhsizeパラメータが導入されたことで、生成されるHTMLドキュメントの見出し構造がよりセマンティックかつ柔軟になりました。これにより、パッケージレベルの関数は<h2>、型に属するメソッドやファクトリ関数は<h3>といったように、適切な階層で表示され、ドキュメントの可読性が向上します。 typeDoc.print内でfactoriesマップがイテレートされ、ファクトリ関数がメソッドと同様に型のドキュメント内に表示されるようになったことで、ユーザーは型のインスタンス化方法に関する情報を一箇所で確認できるようになりました。

これらの変更により、Goのドキュメンテーションは、単にコードの構造を反映するだけでなく、Goの慣習(ファクトリ関数など)を理解し、よりユーザーフレンドリーな情報提供を行う方向に進化しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特にgo/docパッケージやgodocコマンドの歴史)
  • Go言語におけるファクトリ関数の慣習に関する一般的な知識