[インデックス 13841] ファイルの概要
このコミットは、Goコンパイラ (cmd/gc
) におけるバグ修正を目的としています。具体的には、式を持たない switch
ステートメント(いわゆる「タグなしswitch」)において、暗黙的に評価される true
のブール値がインターフェース型に正しく変換されないために発生していたコード生成エラーを修正します。
コミット
commit 551e26382385a91cbe9cfc94b1327d29f030f254
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Mon Sep 17 21:29:10 2012 +0200
cmd/gc: add missing conversion from bool to interface in switches.
In switches without an expression, the compiler would not convert the implicit true to an interface, causing codegen errors.
Fixes #3980.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6497147
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/551e26382385a91cbe9cfc94b1327d29f030f254
元コミット内容
cmd/gc: add missing conversion from bool to interface in switches.
In switches without an expression, the compiler would not convert the implicit true to an interface, causing codegen errors.
Fixes #3980.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6497147
変更の背景
Go言語の switch
ステートメントには、式を伴わない形式があります。この形式では、各 case
節の条件式が true
と評価されるかどうかで分岐が行われます。例えば、switch { case x > 0: ... }
のような記述です。
このコミットが修正する問題は、このような式を伴わない switch
ステートメントにおいて、コンパイラが暗黙的に true
と評価されるブール値をインターフェース型に変換する処理を適切に行っていなかったことに起因します。特に、switch i := interface{}(true); { case i: ... }
のように、true
がインターフェース型として扱われる必要がある場合に、コンパイラが正しいコードを生成できず、実行時エラーや予期せぬ動作を引き起こしていました。
コミットメッセージにある Fixes #3980
は、この問題がGoのIssueトラッカーで報告されていたことを示しています。ただし、現在のGoのGitHubリポジトリではこのIssue番号は存在しないため、古いIssueトラッカーの番号であるか、既に統合・クローズされたものと考えられます。
前提知識の解説
Go言語の switch
ステートメント
Go言語の switch
ステートメントには主に2つの形式があります。
-
式を伴う
switch
(Expression Switch):switch
キーワードの後に式が続き、その式の値と各case
節の値を比較します。x := 10 switch x { case 5: fmt.Println("x is 5") case 10: fmt.Println("x is 10") default: fmt.Println("x is something else") }
-
式を伴わない
switch
(Expressionless Switch / Tagless Switch):switch
キーワードの後に式がなく、各case
節の条件式がブール値として評価されます。最初にtrue
と評価されたcase
節が実行されます。これは一連のif-else if-else
ステートメントの糖衣構文(シンタックスシュガー)と考えることができます。x := 10 switch { case x < 0: fmt.Println("x is negative") case x == 0: fmt.Println("x is zero") case x > 0: fmt.Println("x is positive") // これが実行される }
このコミットで問題となっていたのは、この「式を伴わない
switch
」の内部処理です。
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは「暗黙的」に満たされます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たします。
インターフェース型の変数は、任意の基底型(スカラー型、構造体、関数など)の値を保持できます。このとき、値はインターフェース型に「変換」されます。内部的には、インターフェース値は、保持している値の型情報と値自体の2つのポインタ(または値)で構成されます。
このコミットの文脈では、bool
型の値(true
)が interface{}
(空インターフェース、任意の値を保持できるインターフェース)型に変換される際のコンパイラの挙動が問題となっていました。
Goコンパイラ (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する役割を担っています。コンパイラは、構文解析、型チェック、中間コード生成、最適化、最終的なコード生成といった複数のフェーズを経て処理を行います。
このコミットの変更は、コンパイラの型チェックおよび中間コード生成フェーズ、特に switch
ステートメントの処理ロジック (src/cmd/gc/swt.c
) に関連しています。
技術的詳細
このコミットの核心は、Goコンパイラの src/cmd/gc/swt.c
ファイルにおける switch
ステートメントの処理ロジックの修正です。
swt.c
は、Goの switch
ステートメントのコンパイルを担当する部分です。特に exprbsw
関数は、式を伴う switch
の各 case
節の条件を処理します。しかし、式を伴わない switch
の場合でも、内部的には true
という暗黙の式が扱われることがあります。
問題は、case
節の式がインターフェース型である場合、例えば case i:
のように、i
が interface{}
型の変数であるときに発生していました。この i
の値が true
であるかどうかを評価する際に、コンパイラが bool
型の true
を interface{}
型に正しく変換するコードを生成できていなかったのです。
修正は主に以下の2点です。
-
exprbsw
関数内のOCONVIFACE
チェックの追加:exprbsw
関数内で、assignop
関数を使って型変換の必要性をチェックしています。assignop
は、ある型から別の型への代入が可能かどうか、またその際にどのような操作(変換など)が必要かを判断するコンパイラ内部の関数です。OCONVIFACE
は、インターフェース型への変換が必要であることを示す内部的なオペレーションコードです。 追加されたコードは、n->left->type
(case
節の式の型)とexprname->type
(switch
式の型、式を伴わない場合は暗黙のtrue
の型)の間でOCONVIFACE
変換が必要な場合、goto snorm;
を実行して通常の処理フローにジャンプさせます。これにより、インターフェース変換が適切に行われるパスを通るようになります。 -
式を伴わない
switch
のexprname
の初期化:exprswitch
関数はswitch
ステートメント全体を処理します。式を伴わないswitch
の場合、sw->ntest
はnil
になります。このとき、exprname
(switch
式の評価結果を保持する一時変数)がnodbool(arg == Strue)
で初期化されるようになりました。nodbool
はブール値のリテラルノードを作成する関数です。これにより、暗黙のtrue
がコンパイラ内部で正しくブール値として表現され、その後のインターフェース変換処理に引き渡されるようになります。
これらの変更により、コンパイラは bool
型の true
を interface{}
型に変換する際に必要な内部的な処理(OCONVIFACE
オペレーション)を認識し、適切なコードを生成できるようになりました。
テストファイル test/switch.go
に追加されたテストケースは、この修正が正しく機能することを確認するためのものです。特に、switch i := interface{}(true); { case i: ... }
のようなケースが追加され、以前は壊れていた(was broken: see issue 3980
とコメントされている)挙動が修正されたことを検証しています。
コアとなるコードの変更箇所
src/cmd/gc/swt.c
の変更点:
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -442,6 +442,10 @@ exprbsw(Case *c0, int ncase, int arg)
n = c0->node;
lno = setlineno(n);
+ if(assignop(n->left->type, exprname->type, nil) == OCONVIFACE ||
+ assignop(exprname->type, n->left->type, nil) == OCONVIFACE)
+ goto snorm;
+
switch(arg) {
case Strue:
a = nod(OIF, N, N);
@@ -457,6 +461,7 @@ exprbsw(Case *c0, int ncase, int arg)
break;
default:
+ snorm:
a = nod(OIF, N, N);
a->ntest = nod(OEQ, exprname, n->left); // if name == val
typecheck(&a->ntest, Erv);
@@ -520,6 +525,8 @@ exprswitch(Node *sw)
exprname = temp(sw->ntest->type);
cas = list1(nod(OAS, exprname, sw->ntest));
typechecklist(cas, Etop);
+ } else {
+ exprname = nodbool(arg == Strue);
}
c0 = mkcaselist(sw, arg);
test/switch.go
の変更点:
--- a/test/switch.go
+++ b/test/switch.go
@@ -294,6 +294,17 @@ func main() {
assert(false, `i should be "hello"`)
}
+ // switch on implicit bool converted to interface
+ // was broken: see issue 3980
+ switch i := interface{}(true); {
+ case i:
+ assert(true, "true")
+ case false:
+ assert(false, "i should be true")
+ default:
+ assert(false, "i should be true")
+ }
+
// switch on array.
switch ar := [3]int{1, 2, 3}; ar {
case [3]int{1,2,3}:
コアとなるコードの解説
src/cmd/gc/swt.c
の変更点
-
exprbsw
関数内のif
文とgoto snorm;
:if(assignop(n->left->type, exprname->type, nil) == OCONVIFACE || assignop(exprname->type, n->left->type, nil) == OCONVIFACE) goto snorm;
このコードは、
case
節の式 (n->left
) の型と、switch
式の評価結果を保持する一時変数 (exprname
) の型との間で、インターフェース変換 (OCONVIFACE
) が必要かどうかをチェックしています。assignop
はコンパイラ内部の関数で、型間の代入互換性や必要な変換の種類を判断します。 もしどちらかの方向でインターフェース変換が必要な場合、処理はsnorm
ラベルにジャンプします。snorm
ラベル以降のコードは、if name == val
のような通常の比較ノードを生成する汎用的なパスです。これにより、ブール値からインターフェースへの変換が必要な特殊なケースでも、コンパイラが適切な比較ロジックを生成できるようになります。 -
snorm:
ラベルの追加:default:
の直前にsnorm:
ラベルが追加されました。これは、上記のgoto snorm;
のジャンプ先となります。この変更自体は機能的なものではなく、コードの構造を調整したものです。 -
exprswitch
関数内のelse
ブロック:} else { exprname = nodbool(arg == Strue); }
exprswitch
関数はswitch
ステートメント全体の処理を統括します。if (sw->ntest != nil)
の条件は、switch
ステートメントに明示的な式があるかどうかを判断します。else
ブロックは、式を伴わないswitch
(タグなしswitch)の場合に実行されます。 このelse
ブロック内で、exprname
(switch
式の評価結果を保持する一時変数)がnodbool(arg == Strue)
で初期化されるようになりました。arg
はStrue
(true
を意味する内部定数)と比較され、その結果に基づいてブール値のリテラルノードが作成され、exprname
に割り当てられます。これにより、式を伴わないswitch
の内部で暗黙的に扱われるtrue
が、コンパイラ内部で明示的なブール値として表現され、その後の型チェックやコード生成フェーズで正しく処理されるようになります。
test/switch.go
の変更点
// switch on implicit bool converted to interface
// was broken: see issue 3980
switch i := interface{}(true); {
case i:
assert(true, "true")
case false:
assert(false, "i should be true")
default:
assert(false, "i should be true")
}
このテストケースは、修正されたバグを直接的に検証するものです。
switch i := interface{}(true);
の部分で、true
というブール値が interface{}
型の変数 i
に代入されています。
その後の case i:
は、i
の値が true
と評価されるかどうかをチェックします。
修正前は、この true
がインターフェース型に変換される際に問題が発生し、case i:
が正しく評価されなかったり、コード生成エラーになったりしていました。
このテストケースが assert(true, "true")
を通ることで、コンパイラが bool
から interface{}
への暗黙的な変換を正しく処理し、switch
ステートメントが期待通りに動作することが確認されます。
関連リンク
- Go CL (Change List): https://golang.org/cl/6497147
参考にした情報源リンク
- Go言語の
switch
ステートメントに関する公式ドキュメントやチュートリアル - Go言語のインターフェースに関する公式ドキュメントやチュートリアル
- Goコンパイラのソースコード (
src/cmd/gc/
) golang issue 3980
でのWeb検索結果(現在のGoリポジトリでは該当するIssueが見つからなかったことを確認)- https://github.com/grpc/grpc-go/issues/3980 (関連なし)
- https://github.com/fatedier/frp/issues/3980 (関連なし)
- このIssue番号は、GoのIssueトラッカーがGoogle CodeからGitHubに移行する前の古い番号である可能性が高いです。