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

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

このコミットは、Go言語のドキュメンテーション生成ツールである go/doc パッケージにおけるバグ修正です。具体的には、エクスポートされていない(非公開の)型に対するファクトリ関数が、生成されるドキュメントから失われてしまう問題を解決します。これにより、go/doc がより正確で完全なドキュメントを生成できるようになります。

コミット

commit 212ba8076eb7f1c2efb00b83046da63f8ac75aba
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Feb 2 19:25:29 2012 -0800

    go/doc: don't lose factory functions of non-exported types

    Fixes #2824.

    R=rsc, r
    CC=golang-dev
    https://golang.org/cl/5615043

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

https://github.com/golang/go/commit/212ba8076eb7f1c2efb00b83046da63f8ac75aba

元コミット内容

go/doc: エクスポートされていない型のファクトリ関数を失わないようにする。 Issue #2824 を修正。

変更の背景

Go言語の go/doc ツールは、Goのソースコードからドキュメンテーションを自動生成する役割を担っています。このツールは、パッケージ、型、関数、メソッドなどの情報を抽出し、それらを構造化されたドキュメントとして出力します。

このコミット以前には、go/doc がエクスポートされていない(つまり、パッケージ外部からは直接アクセスできない)型を返すファクトリ関数を適切に処理できないというバグが存在しました。具体的には、go/doc は通常、エクスポートされたエンティティのみをドキュメントに含めますが、ファクトリ関数が非公開の型を生成する場合、そのファクトリ関数自体は公開されていても、その関連性がドキュメントから失われてしまうことがありました。

この問題は、go/doc がファクトリ関数を関連付ける際に、その戻り値の型が「可視(visible)」であるかどうかを誤って判断していたことに起因します。結果として、ユーザーが go/doc を使用して生成したドキュメントには、本来含まれるべきファクトリ関数が欠落し、コードベースの完全な理解を妨げる可能性がありました。このコミットは、この「ファクトリ関数が失われる」問題を修正し、go/doc がより正確で包括的なドキュメントを生成できるようにすることを目的としています。コミットメッセージにある Fixes #2824 は、当時のGoプロジェクトの内部課題管理システムにおける特定のバグ報告に対応していることを示しています。

前提知識の解説

go/doc パッケージ

go/doc パッケージは、Goのソースコードからドキュメンテーションを抽出・生成するための標準ライブラリです。Goの公式ドキュメントサイト (pkg.go.dev) で表示されるドキュメントは、このパッケージによって生成されています。主な機能は以下の通りです。

  • AST (Abstract Syntax Tree) の解析: go/parser パッケージなどを用いてGoのソースコードを解析し、ASTを構築します。
  • ドキュメンテーションコメントの抽出: 関数、型、変数、定数などに関連付けられたドキュメンテーションコメント(// または /* ... */ で始まるコメント)を抽出します。
  • エンティティの関連付け: メソッドをそのレシーバ型に関連付けたり、ファクトリ関数を生成する型に関連付けたりします。
  • エクスポートされたエンティティの識別: Goの可視性ルール(大文字で始まる識別子はエクスポートされる)に基づいて、公開されるべきエンティティを識別します。

エクスポートされた型と非エクスポートされた型 (Exported vs. Non-exported Types)

Go言語では、識別子(変数名、関数名、型名など)の最初の文字が大文字であるか小文字であるかによって、その可視性(スコープ)が決定されます。

  • エクスポートされた識別子 (Exported Identifiers): 最初の文字が大文字で始まる識別子(例: MyType, MyFunction)は、そのパッケージの外部からアクセス可能です。これらは「公開」されたAPIの一部と見なされます。
  • 非エクスポートされた識別子 (Non-exported Identifiers): 最初の文字が小文字で始まる識別子(例: myType, myFunction)は、その識別子が宣言されたパッケージ内でのみアクセス可能です。これらは「非公開」であり、パッケージの内部実装の詳細と見なされます。

go/doc は通常、エクスポートされたエンティティのみをドキュメントに含めますが、ファクトリ関数などの特定のケースでは、非エクスポートされた型との関連性も考慮する必要があります。

ファクトリ関数 (Factory Functions)

ファクトリ関数とは、特定の型の新しいインスタンスを生成して返す関数のことです。Goでは、コンストラクタの概念が明示的に存在しないため、慣習的に New プレフィックスを持つ関数がファクトリ関数として使用されることが多いです(例: NewClient() *Client)。

このコミットで問題となっていたのは、ファクトリ関数が非エクスポートされた型(例: func NewPrivate() private)を返す場合です。ファクトリ関数自体はエクスポートされていても、その戻り値の型が非エクスポートであるために、go/doc がそのファクトリ関数を適切にドキュメントに含めたり、関連付けたりできないという問題がありました。

技術的詳細

このコミットの技術的な核心は、go/doc パッケージ内の reader 構造体がコードを解析し、関数と型を関連付けるロジックにあります。特に、ファクトリ関数をその戻り値の型に関連付ける際の「可視性」の判断基準が修正されました。

go/doc は、ソースコードを読み込み、ASTを走査してドキュメント情報を収集します。このプロセスの中で、関数が特定の型に属するメソッドなのか、あるいは特定の型を生成するファクトリ関数なのかを判断します。

元の実装では、ファクトリ関数をその戻り値の型に関連付ける際、戻り値の型がエクスポートされているかどうか(ast.IsExported(name))のみをチェックしていました。しかし、go/doc には AllDecls というモードがあり、これはエクスポートされていない宣言もすべてドキュメントに含めることを指示するものです。この AllDecls モードが有効な場合でも、ファクトリ関数の戻り値の型が非エクスポートであると、そのファクトリ関数がドキュメントから失われてしまうという問題がありました。

このバグは、reader 構造体の readFunc メソッド内で、ファクトリ関数を処理する部分に存在していました。具体的には、戻り値の型が非エクスポートである場合に、そのファクトリ関数が関連付けの対象から外れてしまっていたのです。

修正は、この可視性チェックをより包括的に行うための isVisible ヘルパー関数を導入し、既存の可視性チェックをこの新しい関数に置き換えることで行われました。isVisible 関数は、AllDecls モードが有効であるか、または識別子がエクスポートされている場合に true を返します。これにより、go/docAllDecls モードが有効な場合、非エクスポートの型を返すファクトリ関数も適切にドキュメントに含めることができるようになりました。

また、テストデータ (testdata/f.go, testdata/f.0.golden, testdata/f.1.golden, testdata/f.2.golden) が追加され、この修正が正しく機能することを確認しています。これらのテストケースは、非エクスポートの型を返すファクトリ関数が、go/doc の異なるモード(例えば、AllDecls が有効な場合とそうでない場合)でどのようにドキュメントされるべきかを示しています。

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

変更は主に src/pkg/go/doc/reader.go ファイルに集中しています。

  1. reader 構造体に isVisible メソッドの追加:

    --- a/src/pkg/go/doc/reader.go
    +++ b/src/pkg/go/doc/reader.go
    @@ -154,6 +154,10 @@ type reader struct {
      	funcs   methodSet
      }\n
    +func (r *reader) isVisible(name string) bool {
    +	return r.mode&AllDecls != 0 || ast.IsExported(name)
    +}\n
    +
     // lookupType returns the base type with the given name.
     // If the base type has not been encountered yet, a new
     // type with the given name but no associated declaration
    
  2. readFunc メソッド内のファクトリ関数処理ロジックの変更:

    --- a/src/pkg/go/doc/reader.go
    +++ b/src/pkg/go/doc/reader.go
    @@ -363,17 +367,16 @@ func (r *reader) readFunc(fun *ast.FuncDecl) {
      	}\n
      \n    -\t// perhaps a factory function
    -\t// determine result type, if any
    +\t// associate factory functions with the first visible result type, if any
      \tif fun.Type.Results.NumFields() >= 1 {
      \t\tres := fun.Type.Results.List[0]
      \t\tif len(res.Names) <= 1 {\n
      \t\t\t// exactly one (named or anonymous) result associated
      \t\t\t// with the first type in result signature (there may
      \t\t\t// be more than one result)\n
    -\t\t\tif n, imp := baseTypeName(res.Type); !imp {\n
    +\t\t\tif n, imp := baseTypeName(res.Type); !imp && r.isVisible(n) {\n
      \t\t\t\tif typ := r.lookupType(n); typ != nil {\n
    -\t\t\t\t\t// associate Func with typ
    +\t\t\t\t\t// associate function with typ
      \t\t\t\t\ttyp.funcs.set(fun)\n
      \t\t\t\t\treturn
      \t\t\t\t}\n
    
  3. cleanupTypes メソッド内の可視性チェックの変更:

    --- a/src/pkg/go/doc/reader.go
    +++ b/src/pkg/go/doc/reader.go
    @@ -580,7 +583,7 @@ func (r *reader) computeMethodSets() {
      // \n
      func (r *reader) cleanupTypes() {
      \tfor _, t := range r.types {\n
    -\t\tvisible := r.mode&AllDecls != 0 || ast.IsExported(t.name)\n
    +\t\tvisible := r.isVisible(t.name)\n
      \t\tif t.decl == nil && (predeclaredTypes[t.name] || t.isEmbedded && visible) {\n
      \t\t\t// t.name is a predeclared type (and was not redeclared in this package),\n
      \t\t\t// or it was embedded somewhere but its declaration is missing (because
    

コアとなるコードの解説

isVisible メソッドの追加

reader 構造体に新しく追加された isVisible メソッドは、与えられた識別子 name が現在の go/doc のモードにおいて「可視」であるべきかを判断します。

func (r *reader) isVisible(name string) bool {
	return r.mode&AllDecls != 0 || ast.IsExported(name)
}
  • r.mode&AllDecls != 0: これは、go/docAllDecls モードで実行されているかどうかをチェックします。AllDecls モードは、エクスポートされていない宣言もすべてドキュメントに含めることを意味します。
  • ast.IsExported(name): これは、Goの go/ast パッケージのヘルパー関数で、識別子 name がGoのルールに従ってエクスポートされている(つまり、大文字で始まる)かどうかをチェックします。

このメソッドは、「AllDecls モードが有効である」か、または「識別子がエクスポートされている」のいずれかの条件が満たされれば true を返します。これにより、ドキュメント生成の際に、非エクスポートの識別子であっても AllDecls モードであれば適切に処理されるようになります。

readFunc メソッド内の変更

readFunc メソッドは、個々の関数宣言を読み込み、それを適切な型に関連付ける役割を担っています。変更された箇所は、ファクトリ関数をその戻り値の型に関連付けるロジックです。

元のコードでは、ファクトリ関数の戻り値の型 n がインポートされた型でない場合に、単純に if n, imp := baseTypeName(res.Type); !imp { という条件で処理を進めていました。この条件だけでは、n が非エクスポートの型である場合に、そのファクトリ関数が typ.funcs.set(fun) によって型に関連付けられるべきかどうかの判断が不十分でした。

修正後のコードでは、この条件に && r.isVisible(n) が追加されました。

-\t\t\tif n, imp := baseTypeName(res.Type); !imp {\n
+\t\t\tif n, imp := baseTypeName(res.Type); !imp && r.isVisible(n) {\n

これにより、ファクトリ関数の戻り値の型 n が、go/doc の現在のモード(AllDecls が有効か、または型がエクスポートされているか)において可視である場合にのみ、そのファクトリ関数が型に関連付けられるようになりました。この変更によって、AllDecls モードが有効な場合、非エクスポートの型を返すファクトリ関数も正しくドキュメントに含められるようになります。

また、コメントもより正確な表現に修正されています。

-\t// perhaps a factory function
-\t// determine result type, if any
+\t// associate factory functions with the first visible result type, if any

そして、関連付けのコメントもより汎用的な表現に修正されました。

-\t\t\t\t\t// associate Func with typ
+\t\t\t\t\t// associate function with typ

cleanupTypes メソッド内の変更

cleanupTypes メソッドは、ドキュメント生成の最終段階で、不要な型情報をクリーンアップする役割を担っています。ここでも、型の可視性を判断するロジックが isVisible メソッドを使用するように変更されました。

-\t\tvisible := r.mode&AllDecls != 0 || ast.IsExported(t.name)\n
+\t\tvisible := r.isVisible(t.name)\n

これにより、cleanupTypes が型の可視性を判断する際にも、AllDecls モードの考慮が統一的に行われるようになり、一貫性が保たれます。

これらの変更により、go/doc は、エクスポートされていない型を返すファクトリ関数であっても、AllDecls モードが有効な場合には適切にドキュメントに含め、関連付けることができるようになりました。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分 (./commit_data/11583.txt)
  • Go言語のドキュメンテーションに関する一般的な知識
  • Go言語の可視性ルールに関する知識
  • Go言語の go/ast パッケージに関する知識