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

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

このコミットは、Goコンパイラ(cmd/gc)における型チェック時のエラーメッセージの改善と、それに関連するテストケースの追加に関するものです。具体的には、変数を型として誤って使用した場合に表示されるエラーメッセージが、より分かりやすく修正されています。

コミット

commit d7a3407e3d6543f3884c181a6b1135fa0fcc882b
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Sun Oct 21 20:50:31 2012 +0200

    cmd/gc: fix confusing error when using variable as type.
    
    Fixes #3783.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6737053

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

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

元コミット内容

このコミットは、Goコンパイラのcmd/gcパッケージにおいて、変数を型として誤って使用した際に発生するエラーメッセージが分かりにくいという問題を修正します。具体的には、Fixes #3783と記載されており、GoのIssueトラッカーにおける3783番の問題を解決することを目的としています。

変更内容は以下の通りです。

  • src/cmd/gc/typecheck.cの1行の修正。
  • test/fixedbugs/issue3783.goという新しいテストファイルの追加(12行)。

変更の背景

この変更は、Go言語のIssue 3783「cmd/gc: confusing error when using variable as type」に対応するものです。このIssueでは、以下のようなコードが提示されていました。

package foo

var i int

func (*i) bar() {}

このコードは、*iを変数iのポインタ型として解釈しようとしていますが、実際にはiint型の変数であり、型ではありません。Go言語では、メソッドのレシーバは型でなければなりません。しかし、当時のコンパイラは、この誤用に対して「invalid indirect of i」(iの不正な間接参照)というエラーメッセージを出力していました。

このエラーメッセージは、ユーザーにとって非常に紛らわしいものでした。なぜなら、ユーザーは変数を型として使おうとしているにもかかわらず、コンパイラはポインタの間接参照に関するエラーを報告していたからです。本来であれば、「iは型ではありません」といった、より直接的で分かりやすいエラーメッセージが期待されます。

このコミットは、このようなユーザーの混乱を解消し、より正確で理解しやすいエラーメッセージを提供することを目的としています。

前提知識の解説

このコミットの理解には、以下のGo言語およびコンパイラの基本的な知識が必要です。

  1. Go言語の型システム:

    • Goは静的型付け言語であり、すべての変数、関数、式には型があります。
    • 型は、値がどのような種類のデータであるか、どのような操作が可能であるかを定義します。
    • ポインタ型(例: *int)は、特定の方の変数のメモリアドレスを保持する型です。
    • メソッドは、特定の型に関連付けられた関数です。メソッドを定義する際には、レシーバ(func (r Type) MethodName() {}Typeの部分)として型を指定する必要があります。変数をレシーバとして直接使用することはできません。
  2. Goコンパイラのcmd/gc:

    • cmd/gcは、Go言語の公式コンパイラの一部であり、Goのソースコードを機械語に変換する役割を担っています。
    • コンパイルプロセスには、字句解析、構文解析、型チェック、最適化、コード生成などのフェーズがあります。
    • 型チェック(Type Checking): プログラムが型規則に準拠しているかを確認するフェーズです。例えば、関数呼び出しの引数の型が期待される型と一致するか、変数の使用方法がその型に適しているかなどを検証します。このフェコミットは、この型チェックのロジックに修正を加えています。
  3. エラーメッセージの重要性:

    • コンパイラのエラーメッセージは、開発者がコードの問題を特定し、修正するために不可欠な情報源です。
    • 分かりにくい、または誤解を招くエラーメッセージは、デバッグ時間を大幅に増加させ、開発者のフラストレーションを高めます。
    • 良いエラーメッセージは、問題の根本原因を正確に指摘し、修正方法のヒントを提供します。
  4. isptrtop & Erv:

    • isptrは、Goコンパイラの内部で使われる、ある型がポインタ型であるかどうかを判定するためのフラグまたは関数であると推測されます。
    • top & Ervは、コンパイラの型チェックのコンテキストを示すビットフラグの操作です。topは現在の型チェックのコンテキスト(例えば、式が値として評価されるべきか、型として評価されるべきかなど)を表す変数であり、Ervはそのコンテキストが「右辺値(expression value)」、つまり式が値として評価されるべきであることを示すフラグであると考えられます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラの型チェックロジックにおける条件分岐の変更にあります。

変更が行われたファイルはsrc/cmd/gc/typecheck.cです。このファイルは、Goコンパイラの型チェック処理の大部分を担っています。

元のコードは以下のようになっていました。

// src/cmd/gc/typecheck.c (変更前)
if(!isptr[t->etype]) {
    yyerror("invalid indirect of %lN", n->left);
    goto error;
}

このコードスニペットは、おそらくポインタの間接参照(*exprのような操作)を型チェックする部分に存在します。t->etypeは、間接参照される式の型を表していると考えられます。isptr[t->etype]は、その型がポインタ型であるかどうかをチェックしています。もしポインタ型でなければ、「invalid indirect of %lN」というエラーを出力していました。

しかし、Issue 3783で示されたfunc (*i) bar()のようなケースでは、*iはポインタの間接参照ではなく、iという変数をポインタ型として解釈しようとする試みです。この場合、iint型の変数であり、ポインタ型ではないため、isptr[t->etype]falseとなり、結果として「invalid indirect」エラーが報告されていました。

このコミットによる変更は以下の通りです。

// src/cmd/gc/typecheck.c (変更後)
if((top & Erv) && !isptr[t->etype]) {
    yyerror("invalid indirect of %lN", n->left);
    goto error;
}

追加された条件(top & Erv)が重要です。

  • top: 現在の型チェックのコンテキストを示すフラグの集合。
  • Erv: "Expression Value"(式が値として評価されるべき)を示すフラグ。

この変更により、「invalid indirect」エラーが報告されるのは、その式が値として評価されるコンテキスト(top & Ervが真)であり、かつ、間接参照される型がポインタではない場合に限定されるようになりました。

func (*i) bar()のようなケースでは、*iはメソッドのレシーバとして使用されており、これは「型」が期待されるコンテキストです。つまり、*iは値として評価されるコンテキストではないため、(top & Erv)の条件がfalseとなり、このifブロック内のエラーメッセージは出力されなくなります。

これにより、コンパイラは、変数を型として使用しようとした別の場所で、より適切な「not a type」というエラーメッセージを出力できるようになります。この修正は、コンパイラがコードの意図をより正確に解釈し、それに応じた適切なエラーメッセージを提供するための、コンテキスト依存の型チェックの改善と言えます。

新しいテストファイルtest/fixedbugs/issue3783.goは、この修正が正しく機能することを確認するために追加されました。このテストは、問題となっていたコードスニペットを含み、期待されるエラーメッセージが「not a type」であることを// ERROR "not a type"コメントで指定しています。これにより、コンパイラが修正後に正しいエラーを出力するかどうかを自動的に検証できます。

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

src/cmd/gc/typecheck.c

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -476,7 +476,7 @@ reswitch:
 			n->left = N;
 			goto ret;
 		}
-		if(!isptr[t->etype]) {
+		if((top & Erv) && !isptr[t->etype]) {
 			yyerror("invalid indirect of %lN", n->left);
 			goto error;
 		}

test/fixedbugs/issue3783.go

--- /dev/null
+++ b/test/fixedbugs/issue3783.go
@@ -0,0 +1,12 @@
+// errorcheck
+
+// Copyright 2012 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.
+
+package foo
+
+var i int
+
+func (*i) bar() // ERROR "not a type"
+

コアとなるコードの解説

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

この変更は、if文の条件に(top & Erv)という新しい条件を追加しています。

  • 変更前: if(!isptr[t->etype])
    • これは、tが指す型がポインタ型でない場合に常に「invalid indirect」エラーを発生させていました。
  • 変更後: if((top & Erv) && !isptr[t->etype])
    • この新しい条件は、エラーを発生させる前に、現在の型チェックのコンテキストが「式が値として評価されるべき」であるかどうか(top & Erv)を確認します。
    • もし式が値として評価されるコンテキストでない場合(例えば、型が期待されるレシーバの位置など)、このif文の条件は偽となり、「invalid indirect」エラーは出力されません。これにより、コンパイラの別の部分で、より適切な「not a type」エラーが報告されるようになります。

この修正は、コンパイラがコードのセマンティクス(意味)をより正確に理解し、そのコンテキストに基づいて適切なエラーメッセージを選択するための重要な改善です。

test/fixedbugs/issue3783.goの追加

このテストファイルは、Goのテストフレームワークの一部であるerrorcheckディレクティブを使用しています。

  • // errorcheck: この行は、Goのテストツールがこのファイルをコンパイルし、指定されたエラーメッセージが出力されることを期待していることを示します。
  • func (*i) bar() // ERROR "not a type": この行がテストの核心です。
    • func (*i) bar(): 問題となっていたコードスニペットです。iは変数であり、型ではありません。
    • // ERROR "not a type": このコメントは、コンパイラがこの行で「not a type」というエラーメッセージを出力することを期待していることを示します。もしコンパイラが異なるエラーメッセージ(例えば「invalid indirect」)を出力した場合、このテストは失敗します。

このテストの追加により、この特定のバグが将来のGoコンパイラのバージョンで再発しないことが保証されます。

関連リンク

参考にした情報源リンク

  • Go Issue 3783の議論内容
  • Go言語の公式ドキュメント(型システム、メソッド、コンパイルプロセスに関する一般的な情報)
  • Goコンパイラのソースコード(src/cmd/gcディレクトリの構造と一般的な処理フロー)
  • Goのテストにおけるerrorcheckディレクティブに関する情報