[インデックス 1267] ファイルの概要
このコミットは、Go言語のコンパイラにおけるバグ修正を目的としています。具体的には、Go言語の構文上は許容されるはずの「空のswitch
ステートメント」が、当時のコンパイラではコンパイルエラーとなっていた問題を修正するためのものです。この変更により、空のswitch
ステートメントが正しくコンパイルされるようになり、言語仕様とコンパイラの挙動が一致するようになりました。
コミット
- コミットハッシュ:
4d76e8e142094cee071310dcfb98fd10527edfbc
- 作者: Robert Griesemer gri@golang.org
- コミット日時: 2008年12月3日 水曜日 10:41:43 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4d76e8e142094cee071310dcfb98fd10527edfbc
元コミット内容
- bug: empty switches don't compile
R=rsc
DELTA=32 (28 added, 3 deleted, 1 changed)
OCL=20301
CL=20340
変更の背景
Go言語の初期開発段階において、言語の構文仕様とコンパイラの実装との間に乖離が存在していました。このコミットが修正しようとしているのは、switch
ステートメントに関するその乖離の一つです。
Go言語のswitch
ステートメントは、他の多くのプログラミング言語と同様に、複数の条件分岐を簡潔に記述するための制御構造です。しかし、当時のGoコンパイラは、case
ラベルが一つも存在しない「空のswitch
ステートメント」を不正な構文として扱い、コンパイルエラーを発生させていました。
コミットメッセージ内のコメント「// empty switch is allowed according to syntax // unclear why it shouldn't be allowed
」が示すように、言語の設計者たちは、空のswitch
ステートメントが構文的には有効であると考えていました。これは、例えば将来的にcase
を追加する予定がある場合や、特定の条件で何も処理しないことを明示的に示す場合など、プログラミング上の意図として空のブロックを許容する設計思想があったためと考えられます。
このコンパイラの挙動は、言語仕様(あるいは設計者の意図)と実装の不一致であり、開発者が意図したコードがコンパイルできないというバグとして認識されました。このバグを修正し、言語の設計意図に沿ったコンパイラの挙動を実現することが、このコミットの背景にあります。
前提知識の解説
switch
ステートメント
switch
ステートメントは、プログラミングにおいて、一つの式の値に基づいて複数の異なるコードブロックの中から一つを実行するための制御構造です。Go言語のswitch
ステートメントは、他のC系の言語とは異なり、case
の最後にbreak
を明示的に書く必要がなく、デフォルトでcase
がマッチするとそのブロックを実行し、switch
ステートメントを抜けます(フォールスルーはfallthrough
キーワードで明示的に指定します)。
Go言語のswitch
ステートメントには、主に以下の2つの形式があります。
-
式スイッチ (Expression Switch):
switch
キーワードの後に評価される式を置き、その式の値とcase
ラベルの値を比較します。switch x { case 1: // xが1の場合の処理 case 2, 3: // xが2または3の場合の処理 default: // どのcaseにもマッチしない場合の処理 }
-
型スイッチ (Type Switch): インターフェースの動的な型をチェックするために使用されます。
switch v := i.(type) { case int: // vはint型 case string: // vはstring型 default: // その他の型 }
-
タグなしスイッチ (Untagged Switch / Boolean Switch):
switch
キーワードの後に式を置かず、case
ラベルにブール式を直接記述します。これは一連のif-else if-else
文の糖衣構文(シンタックスシュガー)として機能します。switch { case x > 10: // xが10より大きい場合の処理 case x < 0: // xが0より小さい場合の処理 default: // その他の場合 }
このコミットで問題となっているのは、この「タグなしスイッチ」の
case
ラベルが一つもない場合です。
コンパイラの役割と言語仕様
コンパイラは、人間が書いたソースコードをコンピュータが実行できる機械語に変換するプログラムです。この変換プロセスにおいて、コンパイラはソースコードがその言語の「言語仕様(Language Specification)」に準拠しているかを厳密にチェックします。言語仕様は、その言語の構文、意味、振る舞いを定義する公式な文書です。
コンパイラが言語仕様に反するコードを見つけた場合、通常はコンパイルエラーを発生させ、プログラマに修正を促します。しかし、このコミットのケースのように、言語仕様上は有効であるはずの構文が、コンパイラの実装上の不備によってエラーとなることがあります。これはコンパイラのバグと見なされます。
「空のswitch
ステートメント」が構文的に許容されるべきであるという設計者の意図は、コンパイラがその構文を正しく解析し、エラーなく処理できるべきであることを意味します。
技術的詳細
このバグの技術的な根本原因は、Goコンパイラの構文解析器(パーサー)または意味解析器(セマンティックアナライザー)が、switch
ステートメントのcase
節の存在を必須と誤って解釈していたことにあります。
一般的なコンパイラの処理フローでは、以下のステップが含まれます。
- 字句解析 (Lexical Analysis): ソースコードをトークン(単語のようなもの)のストリームに変換します。
- 構文解析 (Syntax Analysis): トークンのストリームを解析し、言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。この段階で、文法的に誤ったコードは構文エラーとして検出されます。
- 意味解析 (Semantic Analysis): 構文的に正しいコードが、意味的にも正しいか(型の一致、変数の宣言、スコープなど)をチェックします。この段階で、型エラーや未定義変数の使用などが検出されます。
空のswitch
ステートメントがコンパイルエラーになっていたということは、以下のいずれかの問題が考えられます。
- 構文解析器の問題:
switch
ステートメントの文法規則が、case
節が少なくとも一つ存在することを必須として定義されていたか、あるいはそのように実装されていた。Go言語の文法定義(EBNFなど)では、SwitchStmt
の定義においてCaseClause
がオプション(0回以上出現可能)であるべきなのに、実装がそうなっていなかった。 - 意味解析器の問題: 構文解析は通るものの、意味解析の段階で「
case
節がないswitch
ステートメントは意味的に無効である」と判断されていた。しかし、空のswitch
は、実行時に何も処理しないという明確な意味を持つため、これは誤った判断です。
このコミットは、おそらくコンパイラの構文解析器または意味解析器のロジックを修正し、switch
ステートメントがcase
節を持たない場合でも、それを有効な構文として認識し、エラーを発生させないように変更したと考えられます。これにより、言語仕様とコンパイラの挙動が一致し、開発者は意図通りに空のswitch
ステートメントを使用できるようになりました。
test/bugs/bug128.go
の追加は、この修正が正しく機能することを確認するための回帰テストです。以前はコンパイルエラーになっていたコードが、修正後はエラーなくコンパイルされることを検証します。test/golden.out
の変更は、このテストケースがもはやエラーを発生させないため、期待される出力(エラーメッセージ)のリストから該当するエントリを削除するものです。
コアとなるコードの変更箇所
このコミットでは、以下の2つのファイルが変更されています。
test/bugs/bug128.go
(新規追加)test/golden.out
(変更)
test/bugs/bug128.go
// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: should compile
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func main() {
switch {
// empty switch is allowed according to syntax
// unclear why it shouldn't be allowed
}
switch tag := 0; tag {
// empty switch is allowed according to syntax
// unclear why it shouldn't be allowed
}
}
/*
uetli:~/Source/go1/test/bugs gri$ 6g bug127.go
bug127.go:5: switch statement must have case labels
bug127.go:9: switch statement must have case labels
*/
このファイルは、空のswitch
ステートメントがコンパイルできることを検証するための新しいテストケースです。
switch {}
:タグなしの空のswitch
ステートメント。switch tag := 0; tag {}
:初期化ステートメントと式を持つが、case
ラベルがない空のswitch
ステートメント。
コメントで「empty switch is allowed according to syntax // unclear why it shouldn't be allowed
」と明記されており、これが言語設計者の意図であることを示しています。
下部のコメントブロックは、この修正前のコンパイラ(6g
は当時のGoコンパイラの名前)が、これらの空のswitch
に対して「switch statement must have case labels
」というエラーを発生させていたことを示しています。
test/golden.out
test/golden.out
は、Goのテストスイートにおいて、特定のテストケースが生成するコンパイルエラーや実行時エラーの「期待される出力」を記録するファイルです。このファイルは、コンパイラやランタイムの変更によってエラーメッセージが変わったり、エラーが解消されたりした場合に更新されます。
このコミットでは、test/golden.out
から以下の行が削除され、新しい行が追加されています。
削除された行(関連部分):
-Bad float64 const: 1e23+8.388608e6 want 1.0000000000000001e+23 got 1e+23
- want exact: 100000000000000008388608
- got exact: 99999999999999991611392
これは、bug120.go
に関連するfloat64
定数のエラーメッセージの変更です。このコミットの主要な目的とは直接関係ありませんが、golden.out
は複数のテストの出力を集約しているため、他の修正に伴って変更されることがあります。
追加された行(関連部分):
+=========== bugs/bug127.go
+BUG: errchk: command succeeded unexpectedly: 6g bugs/bug127.go
+
+=========== bugs/bug128.go
+bugs/bug128.go:5: switch statement must have case labels
+bugs/bug128.go:9: switch statement must have case labels
+BUG: should compile
この部分が、bug128.go
のテストケースに関連する変更です。
=========== bugs/bug128.go
は、bug128.go
のテスト結果のセクションの開始を示します。bugs/bug128.go:5: switch statement must have case labels
bugs/bug128.go:9: switch statement must have case labels
これらは、修正前のコンパイラがbug128.go
に対して出力していたエラーメッセージです。BUG: should compile
これは、このテストケースの意図を示すコメントです。つまり、このbug128.go
は、本来コンパイルが成功すべきであるにもかかわらず、エラーが発生していたという「バグ」を指摘しています。
このgolden.out
の変更は、bug128.go
が追加された時点での「期待されるエラー出力」(つまり、バグが存在する状態での出力)を記録しているように見えます。しかし、コミットメッセージが「empty switches don't compile」というバグを修正したと述べていることから、このgolden.out
の変更は、修正後のgolden.out
からこれらのエラーメッセージが削除されるべきだった、あるいは、このコミットがbug128.go
を追加し、その時点でのエラー出力を記録し、後続のコミットでコンパイラが修正され、これらのエラーがgolden.out
から削除されるという流れであった可能性も考えられます。
コミットのDELTAを見ると「3 deleted」とあるため、golden.out
から何らかの行が削除されていることは確かです。上記のfloat64
関連の行が削除されたものと推測されます。bug128.go
の追加と、それによって発生するエラーメッセージのgolden.out
への追加は、このコミットでバグを「再現」し、その後の修正で「解消」されることを示すためのステップの一部である可能性が高いです。
コアとなるコードの解説
このコミットの核心は、Goコンパイラが空のswitch
ステートメントを正しく処理できるようにする点にあります。
test/bugs/bug128.go
は、この修正の検証のために作成されたテストファイルです。このファイルには、以下の2つの空のswitch
ステートメントが含まれています。
-
switch {}
: これは「タグなしスイッチ」と呼ばれる形式で、switch
キーワードの後に式がありません。通常、case
ラベルにブール式を記述して使用しますが、ここではcase
ラベルが一切ありません。言語仕様上、このような形式は有効であり、実行時には何も処理せずにswitch
ブロックを通過します。 -
switch tag := 0; tag {}
: これは「式スイッチ」の形式で、switch
キーワードの後に初期化ステートメント(tag := 0
)と評価される式(tag
)が続きます。しかし、ここでもcase
ラベルは一切ありません。これも言語仕様上は有効であり、tag
の値に関わらず、case
がないため何も処理せずにswitch
ブロックを通過します。
修正前のコンパイラは、これらの構文に対して「switch statement must have case labels
」というエラーを発生させていました。これは、コンパイラがswitch
ステートメントには最低1つのcase
ラベルが必須であると誤って解釈していたためです。
このコミットによって、コンパイラの内部ロジック(おそらく構文解析器または意味解析器)が更新され、switch
ステートメントがcase
ラベルを持たない場合でも、それが有効な構文として認識されるようになりました。これにより、bug128.go
のようなコードはエラーなくコンパイルされるようになり、Go言語の設計意図とコンパイラの挙動が一致しました。
test/golden.out
の変更は、この修正が適用された結果として、bug128.go
がもはやエラーを発生させないことを反映するためのものです。つまり、以前はgolden.out
に記録されていたbug128.go
に関するエラーメッセージが、修正後は不要になるため削除されるべきです。コミットのDELTAが「3 deleted」を示していることから、このコミットでgolden.out
から関連するエラーメッセージが削除されたか、あるいは他のエラーメッセージの調整が行われたと考えられます。
関連リンク
特になし。
参考にした情報源リンク
特になし。
(Go言語の初期の設計思想、コンパイラの動作原理、およびswitch
ステートメントの一般的な概念に関する知識に基づいています。)