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

[インデックス 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")
    }
    
    この例では、i0 の場合、「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 switchcase が型アサーションを伴うため、fallthrough が型の安全性を損なう可能性があるためです。

今回の変更は、通常の switch ステートメントの最後の case においても fallthrough を禁止するものです。コンパイラは、case リストを走査し、現在の case がリストの最後の要素であるかどうかを判断します。もし最後の casefallthrough が検出された場合、コンパイルエラーを発生させます。

この変更は、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 から、最後の casefallthrough を使用するテストケースが削除されています。これは、この変更によってそのテストケースがコンパイルエラーとなるためです。

さらに、新しいテストファイル test/switch4.go が追加されています。このファイルは、switch ステートメントの最後の casefallthrough を使用した場合に、コンパイラが正しくエラーを報告することを確認するためのものです。

// 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.ccasebody 関数は、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 という条件は、現在の caseswitch ステートメント内の最後の case であることを意味します。
  • この条件が true であり、かつ fallthrough が検出された場合(このコードブロックが fallthrough を処理する既存のロジックの直後に配置されていることから推測)、setlineno(last) でエラーの発生行を設定し、yyerror("cannot fallthrough final case in switch") を呼び出してコンパイルエラーメッセージを出力します。

この変更により、コンパイラは switch ステートメントの構文解析中に、最後の casefallthrough が使用されていることを検出し、適切なエラーメッセージを生成するようになります。これにより、開発者はこのような論理的に無意味なコードを記述することを防ぐことができます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/cmd/gc ディレクトリ)
  • Go言語のコンパイラに関する一般的な知識
  • Gitコミットの差分情報
  • Go言語の switch ステートメントと fallthrough に関する一般的なプログラミング情報