[インデックス 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つの主要な問題に対処するために行われました。
- 比較可能な配列に対する
switch
の誤った拒否: Go言語では、要素が比較可能な型である配列は比較可能です。しかし、コンパイラはこのような配列型をswitch
ステートメントの対象として誤って拒否していました。これは、言語仕様とコンパイラの挙動の間に不一致があることを意味します。 - 比較不可能な構造体の不適切な検出:
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);
この変更により、以下の点が改善されました。
isfixedarray(t)
の条件削除: 最初のif
文からisfixedarray(t)
の条件が削除されました。これにより、固定長配列であること自体がswitch
ステートメントを拒否する理由ではなくなりました。- 配列の比較可能性の正確なチェック:
t->etype == TARRAY && !isfixedarray(t)
: これはスライス(固定長ではない配列)の場合を処理し、nilonly = "slice"
を設定します。スライスはnil
とのみ比較可能です。t->etype == TARRAY && isfixedarray(t) && algtype1(t, nil) == ANOEQ
: これは固定長配列の場合を処理します。algtype1(t, nil) == ANOEQ
は、その配列が比較可能でない場合(例えば、要素に比較不可能な型が含まれる場合)に真となります。この場合にのみエラーが報告されます。これにより、比較可能な固定長配列はswitch
ステートメントで許可されるようになります。
- 構造体の比較可能性の正確なチェック:
t->etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ
: これは構造体の場合を処理します。algtype1
がANOEQ
を返す場合、その構造体は比較不可能です。さらに、badtype
変数を通じて、比較不可能な原因となっているフィールドの型を取得し、より詳細なエラーメッセージ(struct containing %T cannot be compared
)を出力できるようになりました。これにより、「比較不可能な構造体がswitch
ステートメントの型チェック時に適切に検出されない」問題が解決されます。
これらの変更により、Goコンパイラはswitch
ステートメントにおける型の比較可能性ルールをより正確に適用し、開発者に対してより明確なエラーメッセージを提供するようになります。
コアとなるコードの変更箇所
src/cmd/gc/swt.c
のtypecheckswitch
関数内、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
の変更
-
Type *badtype
の追加:- Type *t, *missing, *have; + Type *t, *badtype, *missing, *have;
badtype
変数が追加されました。これは、比較不可能な構造体の場合に、どの型が比較を妨げているかを特定し、より詳細なエラーメッセージを生成するために使用されます。 -
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
ステートメントで許可されるようにするための重要な変更です。 -
配列の比較可能性チェックの追加:
- 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
ステートメントが拒否されます。これにより、比較可能な固定長配列は正しく受け入れられるようになります。
-
構造体の比較可能性チェックの追加:
+ 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
ステートメントにおける型の比較可能性ルールをより正確に適用し、開発者に対してより明確で役立つエラーメッセージを提供するようになりました。
関連リンク
- Go言語のIssueトラッカー: https://github.com/golang/go/issues (Issue #3894はこのコミットで修正された問題に関連しています)
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (CL 6442074はこのコミットに対応する変更リストです)
参考にした情報源リンク
- 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.