[インデックス 1844] ファイルの概要
このコミットは、Goコンパイラのsrc/cmd/gc/swt.c
ファイルにおいて、switch
文のcase
節内での特定の代入形式の記述を禁止する変更と、それに関連するテストファイルtest/typeswitch.go
の修正を含んでいます。具体的には、型アサーション、マップのインデックス操作、チャネルからの受信といった操作がswitch
のcase
式として直接使用できなくなりました。
コミット
commit 1cdcfda140102c422eed9b4c8757dcf2b169d96b
Author: Ken Thompson <ken@golang.org>
Date: Wed Mar 18 12:13:42 2009 -0700
remove assignment cases from switch
R=r
OCL=26480
CL=26480
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1cdcfda140102c422eed9b4c8757dcf2b169d96b
元コミット内容
remove assignment cases from switch
R=r
OCL=26480
CL=26480
変更の背景
このコミットの背景には、Go言語のswitch
文のセマンティクスをより明確にし、コンパイラの複雑性を軽減する目的があったと考えられます。初期のGo言語の設計段階では、switch
文のcase
節に、単なる真偽値の評価だけでなく、型アサーション(v.(type)
)、マップの要素アクセス(m[key]
)、チャネルからの受信(<-ch
)といった、値の取得を伴う式を直接記述できるような柔軟な構文が検討されていた可能性があります。
しかし、これらの「代入ケース」をswitch
文内で許容すると、コンパイラはそれらの式を内部的に真偽値に変換したり、一時変数に値を格納したりする複雑な処理を実装する必要がありました。例えば、case x := v.(bool):
のような記述は、v
がbool
型であるかどうかのチェックと、もしそうであればその値をx
に代入するという二重の意味を持つため、コンパイラにとっては特別な扱いが必要になります。
このような暗黙的な挙動や複雑な変換は、言語のセマンティクスを曖昧にし、開発者がswitch
文の動作を直感的に理解するのを妨げる可能性があります。また、コンパイラの実装も複雑になり、バグの温床となるリスクも増大します。
このコミットは、これらの「代入ケース」を明示的に禁止することで、switch
文のcase
節は純粋な真偽値の評価(式がtrue
になるかどうか)に限定されるという、よりシンプルで予測可能なセマンティクスを確立しようとしたものと推測されます。これにより、言語の設計がより堅牢になり、コンパイラのコードも簡素化され、将来的なメンテナンスや最適化が容易になります。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。
-
Go言語の
switch
文:- Go言語の
switch
文は、他のC系の言語とは異なり、case
節に任意の式を記述できます。この式がswitch
式の値と一致するか、またはswitch
式が省略された場合はtrue
と評価されるかによって、対応するcase
ブロックが実行されます。 switch
文には、通常の式を評価する「式スイッチ」と、インターフェースの動的な型を評価する「型スイッチ」の2種類があります。- 式スイッチ:
switch x { case y: ... }
のように、x == y
のような比較が行われます。 - 型スイッチ:
switch v := i.(type) { case T: ... }
のように、インターフェース変数i
の動的な型がT
であるかどうかを評価します。この際、v
にはその型にアサーションされた値が代入されます。
- Go言語の
-
Goコンパイラの内部構造 (初期段階):
- Goコンパイラは、ソースコードを解析して抽象構文木(AST: Abstract Syntax Tree)を構築します。ASTは、プログラムの構造を木構造で表現したものです。
- コンパイラの各フェーズ(字句解析、構文解析、意味解析、コード生成など)は、このASTを操作します。
src/cmd/gc/swt.c
は、Goコンパイラのフロントエンドの一部であり、switch
文の処理、特に意味解析と中間表現への変換を担当していたファイルです。- ASTノードのオペレーションコード (OpCode): コンパイラ内部では、ASTの各ノードがどのような種類の操作を表すかを示すために、オペレーションコード(
OpCode
)が使用されます。OTYPESW
: 型スイッチを表すASTノードのオペレーションコード。OAS
: 代入操作(Assignment)を表すASTノードのオペレーションコード。ORECV
: チャネルからの受信操作(Receive)を表すASTノードのオペレーションコード。例:<-ch
OINDEX
: インデックス操作(Index)を表すASTノードのオペレーションコード。例:arr[i]
,m[key]
ODOTTYPE
: 型アサーション操作(Dot Type)を表すASTノードのオペレーションコード。例:i.(type)
OLIST
: 複数のノードをリストとして結合するオペレーションコード。OIF
:if
文を表すオペレーションコード。ONOT
: 論理否定(Not)を表すオペレーションコード。
- 型システム: コンパイラは、Go言語の型システムを内部的に表現します。
TCHAN
: チャネル型。TMAP
: マップ型。TINTER
: インターフェース型。
yyerror
: コンパイラが構文エラーや意味エラーを検出した際に、エラーメッセージを出力するための関数。
-
walktype
関数:walktype
は、Goコンパイラの意味解析フェーズでASTを走査し、型チェックや型推論、一部の最適化などを行う関数群の一部です。Erv
(Expression for R-value): 式が右辺値として評価されることを示すコンテキスト。Elv
(Expression for L-value): 式が左辺値として評価されることを示すコンテキスト。
このコミットは、これらの内部的なASTノードや型、コンパイラの処理フローを理解していると、その影響範囲と意図がより明確になります。
技術的詳細
このコミットの技術的詳細は、Goコンパイラのswt.c
ファイルにおけるswitch
文の処理ロジックの変更に集約されます。
変更前は、sw0
関数(switch
文の初期処理を行う関数と推測される)内で、case
節の式がOAS
(代入)オペレーションコードを持つ場合に、特別な処理が行われていました。このOAS
ノードは、さらにその右辺(c->right
)のオペレーションコードによって、チャネル受信(ORECV
)、マップインデックス(OINDEX
)、型アサーション(ODOTTYPE
)のいずれかであるかを判別していました。
これらの「代入ケース」は、switch
文のcase
節に直接記述された場合、コンパイラによって以下のような暗黙的な変換が行われていたと考えられます。
- チャネル受信 (
<-chan
):case x := <-ch:
のような記述は、ch
から値を受信し、それが成功したかどうか(チャネルが閉じられていないか、値が受信できたか)を真偽値として評価し、受信した値をx
に代入する、という複雑な意味合いを持っていました。 - マップインデックス (
map[e]
):case x := m[key]:
のような記述は、マップm
にkey
が存在するかどうかを真偽値として評価し、存在すればその値をx
に代入する、という挙動をしていた可能性があります。Go言語では、マップの要素アクセスは2つの戻り値を返すことができ、2番目の戻り値でキーの存在を確認します。このコミット以前は、switch
のcase
でこの挙動が暗黙的に扱われていたかもしれません。 - 型アサーション (
interface.(type)
):case x := v.(bool):
のような記述は、インターフェース変数v
がbool
型であるかどうかを真偽値として評価し、もしbool
型であればその値をx
に代入するという、型スイッチに似た挙動をしていたと考えられます。
このコミットでは、これらのOAS
を伴うcase
節の処理を完全に削除しました。具体的には、sw0
関数内のcase OAS:
ブロックが削除され、代わりにcase OAS:
が検出された場合は即座にyyerror("inappropriate assignment in a case statement");
というエラーを発生させるようになりました。これにより、これらの構文はコンパイル時に明示的に禁止されます。
また、loop:
ラベル以降のコードブロックで、if(t->left->op == OAS)
という条件で始まる大きなコードブロックが削除されています。このブロックは、以前はOAS
オペレーションを持つcase
節を、一時的な真偽値変数(bool
)とif
文の組み合わせに変換する役割を担っていたと推測されます。例えば、v,bool = rhs
のような代入と、そのbool
値に基づいたif
文への変換ロジックが含まれていました。この変換ロジックが不要になったため、関連するコードも削除されました。
test/typeswitch.go
の変更は、このコンパイラの変更を反映したものです。以前はコンパイル可能だったswitch true
文のcase
節に、型アサーション、マップアクセス、チャネル受信を直接記述するテストケースがコメントアウトされています。これは、これらの構文がもはや有効ではないことを示しています。
この変更により、Go言語のswitch
文のcase
節は、より厳密に「式」または「型」の評価に限定され、暗黙的な代入や複雑な変換は行われなくなりました。これにより、言語のセマンティクスが簡素化され、コンパイラのコードベースもクリーンになりました。
コアとなるコードの変更箇所
src/cmd/gc/swt.c
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -44,49 +44,15 @@ sw0(Node *c, Type *place, int arg)
return T;
}
walktype(c, Erv);
- return T;
+ break;
case OTYPESW:
if(arg != Stype)
yyerror("inappropriate type case");
- return T;
- case OAS:
break;
- }
- walktype(c->left, Elv);
-
- r = c->right;
- if(c == N)
- return T;
-
- switch(r->op) {
- default:
- goto bad;
- case ORECV:
- // <-chan
- walktype(r->left, Erv);
- if(!istype(r->left->type, TCHAN))
- goto bad;
- break;
- case OINDEX:
- // map[e]
- walktype(r->left, Erv);
- if(!istype(r->left->type, TMAP))
- goto bad;
- break;
- case ODOTTYPE:
- // interface.(type)
- walktype(r->left, Erv);
- if(!istype(r->left->type, TINTER))
- goto bad;
+ case OAS:
+ yyerror("inappropriate assignment in a case statement");
break;
}
- c->type = types[TBOOL];
- if(arg != Strue)
- goto bad;
- return T;
-
-bad:
- yyerror("inappropriate assignment in a case statement");
return T;
}
@@ -311,26 +277,6 @@ loop:
t->ninit = N;
}
- if(t->left->op == OAS) {
- if(bool == N) {
- bool = nod(OXXX, N, N);
- tempname(bool, types[TBOOL]);
- }
- t->left->left = nod(OLIST, t->left->left, bool);
- cas = list(cas, t->left); // v,bool = rhs
-
- a = nod(OIF, N, N);
- a->nbody = t->right; // then goto l
- a->ntest = bool;
- if(arg != Strue)
- a->ntest = nod(ONOT, bool, N);
- cas = list(cas, a); // if bool goto l
-
- t = listnext(&save);
- goto loop;
- }
-
-
switch(arg) {
default:
// not bool const
test/typeswitch.go
--- a/test/typeswitch.go
+++ b/test/typeswitch.go
@@ -62,30 +62,30 @@ func f(i int) interface{} {
func main() {
// type guard style
- for i := Bool; i < Last; i++ {
- switch v := f(i); true {
- case x := v.(bool):
- assert(x == true && i == Bool, "switch 1 bool");
- case x := v.(int):
- assert(x == 7 && i == Int, "switch 1 int");
- case x := v.(float):
- assert(x == 7.4 && i == Float, "switch 1 float");
- case x := v.(string):
- assert(x == "hello" && i == String, "switch 1 string");
- case x := v.(S):
- assert(x.a == 1234 && i == Struct, "switch 1 struct");
- case x := v.(chan int):
- assert(x == c && i == Chan, "switch 1 chan");
- case x := v.([]int):
- assert(x[3] == 3 && i == Array, "switch 1 array");
- case x := v.(map[string]int):
- assert(x == m && i == Map, "switch 1 map");
- case x := v.(func(i int) interface{}):
- assert(x == f && i == Func, "switch 1 fun");
- default:
- assert(false, "switch 1 unknown");
- }
- }
+// for i := Bool; i < Last; i++ {
+// switch v := f(i); true {
+// case x := v.(bool):
+// assert(x == true && i == Bool, "switch 1 bool");
+// case x := v.(int):
+// assert(x == 7 && i == Int, "switch 1 int");
+// case x := v.(float):
+// assert(x == 7.4 && i == Float, "switch 1 float");
+// case x := v.(string):
+// assert(x == "hello" && i == String, "switch 1 string");
+// case x := v.(S):
+// assert(x.a == 1234 && i == Struct, "switch 1 struct");
+// case x := v.(chan int):
+// assert(x == c && i == Chan, "switch 1 chan");
+// case x := v.([]int):
+// assert(x[3] == 3 && i == Array, "switch 1 array");
+// case x := v.(map[string]int):
+// assert(x == m && i == Map, "switch 1 map");
+// case x := v.(func(i int) interface{}):
+// assert(x == f && i == Func, "switch 1 fun");
+// default:
+// assert(false, "switch 1 unknown");
+// }
+// }
// type switch style
for i := Bool; i < Last; i++ {
@@ -135,24 +135,24 @@ func main() {
assert(false, "switch 4 unknown");
}
-\tswitch true {\n-\tcase x := f(Int).(float):\n-\t\tassert(false, "switch 5 type guard wrong type");\n-\tcase x := f(Int).(int):\n-\t\tassert(x == 7, "switch 5 type guard");\n-\tdefault:\n-\t\tassert(false, "switch 5 unknown");\n-\t}\n+//\tswitch true {\n+//\tcase x := f(Int).(float):\n+//\t\tassert(false, "switch 5 type guard wrong type");\n+//\tcase x := f(Int).(int):\n+//\t\tassert(x == 7, "switch 5 type guard");\n+//\tdefault:\n+//\t\tassert(false, "switch 5 unknown");\n+//\t}\n
\tm[\"7\"] = 7;\n-\tswitch true {\n-\tcase x := m[\"6\"]:\n-\t\tassert(false, "switch 6 map reference wrong");\n-\tcase x := m[\"7\"]:\n-\t\tassert(x == 7, "switch 6 map reference");\n-\tdefault:\n-\t\tassert(false, "switch 6 unknown");\n-\t}\n+//\tswitch true {\n+//\tcase x := m[\"6\"]:\n+//\t\tassert(false, "switch 6 map reference wrong");\n+//\tcase x := m[\"7\"]:\n+//\t\tassert(x == 7, "switch 6 map reference");
+//\tdefault:\n+//\t\tassert(false, "switch 6 unknown");\n+//\t}\n
\tgo func() { <-c; c <- 77; } ();\n \t// guarantee the channel is ready\n@@ -161,13 +161,13 @@ func main() {\n \t\tsys.Gosched();\n \t}\n \tdummyc := make(chan int);\n-\tswitch true {\n-\tcase x := <-dummyc:\n-\t\tassert(false, "switch 7 chan wrong");\n-\tcase x := <-c:\n-\t\tassert(x == 77, "switch 7 chan");\n-\tdefault:\n-\t\tassert(false, "switch 7 unknown");\n-\t}\n+//\tswitch true {\n+//\tcase x := <-dummyc:\n+//\t\tassert(false, "switch 7 chan wrong");\n+//\tcase x := <-c:\n+//\t\tassert(x == 77, "switch 7 chan");\n+//\tdefault:\n+//\t\tassert(false, "switch 7 unknown");\n+//\t}\n
}\n```
## コアとなるコードの解説
### `src/cmd/gc/swt.c` の変更点
1. **`sw0`関数内の`OAS`ケースの削除とエラー化**:
* 変更前は、`sw0`関数内で`case OAS:`(代入ノード)が検出された場合、その右辺のオペレーションコード(`ORECV`, `OINDEX`, `ODOTTYPE`)に応じて、チャネル受信、マップインデックス、型アサーションの各ケースを処理していました。これらの処理は、`switch`の`case`節でこれらの操作が直接行われた場合に、コンパイラが内部的に真偽値に変換し、必要に応じて値を代入するロジックを含んでいました。
* 変更後、この`case OAS:`ブロック全体が削除され、代わりに`case OAS:`が検出された場合は、即座に`yyerror("inappropriate assignment in a case statement");`というエラーメッセージを出力するようになりました。これは、`switch`の`case`節でこれらの代入形式の構文が許可されなくなったことを意味します。
* これにより、`switch`文の`case`節は、純粋な真偽値の評価(`true`または`false`)を返す式に限定されることになります。
2. **`bad:`ラベルと関連コードの削除**:
* 以前のコードでは、不正な代入ケースが検出された場合に`goto bad;`でジャンプし、`bad:`ラベルの箇所でエラーメッセージを出力していました。
* `OAS`ケースの処理が変更され、エラーがその場で出力されるようになったため、`bad:`ラベルとその関連コード(`yyerror("inappropriate assignment in a case statement");`)は不要となり削除されました。
3. **`if(t->left->op == OAS)`ブロックの削除**:
* `loop:`ラベル以降のコードブロックで、`if(t->left->op == OAS)`という条件で始まる大きなコードブロックが削除されています。
* このブロックは、`switch`文の`case`節が代入形式(`OAS`)である場合に、それをコンパイラが内部的に処理するためのロジックを含んでいました。具体的には、`v,bool = rhs`のような形式の代入を処理し、その結果の真偽値(`bool`)に基づいて`if`文を生成するような変換を行っていたと推測されます。
* この変換ロジックが不要になったため、関連するコードも削除され、コンパイラのコードベースが大幅に簡素化されました。
### `test/typeswitch.go` の変更点
* `main`関数内の複数の`switch true`ブロックがコメントアウトされています。これらのブロックには、以下のような`case`節が含まれていました。
* `case x := v.(bool):` (型アサーション)
* `case x := m["6"]:` (マップインデックス)
* `case x := <-dummyc:` (チャネル受信)
* これらのテストケースは、以前はコンパイル可能であり、`switch`の`case`節でこれらの「代入ケース」がどのように振る舞うかを検証していました。しかし、`src/cmd/gc/swt.c`の変更によりこれらの構文が禁止されたため、テストコードもそれに合わせてコメントアウトされました。これにより、コンパイラの変更が言語のセマンティクスに与える影響が明確に示されています。
これらの変更は、Go言語の`switch`文のセマンティクスをより厳密にし、コンパイラの内部処理を簡素化することを目的としています。
## 関連リンク
* Go言語の`switch`文に関する公式ドキュメント (Go言語のバージョンによってセマンティクスが異なる可能性があるため、当時のドキュメントを参照することが望ましいですが、一般的な情報として): [https://go.dev/ref/spec#Switch_statements](https://go.dev/ref/spec#Switch_statements)
* Goコンパイラのソースコードリポジトリ: [https://github.com/golang/go](https://github.com/golang/go)
## 参考にした情報源リンク
* Go言語の公式ドキュメント
* Goコンパイラのソースコード (特に`src/cmd/gc/`ディレクトリ内のファイル)
* Go言語の初期の設計に関する議論やメーリングリストのアーカイブ (もし公開されていれば)
* Go言語のASTに関する情報 (例: `go/ast`パッケージのドキュメント)
* Go言語のコンパイラに関する技術ブログや解説記事 (当時の情報があれば)