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

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

このコミットは、Go言語のドキュメンテーションツールであるgo/docおよびgodocにおいて、匿名フィールド(埋め込みフィールド)のメソッドが正しく表示されるようにする変更です。特に、構造体に埋め込まれた型が持つメソッドが、その構造体のメソッドとしてドキュメントに反映されるように改善されています。

コミット

Author: Robert Griesemer gri@golang.org Date: Thu Dec 22 13:11:40 2011 -0800

Commit Message:

go/doc, godoc: show methods of anonymous fields

Missing: Handling of embedded interfaces.

Also, for reasons outlined in the previous CL (5500055), embedded
types have to be exported for its "inherited" methods to be visible.
This will be addressed w/ a subsequent CL.

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

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

https://github.com/golang/go/commit/7ea92ddd6620cb57c90e7add369ec2b3e6c17444

元コミット内容

go/doc, godoc: show methods of anonymous fields

Missing: Handling of embedded interfaces.

Also, for reasons outlined in the previous CL (5500055), embedded
types have to be exported for its "inherited" methods to be visible.
This will be addressed w/ a subsequent CL.

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

変更の背景

Go言語の構造体には、フィールド名を指定せずに型を埋め込む「匿名フィールド(または埋め込みフィールド)」という機能があります。この機能を使うと、埋め込まれた型のメソッドが、埋め込み先の構造体のメソッドであるかのように振る舞います。これは「メソッドの埋め込み」と呼ばれ、コードの再利用性を高める強力なメカニズムです。

しかし、このコミットが作成された時点では、go/docgodocといったGoの公式ドキュメンテーションツールが、この匿名フィールドを通じて「継承」されたメソッドを適切にドキュメントに表示できていませんでした。その結果、開発者はGoの言語仕様上は利用できるはずのメソッドがドキュメントに現れないため、混乱を招く可能性がありました。

このコミットの目的は、このドキュメンテーションのギャップを埋め、匿名フィールドによって提供されるメソッドもgo/docgodocが正確に解析し、表示できるようにすることです。コミットメッセージには、まだ対応できていない点(埋め込みインターフェースの扱い)や、今後の課題(埋め込み型がエクスポートされている必要がある点)も明記されており、段階的な改善の一環であることが示唆されています。

前提知識の解説

Go言語の匿名フィールド(埋め込みフィールド)

Go言語の構造体では、フィールド名を指定せずに型を宣言することで、その型を「匿名フィールド」として埋め込むことができます。これにより、埋め込まれた型のフィールドやメソッドに、埋め込み先の構造体から直接アクセスできるようになります。これは、継承に似た振る舞いを実現しますが、Goでは「コンポジション(合成)」を通じてコードの再利用を図るという設計思想に基づいています。

例:

type Person struct {
    Name string
}

func (p Person) Greet() string {
    return "Hello, " + p.Name
}

type Employee struct {
    Person // 匿名フィールド
    ID     string
}

func main() {
    e := Employee{Person: Person{Name: "Alice"}, ID: "123"}
    fmt.Println(e.Greet()) // Employee型からPersonのGreetメソッドを直接呼び出せる
}

Go言語のメソッド埋め込み

匿名フィールドを構造体に埋め込むと、埋め込まれた型のメソッドは、埋め込み先の構造体のメソッドセットに「昇格(promote)」されます。つまり、埋め込み先の構造体のインスタンスを通じて、埋め込まれた型のメソッドを直接呼び出すことができるようになります。このメカニズムは、インターフェースの実装を簡素化したり、共通の振る舞いを複数の型で共有したりする際に非常に有用です。

メソッドの解決には優先順位があり、埋め込み先の構造体で直接定義されたメソッドが、埋め込まれた型から昇格されたメソッドよりも優先されます。また、より浅いレベルで埋め込まれたメソッドが、より深いレベルで埋め込まれた同名のメソッドよりも優先されます。

go/docgodoc

  • go/doc: Goの標準ライブラリの一つで、Goのソースコードからドキュメンテーションを抽出するためのパッケージです。AST(抽象構文木)を解析し、パッケージ、型、関数、変数などのドキュメントコメントを構造化されたデータとして提供します。
  • godoc: go/docパッケージを利用して、Goのソースコードから生成されたドキュメントをWebブラウザで表示したり、コマンドラインで参照したりするためのツールです。Goのプロジェクトでは、godocを使ってAPIドキュメントを生成し、公開することが一般的です。

ast パッケージ

go/astパッケージは、Goのソースコードの抽象構文木(Abstract Syntax Tree, AST)を表現するためのデータ構造を提供します。Goのコンパイラやツール(go/docgoimportsなど)は、このASTを解析してコードの構造を理解し、様々な処理を行います。このコミットでは、Goの型定義(ast.TypeSpec)やフィールドリスト(ast.FieldList)などを操作して、匿名フィールドの情報を取得しています。

技術的詳細

このコミットの主要な技術的変更点は、go/docパッケージが匿名フィールドのメソッドをどのように収集し、処理するかを根本的に見直したことです。

  1. embeddedType構造体の導入: 匿名フィールドの情報をより正確に追跡するために、embeddedTypeという新しい内部構造体が導入されました。これは、埋め込まれた型のtypeDoc(ドキュメント情報)と、その匿名フィールドがポインタ型であるかどうか(ptrフィールド)を保持します。これにより、メソッドのレシーバ型を正確にカスタマイズするために必要な情報が保持されます。

    type embeddedType struct {
        typ *typeDoc // the corresponding base type
        ptr bool     // if set, the anonymous field type is a pointer
    }
    
  2. typeDoc構造体の変更: 既存のtypeDoc構造体は、匿名フィールドのリストを[]*typeDocから[]embeddedTypeに変更しました。また、トップレベルのメソッドと埋め込みメソッドを区別するために、methodsフィールドをmethods(トップレベルのみ)とembedded(埋め込みメソッド用)に分割し、最終的なメソッドリストをMethodsとして持つように再編成されました。

    type typeDoc struct {
        // ...
        methods   map[string]*ast.FuncDecl // top-level methods only
        embedded  methodSet                // embedded methods only
        Methods   []*FuncDoc               // all methods including embedded ones
    }
    
  3. makeTypeDocs関数の多段階処理: makeTypeDocs関数は、型ドキュメントを生成する主要な関数ですが、匿名フィールドのメソッドを正確に処理するために、以下の3つのフェーズに分割されました。

    • フェーズ1: 各型ドキュメントの基本的な情報(定数、変数、ファクトリ関数、トップレベルメソッド)を収集し、old.forwardを使って処理済みであることをマークします。
    • フェーズ2: 処理済みの型ドキュメントを走査し、その匿名フィールドから埋め込みメソッドを収集します。この際、collectEmbeddedMethods関数が再帰的に呼び出され、より浅いレベルの埋め込みが優先されるように処理されます。
    • フェーズ3: 各型ドキュメントの最終的なメソッドセットを計算します。トップレベルのメソッドが埋め込みメソッドよりも優先され、名前の衝突がある場合はトップレベルのメソッドが採用されます。
  4. methodSet型の導入: メソッドのコレクションを効率的に管理するために、methodSetという新しい型が導入されました。これはmap[string]*FuncDocをラップし、メソッドの追加(名前の衝突を考慮)とソートされたリストの取得機能を提供します。

  5. collectEmbeddedMethods関数の追加: この関数は、指定されたtypeDocの匿名フィールドから再帰的に埋め込みメソッドを収集します。メソッドの解決順序(より浅い埋め込みが優先)を考慮し、customizeRecvを使ってレシーバ型を調整します。

  6. customizeRecv関数の追加: この関数は、埋め込みメソッドのレシーバ型を、埋め込み先の型に合わせてカスタマイズします。特に、ポインタの有無(*T vs T)を考慮して、正しいレシーバ型を生成します。これにより、ドキュメントに表示されるメソッドシグネチャが、実際の呼び出し方と一致するようになります。

これらの変更により、go/docはGoの言語仕様におけるメソッド埋め込みのセマンティクスをより正確に反映し、匿名フィールドを通じて利用可能なメソッドもドキュメントに表示できるようになりました。

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

embeddedType 構造体の追加

--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -14,15 +14,24 @@ import (
 
 // ----------------------------------------------------------------------------
 
+// embeddedType describes the type of an anonymous field.
+//
+type embeddedType struct {
+	typ *typeDoc // the corresponding base type
+	ptr bool     // if set, the anonymous field type is a pointer
+}
+
 type typeDoc struct {
 	// len(decl.Specs) == 1, and the element type is *ast.TypeSpec
 	// if the type declaration hasn't been seen yet, decl is nil
-\tdecl *ast.GenDecl
+\tdecl     *ast.GenDecl
+\tembedded []embeddedType
+\tforward  *TypeDoc // forward link to processed type documentation
+\n
 	// declarations associated with the type
 	values    []*ast.GenDecl // consts and vars
 	factories map[string]*ast.FuncDecl
 	methods   map[string]*ast.FuncDecl
-\tembedded  []*typeDoc // list of embedded types
 }

typeDoc の初期化と匿名フィールドの処理の変更

lookupTypeDoc関数とaddDecl関数内の匿名フィールド処理が変更されています。

--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -63,43 +72,19 @@ func (doc *docReader) addDoc(comments *ast.CommentGroup) {
 	doc.doc.List = append(list, comments.List...)
 }
 
-func (doc *docReader) addType(decl *ast.GenDecl) *typeDoc {
-\tspec := decl.Specs[0].(*ast.TypeSpec)\n-\ttdoc := doc.lookupTypeDoc(spec.Name.Name)\n-\t// tdoc should always be != nil since declared types\n-\t// are always named - be conservative and check\n-\tif tdoc != nil {\n-\t\t// a type should be added at most once, so tdoc.decl\n-\t\t// should be nil - if it isn\'t, simply overwrite it\n-\t\ttdoc.decl = decl\n-\t}\n-\treturn tdoc\n-}
-\n func (doc *docReader) lookupTypeDoc(name string) *typeDoc {\n-\tif name == "" {\n+\tif name == "" || name == "_" {\n \t\treturn nil // no type docs for anonymous types\n \t}\n \tif tdoc, found := doc.types[name]; found {\n \t\treturn tdoc\n \t}\n \t// type wasn\'t found - add one without declaration\n-\ttdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl), nil}\n-\tdoc.types[name] = tdoc\n-\treturn tdoc\n-}\n-\n-func (doc *docReader) lookupEmbeddedDoc(name string) *typeDoc {\n-\tif name == "" {\n-\t\treturn nil\n+\ttdoc := &typeDoc{\n+\t\tfactories: make(map[string]*ast.FuncDecl),\n+\t\tmethods:   make(map[string]*ast.FuncDecl),\n \t}\n-\tif tdoc, found := doc.embedded[name]; found {\n-\t\treturn tdoc\n-\t}\n-\t// type wasn\'t found - add one without declaration\n-\t// note: embedded types only have methods associated with them\n-\ttdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl), nil}\n-\tdoc.embedded[name] = tdoc\n+\tdoc.types[name] = tdoc\n \treturn tdoc\n }\n \n@@ -235,10 +220,17 @@ func (doc *docReader) addDecl(decl ast.Decl) {\n \t\t\tcase token.TYPE:\n \t\t\t\t// types are handled individually\n \t\t\t\tfor _, spec := range d.Specs {\n-\t\t\t\t\t// make a (fake) GenDecl node for this TypeSpec\n+\t\t\t\t\ttspec := spec.(*ast.TypeSpec)\n+\t\t\t\t\t// add the type to the documentation\n+\t\t\t\t\ttdoc := doc.lookupTypeDoc(tspec.Name.Name)\n+\t\t\t\t\tif tdoc == nil {\n+\t\t\t\t\t\tcontinue // no name - ignore the type\n+\t\t\t\t\t}\n+\t\t\t\t\t// Make a (fake) GenDecl node for this TypeSpec\n \t\t\t\t\t// (we need to do this here - as opposed to just\n \t\t\t\t\t// for printing - so we don\'t lose the GenDecl\n-\t\t\t\t\t// documentation)\n+\t\t\t\t\t// documentation). Since a new GenDecl node is\n+\t\t\t\t\t// created, there\'s no need to nil out d.Doc.\n \t\t\t\t\t//\n \t\t\t\t\t// TODO(gri): Consider just collecting the TypeSpec\n \t\t\t\t\t// node (and copy in the GenDecl.doc if there is no\n@@ -246,11 +238,12 @@ func (doc *docReader) addDecl(decl ast.Decl) {\n \t\t\t\t\t// makeTypeDocs below). Simpler data structures, but\n \t\t\t\t\t// would lose GenDecl documentation if the TypeSpec\n \t\t\t\t\t// has documentation as well.\n-\t\t\t\t\ttdoc := doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})\n-\t\t\t\t\t// A new GenDecl node is created, no need to nil out d.Doc.\n-\t\t\t\t\tif tdoc == nil {\n-\t\t\t\t\t\tcontinue // some error happened; ignore\n-\t\t\t\t\t}\n+\t\t\t\t\tfake := &ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos,\n+\t\t\t\t\t\t[]ast.Spec{tspec}, token.NoPos}\n+\t\t\t\t\t// A type should be added at most once, so tdoc.decl\n+\t\t\t\t\t// should be nil - if it\'s not, simply overwrite it.\n+\t\t\t\t\ttdoc.decl = fake\n+\t\t\t\t\t// Look for anonymous fields that might contribute methods.\n \t\t\t\t\tvar fields *ast.FieldList\n \t\t\t\t\tswitch typ := spec.(*ast.TypeSpec).Type.(type) {\n \t\t\t\t\tcase *ast.StructType:\n@@ -261,11 +254,13 @@ func (doc *docReader) addDecl(decl ast.Decl) {\n \t\t\t\t\tif fields != nil {\n \t\t\t\t\t\tfor _, field := range fields.List {\n \t\t\t\t\t\t\tif len(field.Names) == 0 {\n-\t\t\t\t\t\t\t\t// anonymous field\n+\t\t\t\t\t\t\t\t// anonymous field - add corresponding type\n+\t\t\t\t\t\t\t\t// to the tdoc and collect it in doc\n \t\t\t\t\t\t\t\tname := baseTypeName(field.Type, true)\n-\t\t\t\t\t\t\t\tedoc := doc.lookupEmbeddedDoc(name)\n+\t\t\t\t\t\t\t\tedoc := doc.lookupTypeDoc(name)\n \t\t\t\t\t\t\t\tif edoc != nil {\n-\t\t\t\t\t\t\t\t\ttdoc.embedded = append(tdoc.embedded, edoc)\n+\t\t\t\t\t\t\t\t\t_, ptr := field.Type.(*ast.StarExpr)\n+\t\t\t\t\t\t\t\t\ttdoc.embedded = append(tdoc.embedded, embeddedType{edoc, ptr})\n \t\t\t\t\t\t\t\t}\n \t\t\t\t\t\t\t}\n \t\t\t\t\t\t}\n```

### `methodSet` 型と関連ヘルパー関数の追加

```diff
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -430,6 +425,25 @@ func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
 	return d
 }
 
+type methodSet map[string]*FuncDoc
+
+func (mset methodSet) add(m *FuncDoc) {
+	if mset[m.Name] == nil {
+		mset[m.Name] = m
+	}
+}
+
+func (mset methodSet) sortedList() []*FuncDoc {
+	list := make([]*FuncDoc, len(mset))
+	i := 0
+	for _, m := range mset {
+		list[i] = m
+		i++
+	}
+	sort.Sort(sortFuncDoc(list))
+	return list
+}
+
 // TypeDoc is the documentation for a declared type.
 // Consts and Vars are sorted lists of constants and variables of (mostly) that type.
 // Factories is a sorted list of factory functions that return that type.
@@ -440,8 +454,9 @@ type TypeDoc struct {
 	Consts    []*ValueDoc
 	Vars      []*ValueDoc
 	Factories []*FuncDoc
-\tMethods   []*FuncDoc
-\tEmbedded  []*FuncDoc
+\tmethods   []*FuncDoc // top-level methods only
+\tembedded  methodSet  // embedded methods only
+\tMethods   []*FuncDoc // all methods including embedded ones
 	Decl      *ast.GenDecl
 	order     int
 }

makeTypeDocs の多段階処理への変更と新関数の追加

makeTypeDocs関数が大幅にリファクタリングされ、collectEmbeddedMethodscustomizeRecvが追加されています。

--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -464,7 +479,13 @@ func (p sortTypeDoc) Less(i, j int) bool {
 // blocks, but the doc extractor above has split them into
 // individual declarations.
 func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {\n-\td := make([]*TypeDoc, len(m))\n+\t// TODO(gri) Consider computing the embedded method information\n+\t//           before calling makeTypeDocs. Then this function can\n+\t//           be single-phased again. Also, it might simplify some\n+\t//           of the logic.\n+\t//\n+\t// phase 1: associate collected declarations with TypeDocs\n+\tlist := make([]*TypeDoc, len(m))\n \ti := 0\n \tfor _, old := range m {\n \t\t// all typeDocs should have a declaration associated with\n@@ -485,11 +506,16 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {\n \t\t\tt.Consts = makeValueDocs(old.values, token.CONST)\n \t\t\tt.Vars = makeValueDocs(old.values, token.VAR)\n \t\t\tt.Factories = makeFuncDocs(old.factories)\n-\t\t\tt.Methods = makeFuncDocs(old.methods)\n-\t\t\t// TODO(gri) compute list of embedded methods \n+\t\t\tt.methods = makeFuncDocs(old.methods)\n+\t\t\t// The list of embedded types\' methods is computed from the list\n+\t\t\t// of embedded types, some of which may not have been processed\n+\t\t\t// yet (i.e., their forward link is nil) - do this in a 2nd phase.\n+\t\t\t// The final list of methods can only be computed after that -\n+\t\t\t// do this in a 3rd phase.\n \t\t\tt.Decl = old.decl\n \t\t\tt.order = i\n-\t\t\td[i] = t\n+\t\t\told.forward = t // old has been processed\n+\t\t\tlist[i] = t\n \t\t\ti++\n \t\t} else {\n \t\t\t// no corresponding type declaration found - move any associated\n@@ -512,9 +538,99 @@ func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {\n \t\t\t}\n \t\t}\n \t}\n-\td = d[0:i] // some types may have been ignored\n-\tsort.Sort(d))\n-\treturn d\n+\tlist = list[0:i] // some types may have been ignored\n+\n+\t// phase 2: collect embedded methods for each processed typeDoc\n+\tfor _, old := range m {\n+\t\tif t := old.forward; t != nil {\n+\t\t\t// old has been processed into t; collect embedded\n+\t\t\t// methods for t from the list of processed embedded\n+\t\t\t// types in old (and thus for which the methods are known)\n+\t\t\ttyp := t.Type\n+\t\t\tif _, ok := typ.Type.(*ast.StructType); ok {\n+\t\t\t\t// struct\n+\t\t\t\tt.embedded = make(methodSet)\n+\t\t\t\tcollectEmbeddedMethods(t.embedded, old, typ.Name.Name)\n+\t\t\t} else {\n+\t\t\t\t// interface\n+\t\t\t\t// TODO(gri) fix this\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t// phase 3: compute final method set for each TypeDoc\n+\tfor _, d := range list {\n+\t\tif len(d.embedded) > 0 {\n+\t\t\t// there are embedded methods - exclude\n+\t\t\t// the ones with names conflicting with\n+\t\t\t// non-embedded methods\n+\t\t\tmset := make(methodSet)\n+\t\t\t// top-level methods have priority\n+\t\t\tfor _, m := range d.methods {\n+\t\t\t\tmset.add(m)\n+\t\t\t}\n+\t\t\t// add non-conflicting embedded methods\n+\t\t\tfor _, m := range d.embedded {\n+\t\t\t\tmset.add(m)\n+\t\t\t}\n+\t\t\td.Methods = mset.sortedList()\n+\t\t} else {\n+\t\t\t// no embedded methods\n+\t\t\td.Methods = d.methods\n+\t\t}\n+\t}\n+\n+\tsort.Sort(sortTypeDoc(list))\n+\treturn list\n+}\n+\n+// collectEmbeddedMethods collects the embedded methods from all\n+// processed embedded types found in tdoc in mset. It considers\n+// embedded types at the most shallow level first so that more\n+// deeply nested embedded methods with conflicting names are\n+// excluded.\n+//\n+func collectEmbeddedMethods(mset methodSet, tdoc *typeDoc, recvTypeName string) {\n+\tfor _, e := range tdoc.embedded {\n+\t\tif e.typ.forward != nil { // == e was processed\n+\t\t\tfor _, m := range e.typ.forward.methods {\n+\t\t\t\tmset.add(customizeRecv(m, e.ptr, recvTypeName))\n+\t\t\t}\n+\t\t\tcollectEmbeddedMethods(mset, e.typ, recvTypeName)\n+\t\t}\n+\t}\n+}\n+\n+func customizeRecv(m *FuncDoc, embeddedIsPtr bool, recvTypeName string) *FuncDoc {\n+\tif m == nil || m.Decl == nil || m.Decl.Recv == nil || len(m.Decl.Recv.List) != 1 {\n+\t\treturn m // shouldn\'t happen, but be safe\n+\t}\n+\n+\t// copy existing receiver field and set new type\n+\t// TODO(gri) is receiver type computation correct?\n+\t//           what about deeply nested embeddings?\n+\tnewField := *m.Decl.Recv.List[0]\n+\t_, origRecvIsPtr := newField.Type.(*ast.StarExpr)\n+\tvar typ ast.Expr = ast.NewIdent(recvTypeName)\n+\tif embeddedIsPtr || origRecvIsPtr {\n+\t\ttyp = &ast.StarExpr{token.NoPos, typ}\n+\t}\n+\tnewField.Type = typ\n+\n+\t// copy existing receiver field list and set new receiver field\n+\tnewFieldList := *m.Decl.Recv\n+\tnewFieldList.List = []*ast.Field{&newField}\n+\n+\t// copy existing function declaration and set new receiver field list\n+\tnewFuncDecl := *m.Decl\n+\tnewFuncDecl.Recv = &newFieldList\n+\n+\t// copy existing function documentation and set new declaration\n+\tnewM := *m\n+\tnewM.Decl = &newFuncDecl\n+\tnewM.Recv = typ\n+\n+\treturn &newM\n }\n \n func makeBugDocs(list []*ast.CommentGroup) []string {\n```

## コアとなるコードの解説

### `embeddedType` 構造体

この新しい構造体は、匿名フィールドの型情報とそのフィールドがポインタ型であるか(`ptr`)を保持します。`ptr`情報は、埋め込まれたメソッドのレシーバ型を正確に再構築するために重要です。例えば、`*T`が埋め込まれている場合と`T`が埋め込まれている場合では、メソッドのレシーバの振る舞いが異なるため、この情報が必要になります。

### `typeDoc` の変更

`typeDoc`は、Goの型に関するドキュメント情報を集約する構造体です。
*   `embedded []embeddedType`:匿名フィールドのリストが、より詳細な`embeddedType`のリストに変更されました。
*   `forward *TypeDoc`:多段階処理のために、処理済みの`TypeDoc`への前方参照が追加されました。
*   `methods map[string]*ast.FuncDecl`:これは、その型自身に直接定義されているメソッド(トップレベルメソッド)を保持します。
*   `embedded methodSet`:匿名フィールドから昇格されたメソッドを保持するための新しいフィールドです。`methodSet`は、メソッド名と`FuncDoc`(関数のドキュメント情報)のマッピングを管理し、名前の衝突解決やソートを容易にします。
*   `Methods []*FuncDoc`:最終的にドキュメントに表示される、トップレベルメソッドと埋め込みメソッドを合わせた全てのメソッドのリストです。

### `methodSet` 型とヘルパー関数

`methodSet`は、メソッドのコレクションを扱うためのユーティリティ型です。
*   `add(m *FuncDoc)`:メソッドをセットに追加します。同じ名前のメソッドが既に存在しない場合にのみ追加されます。これは、メソッドの衝突解決(トップレベルメソッドが埋め込みメソッドより優先されるなど)に利用されます。
*   `sortedList()`:セット内のメソッドをソートされたリストとして返します。これにより、ドキュメントの出力順序が安定します。

### `makeTypeDocs` の多段階処理

この関数は、`go/doc`のドキュメント生成の中心的なロジックを含んでいます。
以前は単一のパスで処理されていましたが、匿名フィールドのメソッドを正確に解決するためには、依存関係を考慮した多段階の処理が必要になりました。

*   **Phase 1**: 各型の基本的なドキュメント情報(定数、変数、ファクトリ、トップレベルメソッド)を収集し、`typeDoc.forward`に処理済みの`TypeDoc`インスタンスを設定します。これにより、後続のフェーズで他の型がこの型を参照できるようになります。
*   **Phase 2 (`collectEmbeddedMethods`の呼び出し)**: 各型について、その匿名フィールドから埋め込みメソッドを収集します。`collectEmbeddedMethods`関数が再帰的に呼び出され、埋め込みの深さを考慮してメソッドが収集されます。このフェーズでは、まだ処理されていない埋め込み型(`e.typ.forward == nil`)はスキップされます。
*   **Phase 3**: 最終的なメソッドセットを構築します。まず、トップレベルのメソッドを`methodSet`に追加します。次に、Phase 2で収集された埋め込みメソッドを`methodSet`に追加します。`methodSet.add`のロジックにより、トップレベルメソッドが埋め込みメソッドよりも優先されるため、名前の衝突が適切に解決されます。最後に、この`methodSet`からソートされたリストを`TypeDoc.Methods`に設定します。

### `collectEmbeddedMethods` 関数

この関数は、再帰的に匿名フィールドをたどり、それらの型が持つメソッドを収集します。重要なのは、`e.typ.forward != nil`というチェックで、これは埋め込まれた型が既にPhase 1で処理され、そのメソッド情報が利用可能であることを確認しています。また、`customizeRecv`を呼び出して、収集したメソッドのレシーバ型を、現在の埋め込み先の型に合わせて調整します。これにより、例えば`*Person`が`Employee`に埋め込まれている場合でも、`Employee`のメソッドとしてドキュメントに表示される際に、レシーバが`*Employee`のように適切に表示されるようになります。

### `customizeRecv` 関数

この関数は、埋め込みメソッドのレシーバ(例: `(p Person)` や `(p *Person)`)を、埋め込み先の型に合わせて変更します。
*   元のレシーバがポインタ型であったか(`origRecvIsPtr`)と、匿名フィールド自体がポインタ型であったか(`embeddedIsPtr`)を考慮します。
*   新しいレシーバ型は、埋め込み先の型名(`recvTypeName`)になります。
*   もし元のレシーバがポインタであったり、匿名フィールドがポインタであったりした場合は、新しいレシーバもポインタ型(`*recvTypeName`)として表現されます。
*   これにより、`godoc`が生成するドキュメントにおいて、埋め込みメソッドのシグネチャが、そのメソッドが実際に呼び出される際のレシーバ型と一致するように表示されます。

これらの変更により、`go/doc`はGoの言語仕様におけるメソッド埋め込みの複雑なセマンティクスをより正確にモデル化し、開発者にとってより有用なドキュメントを生成できるようになりました。

## 関連リンク

*   Go CL 5502059: [https://golang.org/cl/5502059](https://golang.org/cl/5502059)

## 参考にした情報源リンク

*   Go言語の埋め込み(Embedding): [https://go.dev/doc/effective_go#embedding](https://go.dev/doc/effective_go#embedding)
*   Go言語の`go/doc`パッケージ: [https://pkg.go.dev/go/doc](https://pkg.go.dev/go/doc)
*   Go言語の`go/ast`パッケージ: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
*   Go言語の`godoc`コマンド: [https://pkg.go.dev/cmd/godoc](https://pkg.go.dev/cmd/godoc)
*   Go言語のメソッドセット: [https://go.dev/ref/spec#Method_sets](https://go.dev/ref/spec#Method_sets)
*   Go言語のポインタ: [https://go.dev/tour/moretypes/1](https://go.dev/tour/moretypes/1)
*   Go言語の構造体: [https://go.dev/tour/moretypes/2](https://go.dev/tour/moretypes/2)