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

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

このコミットは、Goコンパイラのsrc/cmd/gc/swt.cファイルにおいて、switch文のcase節内での特定の代入形式の記述を禁止する変更と、それに関連するテストファイルtest/typeswitch.goの修正を含んでいます。具体的には、型アサーション、マップのインデックス操作、チャネルからの受信といった操作がswitchcase式として直接使用できなくなりました。

コミット

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): のような記述は、vbool型であるかどうかのチェックと、もしそうであればその値をxに代入するという二重の意味を持つため、コンパイラにとっては特別な扱いが必要になります。

このような暗黙的な挙動や複雑な変換は、言語のセマンティクスを曖昧にし、開発者がswitch文の動作を直感的に理解するのを妨げる可能性があります。また、コンパイラの実装も複雑になり、バグの温床となるリスクも増大します。

このコミットは、これらの「代入ケース」を明示的に禁止することで、switch文のcase節は純粋な真偽値の評価(式がtrueになるかどうか)に限定されるという、よりシンプルで予測可能なセマンティクスを確立しようとしたものと推測されます。これにより、言語の設計がより堅牢になり、コンパイラのコードも簡素化され、将来的なメンテナンスや最適化が容易になります。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

  1. 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にはその型にアサーションされた値が代入されます。
  2. 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: コンパイラが構文エラーや意味エラーを検出した際に、エラーメッセージを出力するための関数。
  3. 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]: のような記述は、マップmkeyが存在するかどうかを真偽値として評価し、存在すればその値をxに代入する、という挙動をしていた可能性があります。Go言語では、マップの要素アクセスは2つの戻り値を返すことができ、2番目の戻り値でキーの存在を確認します。このコミット以前は、switchcaseでこの挙動が暗黙的に扱われていたかもしれません。
  • 型アサーション (interface.(type)): case x := v.(bool): のような記述は、インターフェース変数vbool型であるかどうかを真偽値として評価し、もし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言語のコンパイラに関する技術ブログや解説記事 (当時の情報があれば)