[インデックス 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
に関する一般的なプログラミング情報