[インデックス 10263] ファイルの概要
このコミットは、Goコンパイラのガベージコレクタ(gc
)におけるチャネルのrange
操作に関するエラーメッセージを改善するものです。具体的には、受信専用ではない(つまり、送信専用の)チャネルに対してrange
を使用しようとした際に、より分かりやすいエラーメッセージが表示されるように修正されています。また、この変更に伴い、関連するテストケースが追加・修正されています。
コミット
commit ea9e93862d9b6fc0c5b53cdb204204923d653b8a
Author: Luuk van Dijk <lvd@golang.org>
Date: Sun Nov 6 22:14:15 2011 +0100
gc: Better error message for range over non-receive channel.
Fixes #2354
R=rsc
CC=golang-dev
https://golang.org/cl/5346044
---
src/cmd/gc/range.c | 4 ++++\n test/chan/perm.go | 5 ++++-\n 2 files changed, 8 insertions(+), 1 deletion(-)\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ea9e93862d9b6fc0c5b53cdb204204923d653b8a
元コミット内容
このコミットの目的は、「受信専用ではないチャネルに対するrange
操作のエラーメッセージを改善する」ことです。Go言語では、チャネルはデータの送受信に使用されますが、チャネルの型には「送受信可能(chan T
)」、「送信専用(chan<- T
)」、「受信専用(<-chan T
)」の3種類があります。for...range
ループをチャネルに対して使用する場合、そのチャネルは受信可能である必要があります。このコミット以前は、送信専用チャネルに対してrange
を使用した場合のエラーメッセージが不明瞭であったため、開発者が問題を特定しにくかったと考えられます。
コミットメッセージにはFixes #2354
と記載されており、これはGoのIssueトラッカーにおける2354番の課題を解決したことを示しています。また、R=rsc
はコードレビュー担当者(rsc, Rob Pike)を示し、CC=golang-dev
はGo開発者メーリングリストへの通知を示しています。https://golang.org/cl/5346044
は、この変更がGoのコードレビューシステム(Gerrit)における変更リスト(Change List)のURLであることを示しています。
変更の背景
Go言語のfor...range
ステートメントは、スライス、配列、文字列、マップ、そしてチャネルといった様々なデータ構造をイテレートするために使用されます。チャネルに対するrange
は、チャネルがクローズされるまで、チャネルから値を受信し続けます。
しかし、Goのチャネルには方向性があり、送信専用チャネル(chan<- T
)は値の送信のみが可能で、受信はできません。同様に、受信専用チャネル(<-chan T
)は値の受信のみが可能で、送信はできません。送受信可能なチャネル(chan T
)は、送受信の両方が可能です。
for...range
がチャネルから値を受信する操作である以上、送信専用チャネルに対してrange
を使用することは論理的に誤りです。このコミット以前は、このような誤った使用に対するコンパイラのエラーメッセージが、開発者にとって直感的でなかった可能性があります。例えば、「チャネルが受信可能ではない」という直接的なメッセージではなく、より一般的な型エラーや、range
操作の文脈から離れたメッセージが表示されていたかもしれません。
このコミットは、このような状況を改善し、開発者がコードの意図とコンパイラのエラーメッセージの間のギャップを埋める手助けをすることを目的としています。より明確なエラーメッセージを提供することで、デバッグの時間を短縮し、Goコードの品質向上に貢献します。
前提知識の解説
Go言語のチャネル
Go言語のチャネルは、ゴルーチン間で値を安全にやり取りするための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送信できます。
チャネルの宣言と使用には、以下の3つの主要な形式があります。
-
送受信可能なチャネル:
chan T
- 例:
c := make(chan int)
- このチャネルは、
c <- value
(送信)とvalue := <-c
(受信)の両方に使用できます。
- 例:
-
送信専用チャネル:
chan<- T
- 例:
cs := make(chan<- int)
- このチャネルは、
cs <- value
(送信)にのみ使用できます。value := <-cs
のような受信操作はコンパイルエラーになります。
- 例:
-
受信専用チャネル:
<-chan T
- 例:
cr := make(<-chan int)
- このチャネルは、
value := <-cr
(受信)にのみ使用できます。cr <- value
のような送信操作はコンパイルエラーになります。
- 例:
チャネルの方向性は、Goの型システムによって厳密にチェックされ、コンパイル時に不正な操作が検出されます。
for...range
ステートメントとチャネル
Goのfor...range
ステートメントは、チャネルから値を受信する際に使用できます。
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // チャネルをクローズ
}()
for v := range ch {
fmt.Println(v) // チャネルがクローズされるまで値を受信し続ける
}
このループは、ch
から値が送信されるたびにそれを受信し、ch
がクローズされるとループを終了します。
Goコンパイラのgc
パッケージ
src/cmd/gc
は、Goコンパイラの主要な部分を構成するパッケージです。gc
は「Go Compiler」の略であり、Goソースコードをコンパイルして実行可能なバイナリを生成する役割を担っています。このパッケージには、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成など、コンパイルプロセスの様々な段階が含まれています。
このコミットで変更されたsrc/cmd/gc/range.c
ファイルは、for...range
ステートメントの型チェックを担当する部分です。型チェックは、プログラムがGo言語の型規則に準拠していることを確認するコンパイル段階の重要なステップです。この段階で、例えば送信専用チャネルから値を受信しようとするような、型に関する不正な操作が検出されます。
技術的詳細
このコミットの技術的な変更は、主にGoコンパイラの型チェックロジックと、それに対応するテストケースの追加にあります。
src/cmd/gc/range.c
の変更
src/cmd/gc/range.c
ファイルは、for...range
ステートメントの型チェックを行うC言語のソースファイルです。Goコンパイラの一部はC言語で書かれています。
変更箇所はtypecheckrange
関数内にあります。この関数は、range
ステートメントの右辺の式(この場合はチャネル)の型をチェックします。
case TCHAN:
+ if(!(t->chan & Crecv)) {
+ yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);
+ goto out;
+ }
t1 = t->type;
t2 = nil;
if(count(n->list) == 2)
case TCHAN:
:range
の右辺の型がチャネル(TCHAN
)である場合の処理ブロックです。if(!(t->chan & Crecv))
: ここが追加された条件チェックです。t
はチャネルの型情報を表す構造体へのポインタです。t->chan
はチャネルの方向性を示すフラグのビットマスクです。Crecv
は「受信可能」であることを示すビットフラグです。!(t->chan & Crecv)
は、「チャネルが受信可能フラグを持っていない」ことを意味します。つまり、そのチャネルが受信専用ではない(送信専用または送受信可能だが、この文脈では送信専用が問題)場合に真となります。
yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);
:- この行は、上記の条件が真であった場合にコンパイルエラーを発生させます。
yyerror
はGoコンパイラがエラーメッセージを出力するために使用する関数です。- エラーメッセージは「
invalid operation: range %N (receive from send-only type %T)
」とフォーマットされます。%N
はrange
の右辺のノード(式)を表示します。%T
はrange
の右辺の型の種類を表示します。
- これにより、例えば
for _ = range cs
(cs
が送信専用チャネルの場合)のようなコードに対して、「invalid operation: range cs (receive from send-only type chan<- int)
」といった、より具体的で分かりやすいエラーメッセージが出力されるようになります。
goto out;
: エラーが発生したため、このtypecheckrange
関数の残りの処理をスキップして、関数の終了ラベルout
にジャンプします。
この変更により、コンパイラは送信専用チャネルに対するrange
操作をより早期かつ明確に検出し、適切なエラーメッセージを生成できるようになります。
test/chan/perm.go
の変更
test/chan/perm.go
は、Goのチャネルのパーミッション(方向性)に関するテストケースを含むファイルです。このコミットでは、送信専用チャネルに対するrange
操作が正しくエラーとして検出されることを確認するためのテストが追加されています。
@@ -48,7 +48,10 @@ func main() {
case x := <-cs: // ERROR "receive"
_ = x
}\n-\t\n+\n+\tfor _ = range cs {// ERROR "receive"\n+\t}\n+\n \tclose(c)\
\tclose(cs)\
\tclose(cr) // ERROR "receive"\
for _ = range cs {// ERROR "receive"}
: この行が追加されました。cs
はchan<- int
として定義された送信専用チャネルです。- この行のコメント
// ERROR "receive"
は、Goのテストフレームワークにおける特別な指示です。これは、この行で「receive」という文字列を含むコンパイルエラーが発生することを期待していることを示します。 - このテストケースの追加により、
src/cmd/gc/range.c
の変更が意図通りに機能し、送信専用チャネルに対するrange
操作が正しくエラーとして扱われることが保証されます。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/range.c b/src/cmd/gc/range.c
index 1909c9ec77..25d1131ec3 100644
--- a/src/cmd/gc/range.c
+++ b/src/cmd/gc/range.c
@@ -46,6 +46,10 @@ typecheckrange(Node *n)\
break;\
case TCHAN:\
+ if(!(t->chan & Crecv)) {\
+ yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);\
+ goto out;\
+ }\
t1 = t->type;\
t2 = nil;\
if(count(n->list) == 2)\
diff --git a/test/chan/perm.go b/test/chan/perm.go
index af054450ea..a43df19821 100644
--- a/test/chan/perm.go
+++ b/test/chan/perm.go
@@ -48,7 +48,10 @@ func main() {\
case x := <-cs: // ERROR "receive"\
_ = x\
}\
-\t\
+\n+\tfor _ = range cs {// ERROR "receive"\
+\t}\
+\n \tclose(c)\
\tclose(cs)\
\tclose(cr) // ERROR "receive"\
コアとなるコードの解説
src/cmd/gc/range.c
の変更点
この変更は、typecheckrange
関数内でチャネル型を処理するcase TCHAN
ブロックに追加されました。
if(!(t->chan & Crecv))
:t
はrange
の対象となるチャネルの型情報を保持するポインタです。t->chan
はチャネルの方向性を示すビットフラグの集合です。Crecv
は「受信可能」であることを示す定数ビットフラグです。&
はビットAND演算子です。t->chan & Crecv
は、チャネルの方向性フラグにCrecv
が含まれているかどうかをチェックします。!
は論理NOT演算子です。したがって、!(t->chan & Crecv)
は「チャネルが受信可能ではない」場合に真となります。これは、チャネルが送信専用(chan<- T
)である場合や、方向性が指定されていないが受信操作が許可されていない場合(Goの型システムでは送信専用チャネルは受信操作を許可しない)に該当します。
yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);
:- 上記の条件が真、つまり
range
操作が受信できないチャネルに対して行われた場合に、このエラーメッセージがコンパイラによって出力されます。 %N
はrange
の右辺の式(例:cs
)を、%T
はその式の型(例:chan<- int
)をそれぞれ埋め込みます。- これにより、開発者は「送信専用の型から受信しようとしている」という具体的なエラー原因を即座に理解できます。
- 上記の条件が真、つまり
goto out;
:- エラーが検出されたため、
typecheckrange
関数の残りの型チェック処理は不要となり、関数の終了点であるout
ラベルに直接ジャンプして処理を終了します。これにより、無駄な処理を省き、エラー処理を効率化しています。
- エラーが検出されたため、
test/chan/perm.go
の変更点
このテストファイルは、チャネルの送受信パーミッションに関する様々なシナリオを検証します。追加された行は、送信専用チャネルに対するrange
操作が正しくコンパイルエラーとなることを確認するためのものです。
for _ = range cs {// ERROR "receive"}
:cs
はfunc main()
内でcs := make(chan<- int)
として宣言されており、送信専用チャネルです。- この行は、送信専用チャネル
cs
に対してfor...range
ループを使用しようとしています。 // ERROR "receive"
というコメントは、Goのテストツールがこの行で「receive」という文字列を含むエラーメッセージが出力されることを期待していることを示します。これは、src/cmd/gc/range.c
で追加されたエラーメッセージ「invalid operation: range %N (receive from send-only type %T)
」が正しく機能していることを検証します。
これらの変更により、Goコンパイラはより堅牢になり、開発者に対してチャネルの誤用に関するより明確なフィードバックを提供するようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/ea9e93862d9b6fc0c5b53cdb204204923d653b8a
- Go Change List: https://golang.org/cl/5346044
参考にした情報源リンク
- Go言語の公式ドキュメント(チャネル、for...rangeステートメントに関する一般的な知識)
- Goコンパイラのソースコード(
src/cmd/gc
の構造とyyerror
関数の使用法に関する一般的な知識) - Goのテストフレームワークにおける
// ERROR
コメントの慣習に関する一般的な知識