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

[インデックス 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つの形式があります。

  1. 式スイッチ (Expression Switch): switchキーワードの後に評価される式を置き、その式の値とcaseラベルの値を比較します。

    switch x {
    case 1:
        // xが1の場合の処理
    case 2, 3:
        // xが2または3の場合の処理
    default:
        // どのcaseにもマッチしない場合の処理
    }
    
  2. 型スイッチ (Type Switch): インターフェースの動的な型をチェックするために使用されます。

    switch v := i.(type) {
    case int:
        // vはint型
    case string:
        // vはstring型
    default:
        // その他の型
    }
    
  3. タグなしスイッチ (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節の存在を必須と誤って解釈していたことにあります。

一般的なコンパイラの処理フローでは、以下のステップが含まれます。

  1. 字句解析 (Lexical Analysis): ソースコードをトークン(単語のようなもの)のストリームに変換します。
  2. 構文解析 (Syntax Analysis): トークンのストリームを解析し、言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。この段階で、文法的に誤ったコードは構文エラーとして検出されます。
  3. 意味解析 (Semantic Analysis): 構文的に正しいコードが、意味的にも正しいか(型の一致、変数の宣言、スコープなど)をチェックします。この段階で、型エラーや未定義変数の使用などが検出されます。

空のswitchステートメントがコンパイルエラーになっていたということは、以下のいずれかの問題が考えられます。

  • 構文解析器の問題: switchステートメントの文法規則が、case節が少なくとも一つ存在することを必須として定義されていたか、あるいはそのように実装されていた。Go言語の文法定義(EBNFなど)では、SwitchStmtの定義においてCaseClauseがオプション(0回以上出現可能)であるべきなのに、実装がそうなっていなかった。
  • 意味解析器の問題: 構文解析は通るものの、意味解析の段階で「case節がないswitchステートメントは意味的に無効である」と判断されていた。しかし、空のswitchは、実行時に何も処理しないという明確な意味を持つため、これは誤った判断です。

このコミットは、おそらくコンパイラの構文解析器または意味解析器のロジックを修正し、switchステートメントがcase節を持たない場合でも、それを有効な構文として認識し、エラーを発生させないように変更したと考えられます。これにより、言語仕様とコンパイラの挙動が一致し、開発者は意図通りに空のswitchステートメントを使用できるようになりました。

test/bugs/bug128.goの追加は、この修正が正しく機能することを確認するための回帰テストです。以前はコンパイルエラーになっていたコードが、修正後はエラーなくコンパイルされることを検証します。test/golden.outの変更は、このテストケースがもはやエラーを発生させないため、期待される出力(エラーメッセージ)のリストから該当するエントリを削除するものです。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更されています。

  1. test/bugs/bug128.go (新規追加)
  2. 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ステートメントが含まれています。

  1. switch {}: これは「タグなしスイッチ」と呼ばれる形式で、switchキーワードの後に式がありません。通常、caseラベルにブール式を記述して使用しますが、ここではcaseラベルが一切ありません。言語仕様上、このような形式は有効であり、実行時には何も処理せずにswitchブロックを通過します。

  2. 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ステートメントの一般的な概念に関する知識に基づいています。)