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

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

コミット

commit aa3880178850ab0525802a48fc4eeadcdbb2c26c
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Nov 13 10:45:30 2012 -0800

    reflect: fix FieldByNameFunc
    
    The existing algorithm did not properly propagate the type
    count from one level to the next, and as a consequence it
    missed collisions.
    
    Properly propagate multiplicity (count) information to the
    next level.
    
    benchmark                old ns/op    new ns/op    delta
    BenchmarkFieldByName1          182          180   -1.10%
    BenchmarkFieldByName2         6273         6183   -1.43%
    BenchmarkFieldByName3        49267        46784   -5.04%
    
    Fixes #4355.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6821094

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

https://github.com/golang/go/commit/aa3880178850ab0525802a48fc4eeadcdbb2c26c

元コミット内容

このコミットは、Go言語のreflectパッケージにおけるFieldByNameFunc関数のバグ修正を目的としています。既存のアルゴリズムが、構造体の埋め込みフィールドを探索する際に、型の出現回数(multiplicity/count)を適切に伝播していなかったため、名前の衝突(collisions)を見逃していました。この修正により、型の出現回数情報を次のレベルに正しく伝播させることで、衝突を正確に検出できるようになります。

ベンチマーク結果では、FieldByName関連の操作においてパフォーマンスがわずかに向上していることが示されています。

  • BenchmarkFieldByName1: 182 ns/op -> 180 ns/op (-1.10%)
  • BenchmarkFieldByName2: 6273 ns/op -> 6183 ns/op (-1.43%)
  • BenchmarkFieldByName3: 49267 ns/op -> 46784 ns/op (-5.04%)

この修正は、Issue #4355を解決します。

変更の背景

Go言語のreflectパッケージは、実行時に型情報を検査・操作するための機能を提供します。特に、構造体のフィールドに名前でアクセスする機能は、様々なメタプログラミングやシリアライゼーションのシナリオで重要です。

FieldByNameFuncは、特定の条件(関数で定義されるマッチングロジック)に合致する名前のフィールドを構造体から検索する関数です。Goの構造体は、他の構造体を埋め込む(embedding)ことができます。これにより、埋め込まれた構造体のフィールドが、埋め込み元の構造体のフィールドであるかのようにアクセスできるようになります。

この埋め込みのメカニズムは非常に便利ですが、複数のパスから同じ名前のフィールドに到達できる場合、名前の衝突(ambiguity)が発生する可能性があります。例えば、構造体Aが構造体Bと構造体Cを埋め込み、BとCの両方が同じ名前のフィールドXを持っている場合、A.Xにアクセスしようとすると曖昧になります。Goのreflectパッケージは、このような曖昧なケースではフィールドを返すべきではありません。

しかし、このコミット以前のFieldByNameFuncの実装では、埋め込み構造体を再帰的に探索する際に、既に探索済みの型やその出現回数を正確に追跡できていませんでした。具体的には、ある型が複数の埋め込みパスを通じて到達可能であるにもかかわらず、その「複数回出現している」という情報が次の探索レベルに適切に伝播されなかったため、衝突を誤って見逃し、曖昧なフィールドを返してしまうバグが存在しました。

このバグは、Issue #4355で報告されており、reflectパッケージの正確性と堅牢性を確保するために修正が必要でした。

前提知識の解説

Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが自身の構造を検査する(リフレクション)ための機能を提供します。これにより、プログラムは実行時に変数や関数の型情報を取得したり、値を動的に操作したりすることができます。主な用途は以下の通りです。

  • 型情報の取得: 変数の型、メソッド、フィールドなどの情報を取得します。
  • 値の操作: リフレクションを使って、変数の値を読み書きしたり、メソッドを呼び出したりします。
  • 構造体のタグの利用: 構造体のフィールドに付与されたタグ(例: json:"name")を読み取り、シリアライゼーションやバリデーションに利用します。

reflectパッケージは強力ですが、型安全性を損なう可能性があり、パフォーマンスオーバーヘッドも伴うため、必要不可欠な場合にのみ使用することが推奨されます。

構造体の埋め込み(Embedding)

Go言語の構造体は、他の構造体を匿名フィールドとして含めることができます。これを「埋め込み」と呼びます。埋め込まれた構造体のフィールドやメソッドは、あたかも埋め込み元の構造体のフィールドやメソッドであるかのように、直接アクセスできます。

例:

type Point struct {
    X, Y int
}

type Circle struct {
    Point // Point構造体を埋め込み
    Radius int
}

func main() {
    c := Circle{Point: Point{X: 10, Y: 20}, Radius: 5}
    fmt.Println(c.X) // CircleからPointのXフィールドに直接アクセス
}

フィールド名の衝突(Field Name Collisions)

構造体の埋め込みにおいて、異なるパスから同じ名前のフィールドに到達できる場合、フィールド名の衝突が発生します。Goの言語仕様では、このような曖昧なアクセスは許可されません。reflectパッケージも同様に、曖昧なフィールドを検索した場合には、そのフィールドを返すべきではありません。

例:

type S1 struct { X int }
type S2 struct { S1 }
type S3 struct { S1 }
type S4 struct { S2; S3 } // S2とS3の両方がS1を埋め込んでいるため、S4.Xは曖昧

この場合、S4{}.Xに直接アクセスしようとするとコンパイルエラーになります。reflectパッケージのFieldByNameFieldByNameFuncも、このようなケースではフィールドが見つからない(nilを返す)ように振る舞うべきです。

FieldByNameFuncの内部動作(簡略化)

FieldByNameFuncは、指定された関数(match)を使ってフィールド名を探索します。内部的には、構造体のフィールドと、埋め込まれた構造体のフィールドを再帰的に探索します。この探索プロセスでは、各フィールドがどのパスを通じて到達可能か、そしてそのパスが複数存在するかどうかを追跡する必要があります。

この追跡のために、内部でcountのようなマップが使用されます。これは、特定の型が現在の探索レベルで何回出現したかを記録するためのものです。もしある型が2回以上出現した場合、その型を通じてアクセスできるフィールドは曖昧であると判断され、結果から除外されるべきです。

技術的詳細

このコミットの技術的詳細は、reflectパッケージのFieldByNameFuncメソッドにおける、埋め込み構造体の探索ロジック、特に「型の出現回数(multiplicity/count)」の伝播方法にあります。

FieldByNameFuncは、構造体のフィールドを深さ優先探索(またはそれに近い方法)で走査し、指定されたマッチング関数に合致するフィールドを探します。この際、埋め込み構造体に出くわすと、その埋め込み構造体のフィールドも探索対象に加えます。

バグの核心は、nextCountというマップの更新ロジックにありました。nextCountは、次の探索レベルで処理される埋め込み構造体の型が、これまでに何回検出されたかを追跡するためのものです。

修正前のコードでは、埋め込み構造体stypnextCountに既に存在する場合、単にnextCount[styp]++としていました。これは、その型が複数回出現したことを示すには十分ですが、その「複数回出現している」という状態が、さらに深いレベルの探索に適切に伝播されない可能性がありました。

例えば、ABCを埋め込み、BCが両方ともDを埋め込んでいる場合を考えます。 A -> B -> D A -> C -> D この場合、DAから2つの異なるパスで到達可能です。FieldByNameFuncBCを処理する際に、DnextCountに2回追加されるべきです。しかし、もしBを処理した時点でDnextCountに追加され、次にCを処理した際にDが既に存在するためnextCount[D]++となるだけだと、そのDが「曖昧である」という情報が、Dの内部フィールドを探索する際に失われる可能性がありました。

修正後のコードでは、この伝播ロジックが強化されています。

  1. if nextCount[styp] > 0 { nextCount[styp] = 2; continue }

    • これは、stypが既にnextCountに存在する場合(つまり、既に一度検出されている場合)、そのカウントを2に設定します。2という値は、その型が「複数回出現している」ことを明確に示します。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要です。これにより、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。
  2. if count[t] > 1 { nextCount[styp] = 2 }

    • これはさらに重要な変更です。現在の探索レベルの型t自体が既に曖昧である(つまり、count[t] > 1)場合、そのtが埋め込んでいるstypもまた、曖昧なパスを通じて到達可能であると見なされます。したがって、stypnextCount2に設定されます。これにより、曖昧さの情報が探索の深さに応じて正しく伝播されるようになります。

この変更により、FieldByNameFuncは、複数の埋め込みパスを通じて到達可能なフィールド(つまり、曖昧なフィールド)を正しく識別し、それらを結果として返さないようになります。これは、Goのリフレクションが言語の曖昧性解決ルールに厳密に従うために不可欠です。

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

変更は主に以下の2つのファイルで行われています。

  1. src/pkg/reflect/all_test.go

    • 新しいテストケースが追加されています。
    • S14, S15, S16という構造体が定義されています。
    • S14S15S16を埋め込み、S15S16は両方ともS11を埋め込んでいます。
    • S11Xというフィールドを持っています。
    • fieldTests配列に{S14{}, "X", nil, 0}というエントリが追加されています。これは、S14型に対してXという名前のフィールドを検索した場合、結果がnil(フィールドが見つからない)になることを期待しています。これは、S14.XS15.S11.XS16.S11.Xの両方から到達可能であるため、曖昧であるというシナリオをテストしています。
  2. src/pkg/reflect/type.go

    • (*structType).FieldByNameFuncメソッド内のロジックが変更されています。
    • 具体的には、埋め込み構造体を処理する部分で、nextCountマップの更新方法が修正されています。
--- a/src/pkg/reflect/type.go
+++ b/src/pkg/reflect/type.go
@@ -913,19 +913,22 @@ func (t *structType) FieldByNameFunc(match func(string) bool) (result StructFiel
 
 				// Queue embedded struct fields for processing with next level,
 				// but only if we haven't seen a match yet at this level and only
-				// if the embedded types haven't alredy been queued.
+				// if the embedded types haven't already been queued.
 				if ok || ntyp == nil || ntyp.Kind() != Struct {
 					continue
 				}
 				styp := (*structType)(unsafe.Pointer(ntyp))
 				if nextCount[styp] > 0 {
-					nextCount[styp]++
+					nextCount[styp] = 2 // exact multiple doesn't matter
 					continue
 				}
 				if nextCount == nil {
 					nextCount = map[*structType]int{}
 				}
 				nextCount[styp] = 1
+				if count[t] > 1 {
+					nextCount[styp] = 2 // exact multiple doesn't matter
+				}
 				var index []int
 				index = append(index, scan.index...)
 				index = append(index, i)

コアとなるコードの解説

src/pkg/reflect/type.goFieldByNameFuncメソッド内の変更点に焦点を当てて解説します。

このコードブロックは、構造体のフィールドを探索するループ内で、埋め込み構造体(ntypStruct型の場合)を処理する部分です。

  1. if nextCount[styp] > 0 { nextCount[styp] = 2; continue }

    • stypは現在処理している埋め込み構造体の型です。
    • nextCountは、次の探索レベルで処理される埋め込み構造体の型と、それがこれまでに検出された回数を記録するマップです。
    • 変更前: nextCount[styp]++
      • stypが既に検出されている場合、単にそのカウントをインクリメントしていました。
    • 変更後: nextCount[styp] = 2
      • stypが既に検出されている場合(つまり、nextCount[styp]0より大きい場合)、そのカウントを2に設定します。これは、その型が「複数回出現している」ことを明確にマークするためのものです。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要であるため、2という固定値が使われます。これにより、この型を通じてアクセスできるフィールドは曖昧であると判断され、最終的に返されなくなります。
  2. if count[t] > 1 { nextCount[styp] = 2 }

    • この行は新たに追加されたロジックです。
    • tは現在の探索レベルで処理している構造体の型です。
    • count[t]は、現在の構造体tが、上位のレベルから複数のパスを通じて到達可能であるかどうかを示します(count[t] > 1であれば曖昧)。
    • 追加されたロジック: もし現在の構造体t自体が既に曖昧なパスを通じて到達可能である場合、そのtが埋め込んでいるstypもまた、曖昧なパスを通じて到達可能であると見なされます。したがって、stypnextCount2に設定され、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。

これらの変更により、FieldByNameFuncは、埋め込み構造体の探索において、型の出現回数(multiplicity)情報をより正確に伝播できるようになりました。これにより、複数の埋め込みパスを通じて到達可能なフィールド(名前の衝突があるフィールド)を正しく識別し、それらを結果として返さないという、Go言語の仕様に準拠した振る舞いを実現しています。

関連リンク

  • Go言語のreflectパッケージのドキュメント: https://pkg.go.dev/reflect
  • Go言語の構造体の埋め込みに関する公式ドキュメントやチュートリアル(Goのバージョンによってリンクが異なる可能性がありますが、Go embeddingなどで検索すると見つかります)

参考にした情報源リンク

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

コミット

commit aa3880178850ab0525802a48fc4eeadcdbb2c26c
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Nov 13 10:45:30 2012 -0800

    reflect: fix FieldByNameFunc
    
    The existing algorithm did not properly propagate the type
    count from one level to the next, and as a consequence it
    missed collisions.
    
    Properly propagate multiplicity (count) information to the
    next level.
    
    benchmark                old ns/op    new ns/op    delta
    BenchmarkFieldByName1          182          180   -1.10%
    BenchmarkFieldByName2         6273         6183   -1.43%
    BenchmarkFieldByName3        49267        46784   -5.04%
    
    Fixes #4355.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6821094

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

https://github.com/golang/go/commit/aa3880178850ab0525802a48fc4eeadcdbb2c26c

元コミット内容

このコミットは、Go言語の標準ライブラリであるreflectパッケージ内のFieldByNameFunc関数のバグを修正するものです。既存の実装では、構造体のフィールドを名前で検索する際に、特に埋め込み構造体(embedded structs)を扱う場合に、型の出現回数(multiplicity/count)が次の探索レベルに適切に伝播されないという問題がありました。この伝播の欠如により、本来検出されるべきフィールド名の衝突(collisions)が見逃されていました。

この修正の目的は、型の出現回数情報を探索の次のレベルに正しく伝播させることで、フィールド名の衝突を正確に識別し、Go言語の仕様に沿った振る舞いを保証することです。

ベンチマーク結果は、この修正がFieldByName関連の操作のパフォーマンスにわずかながら改善をもたらしたことを示しています。

  • BenchmarkFieldByName1: 182 ns/op から 180 ns/op へ (-1.10%)
  • BenchmarkFieldByName2: 6273 ns/op から 6183 ns/op へ (-1.43%)
  • BenchmarkFieldByName3: 49267 ns/op から 46784 ns/op へ (-5.04%)

この修正は、GoのIssue #4355を解決します。

変更の背景

Go言語のreflectパッケージは、実行時にプログラムの型情報を検査・操作するための強力な機能を提供します。これにより、開発者は動的に型を調べたり、構造体のフィールドにアクセスしたり、メソッドを呼び出したりすることができます。これは、JSONエンコーディング/デコーディング、ORM、テンプレートエンジンなど、多くのメタプログラミングシナリオで不可欠です。

Goの構造体は、他の構造体を匿名フィールドとして埋め込む(embedding)ことができます。これにより、埋め込まれた構造体のフィールドやメソッドが、あたかも埋め込み元の構造体のフィールドやメソッドであるかのように、直接アクセスできるようになります。この機能はコードの再利用性を高め、簡潔な記述を可能にしますが、同時にフィールド名の衝突という複雑な問題を引き起こす可能性があります。

例えば、ある構造体が二つの異なる構造体を埋め込み、それら二つの埋め込み構造体が偶然にも同じ名前のフィールドを持っている場合、そのフィールドにアクセスしようとすると曖昧さ(ambiguity)が生じます。Go言語の仕様では、このような曖昧なフィールドアクセスはコンパイル時にエラーとなります。reflectパッケージもまた、このような曖昧なフィールドを検索した場合には、そのフィールドを返すべきではありません。

しかし、このコミット以前のFieldByNameFuncの実装では、埋め込み構造体を再帰的に探索する際に、既に探索済みの型やその出現回数を正確に追跡できていませんでした。具体的には、ある型が複数の埋め込みパスを通じて到達可能であるにもかかわらず、その「複数回出現している」という情報が次の探索レベルに適切に伝播されなかったため、衝突を誤って見逃し、曖昧なフィールドを返してしまうバグが存在しました。このバグは、reflectパッケージの正確性と堅牢性を損なうものであり、Issue #4355として報告され、修正が求められていました。

前提知識の解説

Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが自身の構造を検査する(リフレクション)ための機能を提供します。これにより、プログラムは実行時に変数や関数の型情報を取得したり、値を動的に操作したりすることができます。主な用途は以下の通りです。

  • 型情報の取得: 変数の型、メソッド、フィールドなどの情報を取得します。例えば、reflect.TypeOf(myVar)で変数の型情報を取得し、その型が構造体であればNumField()でフィールド数を、Field(i)で個々のフィールド情報を取得できます。
  • 値の操作: リフレクションを使って、変数の値を読み書きしたり、メソッドを呼び出したりします。reflect.ValueOf(myVar)で変数の値を取得し、SetInt(), Call()などのメソッドで操作します。ただし、値の変更にはポインタを介したアクセスが必要です。
  • 構造体のタグの利用: 構造体のフィールドに付与されたタグ(例: json:"name", xml:"element")を読み取り、シリアライゼーションやバリデーションに利用します。StructField.Tag.Get("json")のようにしてタグの値を取得します。

reflectパッケージは非常に強力ですが、型安全性を損なう可能性があり、通常の直接的な操作に比べてパフォーマンスオーバーヘッドも伴うため、必要不可欠な場合にのみ使用することが推奨されます。

構造体の埋め込み(Embedding)

Go言語の構造体は、他の構造体を匿名フィールドとして含めることができます。これを「埋め込み」と呼びます。埋め込まれた構造体のフィールドやメソッドは、あたかも埋め込み元の構造体のフィールドやメソッドであるかのように、直接アクセスできます。

例:

type Engine struct {
    Horsepower int
}

type Car struct {
    Engine // Engine構造体を埋め込み
    Brand  string
}

func main() {
    myCar := Car{Engine: Engine{Horsepower: 200}, Brand: "Toyota"}
    fmt.Println(myCar.Horsepower) // CarからEngineのHorsepowerフィールドに直接アクセス
    fmt.Println(myCar.Brand)
}

この例では、Car構造体がEngine構造体を埋め込んでいるため、myCar.Horsepowerのように直接Engineのフィールドにアクセスできます。

フィールド名の衝突(Field Name Collisions)

構造体の埋め込みにおいて、異なるパスから同じ名前のフィールドに到達できる場合、フィールド名の衝突が発生します。Goの言語仕様では、このような曖昧なアクセスは許可されません。reflectパッケージも同様に、曖昧なフィールドを検索した場合には、そのフィールドを返すべきではありません。

例:

type PartA struct {
    ID int
}

type PartB struct {
    ID int
}

type Product struct {
    PartA
    PartB
}

func main() {
    p := Product{}
    // fmt.Println(p.ID) // コンパイルエラー: ambiguous selector p.ID
}

このProduct構造体では、PartAPartBの両方がIDフィールドを持っているため、p.IDは曖昧になります。Goコンパイラはこれをエラーとします。reflectパッケージのFieldByNameFieldByNameFuncも、このようなケースではフィールドが見つからない(nilを返す)ように振る舞うべきです。

技術的詳細

このコミットの技術的詳細は、reflectパッケージのFieldByNameFuncメソッドにおける、埋め込み構造体の探索ロジック、特に「型の出現回数(multiplicity/count)」の伝播方法の改善にあります。

FieldByNameFuncは、指定された関数(match)を使って構造体のフィールドを探索します。この関数は、構造体のフィールドと、埋め込まれた構造体のフィールドを再帰的に探索します。この探索プロセスでは、各フィールドがどのパスを通じて到達可能か、そしてそのパスが複数存在するかどうかを追跡する必要があります。

バグの核心は、nextCountというマップの更新ロジックにありました。nextCountは、次の探索レベルで処理される埋め込み構造体の型が、これまでに何回検出されたかを追跡するためのものです。

修正前のコードでは、埋め込み構造体stypnextCountに既に存在する場合、単にnextCount[styp]++としていました。これは、その型が複数回出現したことを示すには十分ですが、その「複数回出現している」という状態が、さらに深いレベルの探索に適切に伝播されない可能性がありました。

例えば、以下のような構造体階層を考えます。 RootBranch1Branch2を埋め込み、Branch1Branch2が両方ともLeafを埋め込んでいる場合。 Root -> Branch1 -> Leaf Root -> Branch2 -> Leaf この場合、LeafRootから2つの異なるパスで到達可能です。FieldByNameFuncBranch1Branch2を処理する際に、LeafnextCountに追加されるべきです。しかし、もしBranch1を処理した時点でLeafnextCountに追加され、次にBranch2を処理した際にLeafが既に存在するためnextCount[Leaf]++となるだけだと、そのLeafが「曖昧である」という情報が、Leafの内部フィールドを探索する際に失われる可能性がありました。

修正後のコードでは、この伝播ロジックが強化されています。

  1. if nextCount[styp] > 0 { nextCount[styp] = 2; continue }

    • これは、stypが既にnextCountに存在する場合(つまり、既に一度検出されている場合)、そのカウントを2に設定します。2という値は、その型が「複数回出現している」ことを明確に示します。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要です。これにより、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。
  2. if count[t] > 1 { nextCount[styp] = 2 }

    • これは新たに追加された重要な変更です。現在の探索レベルの型t自体が既に曖昧である(つまり、count[t] > 1)場合、そのtが埋め込んでいるstypもまた、曖昧なパスを通じて到達可能であると見なされます。したがって、stypnextCount2に設定されます。これにより、曖昧さの情報が探索の深さに応じて正しく伝播されるようになります。

これらの変更により、FieldByNameFuncは、複数の埋め込みパスを通じて到達可能なフィールド(つまり、曖昧なフィールド)を正しく識別し、それらを結果として返さないようになります。これは、Goのリフレクションが言語の曖昧性解決ルールに厳密に従うために不可欠です。

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

変更は主に以下の2つのファイルで行われています。

  1. src/pkg/reflect/all_test.go

    • 新しいテストケースが追加されています。これは、修正が正しく機能することを確認するためのものです。
    • S14, S15, S16という構造体が定義されています。
      type S14 struct {
          S15
          S16
      }
      
      type S15 struct {
          S11
      }
      
      type S16 struct {
          S11
      }
      
      ここで、S11Xというフィールドを持つ構造体です。S14S15S16を埋め込み、S15S16は両方ともS11を埋め込んでいるため、S14.XS15.S11.XS16.S11.Xの両方から到達可能となり、曖昧なフィールドとなります。
    • fieldTests配列に{S14{}, "X", nil, 0}というエントリが追加されています。これは、S14型に対してXという名前のフィールドを検索した場合、結果がnil(フィールドが見つからない)になることを期待しています。これは、この修正が意図する「曖昧なフィールドは返さない」という振る舞いを検証します。
  2. src/pkg/reflect/type.go

    • (*structType).FieldByNameFuncメソッド内のロジックが変更されています。
    • 具体的には、埋め込み構造体を処理する部分で、nextCountマップの更新方法が修正されています。
--- a/src/pkg/reflect/type.go
+++ b/pkg/reflect/type.go
@@ -913,19 +913,22 @@ func (t *structType) FieldByNameFunc(match func(string) bool) (result StructFiel
 
 				// Queue embedded struct fields for processing with next level,
 				// but only if we haven't seen a match yet at this level and only
-				// if the embedded types haven't alredy been queued.
+				// if the embedded types haven't already been queued.
 				if ok || ntyp == nil || ntyp.Kind() != Struct {
 					continue
 				}
 				styp := (*structType)(unsafe.Pointer(ntyp))
 				if nextCount[styp] > 0 {
-					nextCount[styp]++
+					nextCount[styp] = 2 // exact multiple doesn't matter
 					continue
 				}
 				if nextCount == nil {
 					nextCount = map[*structType]int{}
 				}
 				nextCount[styp] = 1
+				if count[t] > 1 {
+					nextCount[styp] = 2 // exact multiple doesn't matter
+				}
 				var index []int
 				index = append(index, scan.index...)
 				index = append(index, i)

コアとなるコードの解説

src/pkg/reflect/type.goFieldByNameFuncメソッド内の変更点に焦点を当てて解説します。このコードブロックは、構造体のフィールドを探索するループ内で、埋め込み構造体(ntypStruct型の場合)を処理する部分です。

  1. if nextCount[styp] > 0 { nextCount[styp] = 2; continue }

    • stypは現在処理している埋め込み構造体の型です。
    • nextCountは、次の探索レベルで処理される埋め込み構造体の型と、それがこれまでに検出された回数を記録するマップです。
    • 変更前: nextCount[styp]++
      • stypが既に検出されている場合、単にそのカウントをインクリメントしていました。これは、stypが複数回出現していることを示しますが、その「複数回出現している」という状態が、さらに深いレベルの探索に適切に伝播されない可能性がありました。
    • 変更後: nextCount[styp] = 2
      • stypが既に検出されている場合(つまり、nextCount[styp]0より大きい場合)、そのカウントを2に設定します。これは、その型が「複数回出現している」ことを明確にマークするためのものです。正確な出現回数(3回、4回など)は重要ではなく、2回以上出現しているかどうかが重要であるため、2という固定値が使われます。これにより、この型を通じてアクセスできるフィールドは曖昧であると判断され、最終的に返されなくなります。continueは、このstypの処理をスキップし、次の埋め込み構造体の探索に進むことを意味します。
  2. if count[t] > 1 { nextCount[styp] = 2 }

    • この行は新たに追加されたロジックです。
    • tは現在の探索レベルで処理している構造体の型です。
    • count[t]は、現在の構造体tが、上位のレベルから複数のパスを通じて到達可能であるかどうかを示します(count[t] > 1であれば曖昧)。
    • 追加されたロジック: もし現在の構造体t自体が既に曖昧なパスを通じて到達可能である場合、そのtが埋め込んでいるstypもまた、曖昧なパスを通じて到達可能であると見なされます。したがって、stypnextCount2に設定され、その型を通じてアクセスできるフィールドが曖昧であるとマークされます。これにより、曖昧さの情報が探索の深さに応じて正しく伝播されるようになります。

これらの変更により、FieldByNameFuncは、埋め込み構造体の探索において、型の出現回数(multiplicity)情報をより正確に伝播できるようになりました。これにより、複数の埋め込みパスを通じて到達可能なフィールド(名前の衝突があるフィールド)を正しく識別し、それらを結果として返さないという、Go言語の仕様に準拠した振る舞いを実現しています。

関連リンク

  • Go言語のreflectパッケージのドキュメント: https://pkg.go.dev/reflect
  • Go言語の構造体の埋め込みに関する公式ドキュメントやチュートリアル(Goのバージョンによってリンクが異なる場合がありますが、Go embeddingなどで検索すると見つかります)

参考にした情報源リンク