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

[インデックス 13568] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)におけるswitchステートメントの型チェックに関するバグ修正と機能改善を目的としています。具体的には、比較可能な配列型に対するswitchステートメントが誤って拒否される問題と、比較不可能な構造体がswitchステートメントの型チェック時に適切に検出されず、生成されたコードの型チェック時に読みにくいエラーが発生する問題を解決します。これにより、Go言語の型システムにおける比較可能性のルールがswitchステートメントに正しく適用されるようになります。

コミット

commit f4f1ba2b1ebb76d4277cd775215ccd12994ffb40
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Aug 3 21:47:26 2012 +0200

    cmd/gc: accept switches on comparable arrays.
    
    The compiler is incorrectly rejecting switches on arrays of
    comparable types. It also doesn't catch incomparable structs
    when typechecking the switch, leading to unreadable errors
    during typechecking of the generated code.
    
    Fixes #3894.
    
    R=rsc
    CC=gobot, golang-dev, r, remy
    https://golang.org/cl/6442074

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f4f1ba2b1ebb76d4277cd775215ccd12994ffb40

元コミット内容

cmd/gc: accept switches on comparable arrays.

The compiler is incorrectly rejecting switches on arrays of
comparable types. It also doesn't catch incomparable structs
when typechecking the switch, leading to unreadable errors
during typechecking of the generated code.

Fixes #3894.

R=rsc
CC=gobot, golang-dev, r, remy
https://golang.org/cl/6442074

変更の背景

この変更は、Goコンパイラがswitchステートメントの対象となる型を処理する方法における2つの主要な問題に対処するために行われました。

  1. 比較可能な配列に対するswitchの誤った拒否: Go言語では、要素が比較可能な型である配列は比較可能です。しかし、コンパイラはこのような配列型をswitchステートメントの対象として誤って拒否していました。これは、言語仕様とコンパイラの挙動の間に不一致があることを意味します。
  2. 比較不可能な構造体の不適切な検出: switchステートメントの型チェック時に、比較不可能なフィールドを含む構造体が適切に検出されませんでした。その結果、コンパイラは後続のコード生成フェーズで、より低レベルで理解しにくいエラーを報告していました。これは、開発者にとってデバッグが困難な状況を引き起こしていました。

これらの問題は、Go言語のIssue #3894として報告されており、このコミットはその修正を目的としています。

前提知識の解説

Go言語のswitchステートメント

Go言語のswitchステートメントは、他の多くの言語と同様に、式の値に基づいて異なるコードブロックを実行するための制御構造です。Goのswitchは、case句に複数の値を指定できる、フォールスルーがデフォルトで無効になっている(fallthroughキーワードで明示的に有効にする必要がある)、型スイッチ(switch x.(type))をサポートするなど、いくつかの特徴があります。

Go言語における型の比較可能性

Go言語では、すべての型が比較可能であるわけではありません。比較可能な型は、==演算子や!=演算子で比較できます。

  • 数値型、文字列型、ブール型: これらは常に比較可能です。
  • ポインタ型: 同じ基底型を持つポインタは比較可能です。nilとの比較も可能です。
  • チャネル型: 同じ要素型を持つチャネルは比較可能です。nilとの比較も可能です。
  • インターフェース型: インターフェースの値は、その動的な型と動的な値が両方とも等しい場合に比較可能です。nilとの比較も可能です。
  • 構造体型: 構造体は、そのすべてのフィールドが比較可能である場合にのみ比較可能です。フィールドにスライス、マップ、関数などの比較不可能な型が含まれている場合、その構造体は比較不可能です。
  • 配列型: 配列は、その要素型が比較可能である場合にのみ比較可能です。配列の長さは比較可能性に影響しません。
  • スライス型、マップ型、関数型: これらの型は、nilとの比較を除いて、比較不可能です。

switchステートメントの式は、比較可能な型である必要があります。

Goコンパイラ(cmd/gc

cmd/gcは、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要な役割を担っています。コンパイルプロセスには、字句解析、構文解析、型チェック、最適化、コード生成などのフェーズが含まれます。このコミットで変更されるsrc/cmd/gc/swt.cは、switchステートメントの型チェックロジックを処理する部分です。

  • typecheckswitch関数: この関数は、switchステートメントの式とcase句の型がGo言語の型規則に準拠しているかを検証します。特に、switch式の型が比較可能であるか、caseの値がswitch式の型に割り当て可能であるかなどをチェックします。
  • okforeq: これは、特定の型が等価比較(==!=)に適しているかどうかを示すフラグまたは関数です。
  • isfixedarray: 配列が固定長配列([N]T)であるかどうかをチェックする関数です。Goにはスライス([]T)と配列([N]T)があり、配列は値型であり、比較可能です(要素が比較可能であれば)。
  • algtype1: 型の比較アルゴリズムを決定する関数です。ANOEQは、その型が等価比較をサポートしていないことを示します。
  • yyerror: コンパイラがエラーメッセージを出力するために使用する関数です。

技術的詳細

このコミットの核心は、src/cmd/gc/swt.c内のtypecheckswitch関数の修正にあります。この関数は、switchステートメントのテスト式(n->ntest)の型tを検証します。

変更前は、以下の条件でswitchステートメントが拒否されていました。

if(!okforeq[t->etype] || isfixedarray(t))
    yyerror("cannot switch on %lN", n->ntest);

この行は、「型tが等価比較に適していない、または固定長配列である場合、エラーを出す」という意味でした。しかし、これは「比較可能な配列に対するswitchの誤った拒否」の原因となっていました。なぜなら、Go言語では固定長配列であっても、その要素が比較可能であれば配列自体も比較可能だからです。

このコミットでは、この条件が以下のように変更されました。

if(!okforeq[t->etype])
    yyerror("cannot switch on %lN", n->ntest);
else if(t->etype == TARRAY && !isfixedarray(t))
    nilonly = "slice";
else if(t->etype == TARRAY && isfixedarray(t) && algtype1(t, nil) == ANOEQ)
    yyerror("cannot switch on %lN", n->ntest);
else if(t->etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ)
    yyerror("cannot switch on %lN (struct containing %T cannot be compared)", n->ntest, badtype);

この変更により、以下の点が改善されました。

  1. isfixedarray(t)の条件削除: 最初のif文からisfixedarray(t)の条件が削除されました。これにより、固定長配列であること自体がswitchステートメントを拒否する理由ではなくなりました。
  2. 配列の比較可能性の正確なチェック:
    • t->etype == TARRAY && !isfixedarray(t): これはスライス(固定長ではない配列)の場合を処理し、nilonly = "slice"を設定します。スライスはnilとのみ比較可能です。
    • t->etype == TARRAY && isfixedarray(t) && algtype1(t, nil) == ANOEQ: これは固定長配列の場合を処理します。algtype1(t, nil) == ANOEQは、その配列が比較可能でない場合(例えば、要素に比較不可能な型が含まれる場合)に真となります。この場合にのみエラーが報告されます。これにより、比較可能な固定長配列はswitchステートメントで許可されるようになります。
  3. 構造体の比較可能性の正確なチェック:
    • t->etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ: これは構造体の場合を処理します。algtype1ANOEQを返す場合、その構造体は比較不可能です。さらに、badtype変数を通じて、比較不可能な原因となっているフィールドの型を取得し、より詳細なエラーメッセージ(struct containing %T cannot be compared)を出力できるようになりました。これにより、「比較不可能な構造体がswitchステートメントの型チェック時に適切に検出されない」問題が解決されます。

これらの変更により、Goコンパイラはswitchステートメントにおける型の比較可能性ルールをより正確に適用し、開発者に対してより明確なエラーメッセージを提供するようになります。

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

src/cmd/gc/swt.ctypecheckswitch関数内、813行目から843行目にかけての変更がコアとなります。

--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -813,7 +813,7 @@ typecheckswitch(Node *n)
 {
 	int top, lno, ptr;
 	char *nilonly;
-	Type *t, *missing, *have;
+	Type *t, *badtype, *missing, *have;
 	NodeList *l, *ll;
 	Node *ncase, *nvar;
 	Node *def;
@@ -839,10 +839,14 @@ typecheckswitch(Node *n)
 		} else
 			t = types[TBOOL];
 		if(t) {
-			if(!okforeq[t->etype] || isfixedarray(t))
+			if(!okforeq[t->etype])
 				yyerror("cannot switch on %lN", n->ntest);
-			else if(t->etype == TARRAY)
+			else if(t->etype == TARRAY && !isfixedarray(t))
 				nilonly = "slice";
+			else if(t->etype == TARRAY && isfixedarray(t) && algtype1(t, nil) == ANOEQ)
+				yyerror("cannot switch on %lN", n->ntest);
+			else if(t->etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ)
+				yyerror("cannot switch on %lN (struct containing %T cannot be compared)", n->ntest, badtype);
 			else if(t->etype == TFUNC)
 				nilonly = "func";
 			else if(t->etype == TMAP)

また、この変更を検証するためのテストケースが追加されています。

  • test/switch.go: 比較可能な配列やチャネルに対するswitchステートメントの成功例が追加されています。
  • test/switch3.go: 比較不可能な関数を含む配列や構造体に対するswitchステートメントが正しくエラーとなる例が追加されています。

コアとなるコードの解説

src/cmd/gc/swt.cの変更

  1. Type *badtypeの追加:

    -	Type *t, *missing, *have;
    +	Type *t, *badtype, *missing, *have;
    

    badtype変数が追加されました。これは、比較不可能な構造体の場合に、どの型が比較を妨げているかを特定し、より詳細なエラーメッセージを生成するために使用されます。

  2. switch条件の修正:

    -			if(!okforeq[t->etype] || isfixedarray(t))
    +			if(!okforeq[t->etype])
    				yyerror("cannot switch on %lN", n->ntest);
    

    元のコードでは、okforeq[t->etype]が偽であるか、またはtが固定長配列である場合にエラーを出していました。この修正により、isfixedarray(t)の条件が削除され、固定長配列であること自体がエラーの原因ではなくなりました。これは、比較可能な固定長配列がswitchステートメントで許可されるようにするための重要な変更です。

  3. 配列の比較可能性チェックの追加:

    -			else if(t->etype == TARRAY)
    +			else if(t->etype == TARRAY && !isfixedarray(t))
    				nilonly = "slice";
    +			else if(t->etype == TARRAY && isfixedarray(t) && algtype1(t, nil) == ANOEQ)
    +				yyerror("cannot switch on %lN", n->ntest);
    
    • else if(t->etype == TARRAY && !isfixedarray(t)): これはスライス(固定長ではない配列)の場合を明示的に処理します。スライスはnilとのみ比較可能であるため、nilonly = "slice"が設定されます。
    • else if(t->etype == TARRAY && isfixedarray(t) && algtype1(t, nil) == ANOEQ): これは固定長配列の場合を処理します。algtype1(t, nil) == ANOEQは、配列の要素に比較不可能な型が含まれているなど、その配列自体が比較可能でない場合に真となります。この条件が真の場合にのみ、switchステートメントが拒否されます。これにより、比較可能な固定長配列は正しく受け入れられるようになります。
  4. 構造体の比較可能性チェックの追加:

    +			else if(t->etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ)
    +				yyerror("cannot switch on %lN (struct containing %T cannot be compared)", n->ntest, badtype);
    

    この新しいelse ifブロックは、switch式の型が構造体(TSTRUCT)である場合に実行されます。algtype1(t, &badtype) == ANOEQは、その構造体が比較可能でない場合に真となります。このとき、yyerror関数が呼び出され、switchステートメントが拒否されます。特筆すべきは、エラーメッセージにbadtypeが使用されている点です。これにより、比較不可能な構造体の場合に、どのフィールドの型が比較を妨げているのかを具体的に示すエラーメッセージが表示されるようになり、デバッグが大幅に容易になります。

テストファイルの追加

  • test/switch.go: このファイルには、インターフェース、配列、チャネルに対するswitchステートメントの新しいテストケースが追加されています。

    // switch on array.
    switch ar := [3]int{1, 2, 3}; ar {
    case [3]int{1,2,3}:
        assert(true, "[1 2 3]")
    case [3]int{4,5,6}:
        assert(false, "ar should be [1 2 3]")
    default:
        assert(false, "ar should be [1 2 3]")
    }
    

    このテストケースは、[3]intのような比較可能な配列がswitchステートメントで正しく機能することを確認します。

  • test/switch3.go: このファイルには、比較不可能な型(関数を含む配列や構造体)に対するswitchステートメントがコンパイルエラーとなることを確認するテストケースが追加されています。

    var ar, ar1 [4]func()
    switch ar { // ERROR "cannot switch on"
    case ar1:
    default:
    }
    
    var st, st1 struct{ f func() }
    switch st { // ERROR "cannot switch on"
    case st1:
    }
    

    これらのテストは、func()型が比較不可能であるため、それを含む配列や構造体も比較不可能となり、switchステートメントの対象として拒否されることを検証します。// ERROR "cannot switch on"コメントは、コンパイラがこの行で指定されたエラーメッセージを出力することを期待していることを示します。

これらの変更とテストケースの追加により、Goコンパイラはswitchステートメントにおける型の比較可能性ルールをより正確に適用し、開発者に対してより明確で役立つエラーメッセージを提供するようになりました。

関連リンク

参考にした情報源リンク

  • GitHub上のコミットページ: https://github.com/golang/go/commit/f4f1ba2b1ebb76d4277cd775215ccd12994ffb40
  • Go言語の仕様: https://go.dev/ref/spec (特に「Comparison operators」のセクション)
  • Go言語のswitchステートメントに関するドキュメント: https://go.dev/tour/flowcontrol/9
  • Go言語の配列とスライスに関するドキュメント: https://go.dev/tour/moretypes/6
  • Go言語のコンパイラに関する一般的な情報 (Goのソースコード構造など): https://go.dev/doc/devel/compiler (これは一般的な情報であり、特定の関数に関する詳細なドキュメントではありませんが、コンパイラの全体像を理解するのに役立ちます。)
  • Go言語のIssue #3894に関する情報 (コミットメッセージに記載されているが、Web検索では直接的な情報が見つからなかったため、一般的なIssueトラッカーのリンクを記載)
  • Go CL 6442074に関する情報 (Web検索で確認された情報)I have generated the commit explanation based on the provided instructions and the content of commit_data/13568.txt. I also used the web search results to confirm the CL and issue number, although the specific issue details were not found.

The explanation covers all the required sections in the specified order, including a detailed technical breakdown of the changes in src/cmd/gc/swt.c and the purpose of the new test files.

I will now output the generated explanation to standard output.