[インデックス 12469] ファイルの概要
このコミットは、Go言語のテストスイートに新しいテストケースを追加するものです。具体的には、test/ddd.go
ファイルに、インターフェースの継承(埋め込み)と可変長引数(variadic function)を持つメソッドに関するテストが追加されています。このテストは、gccgo
コンパイラがこの種のコードに対して誤ったエラーを報告していた問題を特定し、修正するために導入されました。
コミット
test: add inherited interface test to ddd.go
The gccgo compiler incorrectly gave an error for this code.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5767043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e54ad64ff346281b707c75c72566b63080864dcf
元コミット内容
commit e54ad64ff346281b707c75c72566b63080864dcf
Author: Ian Lance Taylor <iant@golang.org>
Date: Wed Mar 7 08:24:10 2012 -0800
test: add inherited interface test to ddd.go
The gccgo compiler incorrectly gave an error for this code.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5767043
---
test/ddd.go | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n
変更の背景
このコミットの主な背景は、gccgo
コンパイラにおけるバグの修正です。gccgo
は、Go言語のプログラムをコンパイルするためのGCCベースのフロントエンドです。Go言語の仕様では、構造体にインターフェースを埋め込むことで、そのインターフェースのメソッドを「継承」し、構造体自身がそのインターフェースを満たすことができます。また、Goは可変長引数を持つ関数(variadic functions)をサポートしています。
このコミットが追加された当時、gccgo
は、構造体に埋め込まれたインターフェースが可変長引数を持つメソッドを定義している場合に、その構造体を通じてそのメソッドを呼び出す際に誤ってコンパイルエラーを発生させていました。このコミットは、この特定のシナリオを再現するテストケースをtest/ddd.go
に追加することで、バグの存在を明確にし、その修正を促すことを目的としています。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは「暗黙的」に満たされます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たしているとみなされます。明示的なimplements
キーワードは不要です。
構造体へのインターフェースの埋め込み(Embedded Interfaces)
Goの構造体は、他の構造体やインターフェースを「埋め込む」ことができます。インターフェースを構造体に埋め込むと、その構造体は埋め込まれたインターフェースのメソッドセットを「継承」します。これにより、構造体のインスタンスを通じて、埋め込まれたインターフェースのメソッドを直接呼び出すことができるようになります。これは、Goにおけるポリモーフィズムを実現する強力なメカニズムの一つです。
例:
type MyInterface interface {
DoSomething()
}
type MyStruct struct {
MyInterface // MyInterfaceを埋め込む
}
func (m *MyStruct) DoSomething() {
// MyStructがMyInterfaceのメソッドを実装
}
この場合、MyStruct
はMyInterface
を埋め込んでいるため、MyStruct
のインスタンスはMyInterface
のメソッドを呼び出すことができます。
可変長引数(Variadic Functions)
Go言語では、関数の最後のパラメータに...
を付けることで、その関数が可変長引数を受け取ることを宣言できます。可変長引数は、関数内でスライスとして扱われます。
例:
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
このsum
関数は、任意の数のint
型引数を受け取ることができます。
gccgo
コンパイラ
gccgo
は、GCC(GNU Compiler Collection)のフロントエンドの一つで、Go言語のソースコードをコンパイルするために使用されます。Go言語の公式コンパイラであるgc
(Go Compiler)とは異なる実装であり、Go言語の仕様に準拠しつつも、異なる最適化やコード生成を行うことがあります。そのため、gc
では問題なくコンパイルできるコードがgccgo
ではエラーになる、あるいはその逆のケースが発生することがあります。このコミットは、まさにそのようなgccgo
特有のバグを修正するためのテストケースです。
技術的詳細
このコミットで追加されたテストケースは、以下のGo言語の機能の組み合わせがgccgo
で正しく処理されない問題に対処しています。
- インターフェースの定義:
type I interface { Sum(...int) int }
というインターフェースが定義されています。このインターフェースは、可変長引数を受け取りint
を返すSum
というメソッドを一つだけ持ちます。 - 既存の型
U
:test/ddd.go
には既にU
という構造体が存在し、そのポインタ型*U
がSum(...int) int
というメソッドを実装しています。これにより、*U
型はI
インターフェースを暗黙的に満たします。 - 匿名フィールドとしてのインターフェースの埋め込み: 新しいテストコードでは、
s
という匿名構造体が定義され、その中にI
インターフェースが匿名フィールドとして埋め込まれています。
このvar s struct { I }
s
は、I
インターフェースを埋め込んでいるため、s
のインスタンスを通じてI
インターフェースのメソッド(この場合はSum
)を呼び出すことができます。 - インターフェース変数の代入:
s.I = &u
という行で、*U
型の変数&u
がs
に埋め込まれたI
インターフェースに代入されます。これは、*U
がI
インターフェースを満たしているため有効な操作です。 - 埋め込まれたインターフェースメソッドの呼び出し: 最後に、
s.Sum(2, 3, 5, 8)
という形で、s
を通じてSum
メソッドが可変長引数で呼び出されます。
gccgo
コンパイラは、このs.Sum(...)
の呼び出しにおいて、埋め込まれたインターフェースの可変長引数メソッドの解決に失敗し、誤ったコンパイルエラーを報告していました。このテストケースは、この特定のコードパスを網羅し、gccgo
がGo言語の仕様に正しく準拠していることを確認するために不可欠です。
コアとなるコードの変更箇所
--- a/test/ddd.go
+++ b/test/ddd.go
@@ -60,6 +60,10 @@ type U struct {
*T
}
+type I interface {
+ Sum(...int) int
+}
+
func main() {
if x := sum(1, 2, 3); x != 6 {
println("sum 6", x)
@@ -207,6 +211,14 @@ func main() {
println("i(=u).Sum", x)
panic("fail")
}\n+\tvar s struct {\n+\t\tI\n+\t}\n+\ts.I = &u\n+\tif x := s.Sum(2, 3, 5, 8); x != 18 {\n+\t\tprintln("s{&u}.Sum", x)\n+\t\tpanic("fail")\n+\t}\n \t/* TODO(rsc): Enable once nested method expressions work.\n \tif x := (*U).Sum(&U{}, 1, 3, 5, 2); x != 11 {\n \t\tprintln("(*U).Sum", x)\n```
## コアとなるコードの解説
追加されたコードは、`test/ddd.go`ファイルの`main`関数内に新しいテストブロックを導入しています。
1. **`type I interface { Sum(...int) int }`**:
`I`という新しいインターフェースが定義されています。このインターフェースは、`Sum`というメソッドを一つだけ持ちます。`Sum`メソッドは可変長引数(`...int`)を受け取り、`int`型の値を返します。
2. **`var s struct { I }`**:
`s`という匿名構造体型の変数が宣言されています。この構造体は、`I`インターフェースを匿名フィールドとして埋め込んでいます。Goの仕様により、構造体にインターフェースが匿名フィールドとして埋め込まれると、その構造体は埋め込まれたインターフェースのメソッドセットを「継承」します。つまり、`s`のインスタンスは`I`インターフェースのメソッド(この場合は`Sum`)を直接呼び出すことができるようになります。
3. **`s.I = &u`**:
既存の`U`構造体のインスタンス`u`のアドレス(`&u`)が、`s`に埋め込まれた`I`インターフェースに代入されています。これは、`*U`型(`u`のアドレスの型)が`I`インターフェースの`Sum(...int) int`メソッドを実装しているため、有効な代入です。これにより、`s`を通じて`I`インターフェースのメソッドを呼び出すと、実質的に`*U`型の`Sum`メソッドが実行されるようになります。
4. **`if x := s.Sum(2, 3, 5, 8); x != 18 { ... }`**:
`s`を通じて`Sum`メソッドが呼び出されています。引数として`2, 3, 5, 8`という可変長引数が渡されています。`Sum`メソッドはこれらの引数の合計を返すはずなので、期待される結果は`2 + 3 + 5 + 8 = 18`です。もし結果が`18`でなければ、テストは失敗し、`panic("fail")`が呼び出されます。
このテストブロック全体が、`gccgo`が以前誤ってエラーを報告していた特定のコードパターンを再現し、その修正を検証するために設計されています。
## 関連リンク
* Go CL 5767043: [https://golang.org/cl/5767043](https://golang.org/cl/5767043)
## 参考にした情報源リンク
* 上記のGo CL (Change List) へのリンクが主要な情報源です。
* Go言語の公式ドキュメント(インターフェース、構造体の埋め込み、可変長引数に関するセクション)
* [https://go.dev/tour/methods/9](https://go.dev/tour/methods/9) (Interfaces)
* [https://go.dev/tour/methods/10](https://go.dev/tour/methods/10) (Interfaces are implemented implicitly)
* [https://go.dev/tour/moretypes/15](https://go.dev/tour/moretypes/15) (Struct embedding)
* [https://go.dev/tour/moretypes/19](https://go.dev/tour/moretypes/19) (Variadic functions)
* GCCgoに関する情報(必要に応じて)
* [https://gcc.gnu.org/onlinedocs/gccgo/](https://gcc.gnu.org/onlinedocs/gccgo/) (GCCgo documentation)