[インデックス 15785] ファイルの概要
このコミットは、Go言語のコンパイラ (cmd/gc) における switch ステートメントの fallthrough キーワードの挙動に関する変更を導入しています。具体的には、switch ステートメントの最後の case における fallthrough の使用を禁止するようコンパイラに修正が加えられました。これにより、Go言語の switch ステートメントのセマンティクスがより明確になり、潜在的なバグの発生を防ぐことが意図されています。
コミット
commit f6a952599e2e9d75b50d1b1252397325111b1a87
Author: Tyler Bunnell <tylerbunnell@gmail.com>
Date: Fri Mar 15 00:35:09 2013 -0400
cmd/gc: disallow fallthrough in final case of switch
Small change to cmd/gc to catch a "fallthrough" in the final case of a switch.
R=golang-dev, rsc, mtj
CC=golang-dev
https://golang.org/cl/7841043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f6a952599e2e9d75b50d1b1252397325111b1a87
元コミット内容
このコミットは、Goコンパイラ (cmd/gc) に対する小さな変更であり、switch ステートメントの最後の case における fallthrough の使用を検出するようにします。
変更の背景
Go言語の switch ステートメントは、他の多くのC系言語とは異なり、デフォルトで case の最後に暗黙的な break が存在します。つまり、ある case が実行された後、明示的に fallthrough キーワードを使用しない限り、次の case へと処理が継続することはありません。この設計は、意図しない fallthrough によるバグを防ぐためのものです。
しかし、switch ステートメントの最後の case において fallthrough を使用することは、論理的に意味がありません。なぜなら、その case の後に続く case が存在しないため、処理が「落ちる」先がないからです。このようなコードは、開発者の意図の誤解や、将来のコード変更時に混乱を招く可能性があります。
このコミットは、このような無意味な、あるいは潜在的に誤解を招く fallthrough の使用をコンパイル時に検出することで、Goコードの品質と堅牢性を向上させることを目的としています。これにより、開発者はよりクリーンで意図が明確なコードを書くことが奨励されます。
前提知識の解説
Go言語の switch ステートメント
Go言語の switch ステートメントは、複数の条件分岐を簡潔に記述するための制御構造です。基本的な構文は以下の通りです。
switch expression {
case value1:
// value1 に一致した場合の処理
case value2:
// value2 に一致した場合の処理
default:
// どの case にも一致しなかった場合の処理
}
Goの switch の重要な特徴は以下の通りです。
- 暗黙的な
break: 各caseブロックの最後に暗黙的なbreakが存在します。これにより、一致したcaseの処理が完了すると、switchステートメント全体から抜け出します。 fallthroughキーワード: 明示的に次のcaseへと処理を継続させたい場合は、fallthroughキーワードを使用します。
この例では、switch i { case 0: fmt.Println("0") fallthrough // 次の case へ処理を継続 case 1: fmt.Println("1") case 2: fmt.Println("2") }iが0の場合、「0」と「1」の両方が出力されます。- 式なし
switch:switchの後に式を記述しない場合、switch trueと同等に扱われ、各caseはブール式として評価されます。最初にtrueと評価されたcaseが実行されます。switch { case i < 0: fmt.Println("Negative") case i == 0: fmt.Println("Zero") default: fmt.Println("Positive") }
Goコンパイラ (cmd/gc)
cmd/gc は、Go言語の公式コンパイラの一つであり、Goソースコードを機械語に変換する役割を担っています。Go言語の進化とともに、cmd/gc も継続的に改善され、新しい言語機能のサポート、最適化、そして今回のような構文チェックやエラー検出機能の強化が行われています。コンパイラは、言語仕様に準拠しているか、潜在的なエラーがないかなどを静的に解析し、開発者にフィードバックを提供します。
技術的詳細
この変更は、Goコンパイラのフロントエンド部分、特に switch ステートメントのセマンティック解析を担当する src/cmd/gc/swt.c ファイルに影響を与えます。
Goコンパイラは、ソースコードを抽象構文木 (AST) に変換した後、様々なフェーズで解析を行います。switch ステートメントの fallthrough のチェックもその一つです。
既存のコンパイラは、type switch における fallthrough を既に禁止していました。これは、type switch の case が型アサーションを伴うため、fallthrough が型の安全性を損なう可能性があるためです。
今回の変更は、通常の switch ステートメントの最後の case においても fallthrough を禁止するものです。コンパイラは、case リストを走査し、現在の case がリストの最後の要素であるかどうかを判断します。もし最後の case で fallthrough が検出された場合、コンパイルエラーを発生させます。
この変更は、Go言語の設計哲学である「明示的であること」と「エラーを早期に検出すること」に合致しています。無意味な fallthrough を禁止することで、開発者はより意図が明確なコードを書くことができ、将来的なメンテナンスコストを削減できます。
コアとなるコードの変更箇所
変更は src/cmd/gc/swt.c ファイルの casebody 関数内で行われています。
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -322,6 +322,10 @@ casebody(Node *sw, Node *typeswvar)
setlineno(last);
yyerror("cannot fallthrough in type switch");
}
+ if(l->next == nil) {
+ setlineno(last);
+ yyerror("cannot fallthrough final case in switch");
+ }
last->op = OFALL;
} else
stat = list(stat, br);
また、この変更に伴い、既存のテストファイル test/switch.go から、最後の case で fallthrough を使用するテストケースが削除されています。これは、この変更によってそのテストケースがコンパイルエラーとなるためです。
さらに、新しいテストファイル test/switch4.go が追加されています。このファイルは、switch ステートメントの最後の case で fallthrough を使用した場合に、コンパイラが正しくエラーを報告することを確認するためのものです。
// errorcheck
// Copyright 2011 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.
// Verify that erroneous switch statements are detected by the compiler.
// Does not compile.
package main
type I interface {
M()
}
func bad() {
i5 := 5
switch i5 {
case 5:
fallthrough // ERROR "cannot fallthrough final case in switch"
}
}
func good() {
var i interface{}
var s string
switch i {
case s:
}
switch s {
case i:
}
}
// ERROR "cannot fallthrough final case in switch" というコメントは、errorcheck ツールがこの行で指定されたエラーメッセージが出力されることを期待していることを示しています。
コアとなるコードの解説
src/cmd/gc/swt.c の casebody 関数は、switch ステートメントの各 case ブロックの本体を処理する役割を担っています。
追加されたコードブロックは以下の通りです。
if(l->next == nil) {
setlineno(last);
yyerror("cannot fallthrough final case in switch");
}
lは現在のcaseを表すノード(Node構造体)へのポインタであると推測されます。l->nextは、現在のcaseの次に続くcaseが存在するかどうかを示すポインタです。l->next == nilという条件は、現在のcaseがswitchステートメント内の最後のcaseであることを意味します。- この条件が
trueであり、かつfallthroughが検出された場合(このコードブロックがfallthroughを処理する既存のロジックの直後に配置されていることから推測)、setlineno(last)でエラーの発生行を設定し、yyerror("cannot fallthrough final case in switch")を呼び出してコンパイルエラーメッセージを出力します。
この変更により、コンパイラは switch ステートメントの構文解析中に、最後の case で fallthrough が使用されていることを検出し、適切なエラーメッセージを生成するようになります。これにより、開発者はこのような論理的に無意味なコードを記述することを防ぐことができます。
関連リンク
- Go言語の
switchステートメントに関する公式ドキュメント: https://go.dev/ref/spec#Switch_statements - Go言語の
fallthroughキーワードに関する議論: https://go.dev/blog/go-concurrency-patterns-pipelines (直接fallthroughの議論ではないが、Goの設計思想に関連するブログ記事)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
src/cmd/gcディレクトリ) - Go言語のコンパイラに関する一般的な知識
- Gitコミットの差分情報
- Go言語の
switchステートメントとfallthroughに関する一般的なプログラミング情報