[インデックス 19456] ファイルの概要
このコミットは、Go言語のテストスイートにおいて、issue7863
に関連する既存のテストケースを拡張するものです。具体的には、test/fixedbugs/issue7863.go
ファイルに、メソッド値の挙動に関するより詳細な検証ロジックが追加されています。これは、過去に修正されたバグ(issue7863
)に対するテストカバレッジを強化し、回帰を防ぐことを目的としています。
コミット
- コミットハッシュ:
d432238fadd10fa514a04975dfe85719b5b8f377
- 作者: Russ Cox rsc@golang.org
- 日付: 2014年5月27日 火曜日 21:53:39 -0700
- コミットメッセージ:
test: expand issue7863 test
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d432238fadd10fa514a04975dfe85719b5b8f377
元コミット内容
commit d432238fadd10fa514a04975dfe85719b5b8f377
Author: Russ Cox <rsc@golang.org>
Date: Tue May 27 21:53:39 2014 -0700
test: expand issue7863 test
This was sitting in my client but I forgot hg add.
LGTM=bradfitz
R=bradfitz
CC=golang-codereviews
https://golang.org/cl/101800045
---
test/fixedbugs/issue7863.go | 53 ++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 48 insertions(+), 5 deletions(-)
diff --git a/test/fixedbugs/issue7863.go b/test/fixedbugs/issue7863.go
index 796db6a98f..97f2255350 100644
--- a/test/fixedbugs/issue7863.go
+++ b/test/fixedbugs/issue7863.go
@@ -6,12 +6,55 @@
package main
-import "time"
+import (
+ "fmt"
+)
+
+type Foo int64
+
+func (f *Foo) F() int64 {
+ return int64(*f)
+}
+
+type Bar int64
+
+func (b Bar) F() int64 {
+ return int64(b)
+}
+
+type Baz int32
+
+func (b Baz) F() int64 {
+ return int64(b)
+}
func main() {
-\tnow := time.Now()\n-\tf := now.Unix\n-\tif now.Unix() != f() {\n-\t\tprintln(\"BUG: \", now.Unix(), \"!=\", f())\n+\tfoo := Foo(123)\n+\tf := foo.F\n+\tif foo.F() != f() {\n+\t\tbug()\n+\t\tfmt.Println(\"foo.F\", foo.F(), f())\n+\t}\n+\tbar := Bar(123)\n+\tf = bar.F\n+\tif bar.F() != f() {\n+\t\tbug()\n+\t\tfmt.Println(\"bar.F\", bar.F(), f()) // duh!\n+\t}\n+\n+\tbaz := Baz(123)\n+\tf = baz.F\n+\tif baz.F() != f() {\n+\t\tbug()\n+\t\tfmt.Println(\"baz.F\", baz.F(), f())\n+\t}\n+}\n+\n+var bugged bool\n+\n+func bug() {\n+\tif !bugged {\n+\t\tbugged = true\n+\t\tfmt.Println(\"BUG\")\n \t}\n }\n```
## 変更の背景
このコミットの背景には、Go言語のコンパイラまたはランタイムにおける、メソッド値の取り扱いに関する過去のバグ(`issue7863`)が存在します。元のテストコードは非常に単純で、`time.Now().Unix`という特定のケースのみを検証していました。しかし、このバグはより広範な状況で発生する可能性があったため、その修正が完全であることを保証するために、より多様なシナリオをカバーするテストケースが必要とされました。
このコミットは、`issue7863`の修正が意図通りに機能し、将来の変更によって回帰しないことを確認するための、テストカバレッジの強化を目的としています。特に、異なるレシーバ型(ポインタレシーバと値レシーバ)や異なる基底型を持つメソッド値が正しく動作するかどうかを検証しています。
## 前提知識の解説
このコミットを理解するためには、以下のGo言語の概念を理解しておく必要があります。
* **メソッド (Methods)**: Go言語では、関数を型に関連付けることができます。これをメソッドと呼びます。メソッドはレシーバ(receiver)と呼ばれる特別な引数を持ち、これにより特定の型の値に対して操作を行うことができます。レシーバには、値レシーバ(`func (t T) MethodName(...)`)とポインタレシーバ(`func (t *T) MethodName(...)`)の2種類があります。
* **値レシーバ**: メソッドがレシーバのコピーを受け取ります。メソッド内でレシーバのフィールドを変更しても、元の値には影響しません。
* **ポインタレシーバ**: メソッドがレシーバへのポインタを受け取ります。メソッド内でレシーバのフィールドを変更すると、元の値も変更されます。
* **メソッド値 (Method Values)**: Go言語では、構造体のインスタンスからメソッドを「取り出す」ことができます。これをメソッド値と呼びます。メソッド値は、レシーバがすでにバインドされた関数のようなもので、引数リストからレシーバが取り除かれた形で呼び出すことができます。
```go
type MyStruct struct {
value int
}
func (m MyStruct) GetValue() int {
return m.value
}
func main() {
s := MyStruct{value: 10}
f := s.GetValue // メソッド値
fmt.Println(f()) // 10
}
```
この例では、`s.GetValue`は`s`というレシーバがバインドされたメソッド値`f`を生成します。`f`を呼び出す際には、レシーバを明示的に渡す必要はありません。
* **メソッド式 (Method Expressions)**: メソッド値と似ていますが、メソッド式はレシーバをバインドせずにメソッド自体を参照します。メソッド式は、レシーバを最初の引数として受け取る通常の関数のように振る舞います。
```go
type MyStruct struct {
value int
}
func (m MyStruct) GetValue() int {
return m.value
}
func main() {
f := MyStruct.GetValue // メソッド式
s := MyStruct{value: 10}
fmt.Println(f(s)) // 10
}
```
このコミットは、特にメソッド値の挙動に焦点を当てています。
* **Goの型システムとインターフェース**: Goの型システムは静的型付けですが、インターフェースを通じてポリモーフィズムを実現します。メソッド値やメソッド式がどのように型システムと相互作用するかは、Goのコンパイラがどのようにこれらを内部的に表現し、呼び出すかに関わってきます。
`issue7863`は、おそらくコンパイラがメソッド値を生成する際の内部的な不整合や、異なるレシーバ型(値レシーバとポインタレシーバ)を持つメソッド値の扱いに関するバグであったと推測されます。
## 技術的詳細
このコミットは、`issue7863`というバグが、Goコンパイラがメソッド値を処理する方法に関連していたことを示唆しています。Goでは、メソッド値はクロージャのような形で実装されることが多く、元のレシーバをキャプチャして、そのメソッドを呼び出すためのコードを生成します。
元の`issue7863.go`のテストは、`time.Now().Unix`という特定のメソッド値が、直接`time.Now().Unix()`を呼び出した場合と同じ結果を返すことを確認するものでした。これは、`Unix`メソッドが値レシーバを持つ`time.Time`型に対して定義されているため、値レシーバのメソッド値の基本的な動作を検証していました。
拡張されたテストでは、以下の点が追加されています。
1. **異なるレシーバ型**:
* `Foo`型はポインタレシーバを持つ`F()`メソッド(`func (f *Foo) F() int64`)を持ちます。
* `Bar`型は値レシーバを持つ`F()`メソッド(`func (b Bar) F() int64`)を持ちます。
* `Baz`型は値レシーバを持ち、基底型が`int32`である`F()`メソッド(`func (b Baz) F() int64`)を持ちます。
2. **異なる基底型**: `Baz`型は`int32`を基底型としており、`int64`を基底型とする`Foo`や`Bar`とは異なります。これにより、型変換や基底型の違いがメソッド値の挙動に影響を与えないことを確認しています。
3. **`bug()`ヘルパー関数**: テストが失敗した場合に一度だけ"BUG"と出力するためのヘルパー関数`bug()`が導入されています。これにより、複数のテストケースが失敗しても、冗長なエラーメッセージを防ぎます。
これらの追加されたテストケースは、コンパイラがポインタレシーバのメソッド値、値レシーバのメソッド値、そして異なる基底型を持つ値レシーバのメソッド値を、それぞれ正しく生成し、呼び出すことができるかを検証しています。特に、メソッド値を変数`f`に代入し、その`f()`を呼び出した結果が、元のオブジェクトから直接メソッドを呼び出した結果(例: `foo.F()`)と一致するかどうかを厳密にチェックしています。
もし`issue7863`がメソッド値の生成に関するバグであった場合、これらの多様なケースを追加することで、その修正が堅牢であり、将来のコンパイラの変更によって意図しない回帰が発生しないことを保証するのに役立ちます。
## コアとなるコードの変更箇所
変更は`test/fixedbugs/issue7863.go`ファイルに集中しています。
```diff
--- a/test/fixedbugs/issue7863.go
+++ b/test/fixedbugs/issue7863.go
@@ -6,12 +6,55 @@
package main
-import "time"
+import (
+ "fmt"
+)
+
+type Foo int64
+
+func (f *Foo) F() int64 {
+ return int64(*f)
+}
+
+type Bar int64
+
+func (b Bar) F() int64 {
+ return int64(b)
+}
+
+type Baz int32
+
+func (b Baz) F() int64 {
+ return int64(b)
+}
func main() {
-\tnow := time.Now()\n-\tf := now.Unix\n-\tif now.Unix() != f() {\n-\t\tprintln(\"BUG: \", now.Unix(), \"!=\", f())\n+\tfoo := Foo(123)\n+\tf := foo.F\n+\tif foo.F() != f() {\n+\t\tbug()\n+\t\tfmt.Println(\"foo.F\", foo.F(), f())\n+\t}\n+\tbar := Bar(123)\n+\tf = bar.F
+\tif bar.F() != f() {\n+\t\tbug()\n+\t\tfmt.Println(\"bar.F\", bar.F(), f()) // duh!\n+\t}\n+\n+\tbaz := Baz(123)\n+\tf = baz.F\n+\tif baz.F() != f() {\n+\t\tbug()\n+\t\tfmt.Println(\"baz.F\", baz.F(), f())\n+\t}\n+}\n+\n+var bugged bool\n+\n+func bug() {\n+\tif !bugged {\n+\t\tbugged = true\n+\t\tfmt.Println(\"BUG\")\n \t}\n }\
主な変更点は以下の通りです。
import "time"
が削除され、import "fmt"
が追加されました。Foo
,Bar
,Baz
という新しい型と、それぞれに定義されたF()
メソッドが追加されました。Foo
:int64
を基底とするポインタレシーバのメソッドを持つ型。Bar
:int64
を基底とする値レシーバのメソッドを持つ型。Baz
:int32
を基底とする値レシーバのメソッドを持つ型。
main
関数内のテストロジックが大幅に拡張されました。time.Now().Unix
に関する元のテストコードが削除されました。Foo
,Bar
,Baz
のインスタンスを作成し、それぞれのF()
メソッドをメソッド値として取得し、直接呼び出しと比較するテストが追加されました。
bugged
というグローバル変数と、bug()
というヘルパー関数が追加されました。これは、テストが失敗した際に一度だけ"BUG"というメッセージを出力するためのものです。
コアとなるコードの解説
追加されたコードは、Go言語におけるメソッド値の正確性を多角的に検証することを目的としています。
-
型定義とメソッド:
type Foo int64
とfunc (f *Foo) F() int64
は、ポインタレシーバを持つメソッドのテストケースを提供します。ポインタレシーバのメソッド値が正しく機能するかを確認します。type Bar int64
とfunc (b Bar) F() int64
は、値レシーバを持つメソッドのテストケースを提供します。これは、元のtime.Now().Unix
のケースに似ていますが、ユーザー定義型で検証します。type Baz int32
とfunc (b Baz) F() int64
は、異なる基底型(int32
)を持つ値レシーバのメソッドのテストケースを提供します。これにより、基底型の違いがメソッド値の挙動に影響を与えないことを確認します。
-
main
関数内のテストロジック: 各型に対して、以下のパターンでテストが行われています。instance := Type(value) f := instance.Method // メソッド値を取得 if instance.Method() != f() { // 直接呼び出しとメソッド値の呼び出しを比較 bug() fmt.Println("Type.Method", instance.Method(), f()) }
この比較は、Goコンパイラがメソッド値を生成する際に、レシーバの型、レシーバの種類(値かポインタか)、そしてメソッドのシグネチャを正しく処理しているかを検証するものです。もし
instance.Method()
とf()
の結果が異なれば、それはコンパイラがメソッド値を誤って生成しているか、または呼び出し規約に問題があることを示します。 -
bug()
関数:bug()
関数は、テストが失敗した際に一度だけ"BUG"というメッセージを出力するためのシンプルなフラグ管理です。これにより、テストの出力が冗長になるのを防ぎ、問題の特定を容易にします。
これらのテストケースは、Goコンパイラがメソッド値を生成する際の堅牢性を高め、特にissue7863
のようなバグの回帰を防ぐための重要な役割を果たしています。
関連リンク
- このコミットは、Go言語のコードレビューシステムであるGerrit上の変更リスト(Change List: CL)
https://golang.org/cl/101800045
に関連しています。ただし、このCL番号は非常に古いため、現在のGerritシステムでは直接アクセスできない可能性があります。
参考にした情報源リンク
- Go言語の公式ドキュメント(メソッド、型システムに関する情報)
- Go言語のソースコード(
test/fixedbugs/issue7863.go
) - Go言語のコミット履歴
- Go言語におけるメソッド値とメソッド式の概念に関する一般的なプログラミング情報
- GitHub: https://github.com/golang/go/commit/d432238fadd10fa514a04975dfe85719b5b8f377