[インデックス 13734] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における型アサーションのエラーメッセージを改善することを目的としています。具体的には、x.(T)
のような型アサーションが失敗した場合に、もし *T
であれば成功する可能性があった場合に、エラーメッセージ内で *T
を提案するように変更されました。これは、typecheck.c
と subr.c
間での変換エラーのフォーマットを同期させることによって実現されています。
コミット
commit 1c2021ca142677fbfbbea950dd5a35986d86e678
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Sat Sep 1 13:52:55 2012 -0400
cmd/gc: Suggest *T in error for x.(T) if it would work.
Accomplished by synchronizing the formatting of conversion errors between typecheck.c and subr.c
Fixes #3984.
R=golang-dev, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/6500064
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1c2021ca142677fbfbbea950dd5a35986d86e678
元コミット内容
cmd/gc: Suggest *T in error for x.(T) if it would work.
Accomplished by synchronizing the formatting of conversion errors between typecheck.c and subr.c
Fixes #3984.
R=golang-dev, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/6500064
変更の背景
この変更は、GoのIssue #3984「x.(T)
fails, but x.(*T)
would work, suggest *T
」を修正するために行われました。
Go言語において、インターフェース型から具象型への型アサーション(x.(T)
)は、実行時に値の実際の型が T
であるかどうかをチェックし、そうであればその型に変換します。しかし、インターフェースのメソッドセットのルール(特にポインタレシーバと値レシーバの違い)により、ユーザーが意図しないエラーに遭遇することがありました。
例えば、ある型 S
がインターフェース I
を実装しているが、その実装がポインタレシーバ (func (s *S) M()
) で行われている場合、S
型の値は I
を実装しませんが、*S
型の値は I
を実装します。このような状況で、ユーザーが x.(S)
と誤って記述した場合、コンパイラは「S
は I
を実装していない」というエラーを出すだけで、なぜ実装していないのか、あるいは *S
であれば実装できるというヒントを提供していませんでした。
このコミット以前のコンパイラのエラーメッセージは、ユーザーが問題をデバッグする上で十分な情報を提供していませんでした。特に、ポインタレシーバのメソッドを持つ型に対する型アサーションの失敗は、Go言語のインターフェースの仕組みを完全に理解していない開発者にとっては混乱を招くものでした。この改善により、コンパイラはより親切なエラーメッセージを提供し、開発者が問題を迅速に特定し修正できるようになります。
前提知識の解説
Goの型アサーション (x.(T)
)
Go言語における型アサーションは、インターフェース型の変数が保持している実際の値の型を、特定の具象型に変換するために使用されます。構文は value.(Type)
の形式を取ります。
- 成功した場合:
value
がType
型の値を保持している場合、その値とtrue
が返されます(v, ok := value.(Type)
の形式の場合)。 - 失敗した場合:
value
がType
型の値を保持していない場合、パニックが発生するか(v := value.(Type)
の形式の場合)、またはok
がfalse
になります(v, ok := value.(Type)
の形式の場合)。
型アサーションは、インターフェースが動的な型情報を持つGoの特性を活かす上で非常に重要な機能です。
Goのインターフェースとメソッドセット(値レシーバとポインタレシーバの違い)
Goのインターフェースは、メソッドのシグネチャの集合を定義します。ある型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」とみなされます。
重要なのは、メソッドが値レシーバ (func (s S) M()
) で定義されているか、ポインタレシーバ (func (s *S) M()
) で定義されているかによって、その型がインターフェースを実装するかどうかが変わる点です。
-
値レシーバ (
func (s S) M()
):- 型
S
の値も、型*S
の値も、インターフェースI
を実装します。 - これは、
*S
の値からS
の値への間接参照解除が自動的に行われるためです。
- 型
-
ポインタレシーバ (
func (s *S) M()
):- 型
*S
の値のみがインターフェースI
を実装します。 - 型
S
の値は、インターフェースI
を実装しません。 - これは、
S
の値から*S
の値へのアドレス取得が自動的に行われないためです。インターフェースは値のコピーを保持するため、メソッドがポインタを必要とする場合、そのコピーのアドレスは取得できません。
- 型
この違いが、型アサーションのエラーメッセージの改善が必要とされた主な理由です。
Goコンパイラ (cmd/gc
) の役割と typecheck.c
の位置づけ
cmd/gc
は、Go言語の公式コンパイラの一つであり、Goソースコードを機械語に変換する主要なツールです。コンパイルプロセスは複数のフェーズに分かれており、その中に「型チェック(Type Checking)」のフェーズがあります。
-
typecheck.c
: このファイルは、Goコンパイラの型チェックフェーズの主要な部分を実装しています。ソースコード内の式や宣言がGo言語の型システム規則に準拠しているかを検証し、型の一貫性を保証します。型アサーションのチェックもこのファイルで行われ、型が一致しない場合やインターフェースの実装が不適切な場合にエラーを報告する役割を担っています。 -
subr.c
: このファイルは、Goコンパイラ内で使用される様々なユーティリティ関数やサブルーチンを含んでいます。エラーメッセージのフォーマットや、型に関する補助的な処理などが含まれることがあります。このコミットでは、typecheck.c
とsubr.c
間でエラーメッセージのフォーマットを同期させることで、一貫性のあるエラー報告を実現しています。
技術的詳細
このコミットの技術的詳細は、主に src/cmd/gc/typecheck.c
内の implements
関数の呼び出し後のエラー報告ロジックの変更にあります。
implements(n->type, t, &missing, &have, &ptr)
関数は、n->type
がインターフェース t
を実装しているかどうかをチェックします。
missing
: 実装されていないメソッドのシンボルを指します。have
:missing
と同じ名前だがシグネチャが異なるメソッドのシンボルを指します(もしあれば)。ptr
:n->type
がインターフェースt
を実装しないが、*n->type
であれば実装する(つまり、ポインタレシーバが必要なメソッドがある)場合にtrue
になります。
変更前のコードでは、implements
が false
を返した場合、主に以下の2つのエラーメッセージを出力していました。
have
が存在する場合(メソッド名が一致するがシグネチャが異なる場合):impossible type assertion: %lN cannot have dynamic type %T (wrong type for %S method)
have
が存在しない場合(メソッドが完全に欠落している場合):impossible type assertion: %lN cannot have dynamic type %T (missing %S method)
この変更では、これらのエラーメッセージのフォーマットをより詳細かつ分かりやすくするために、複数の if-else if
ブロックが追加されました。特に注目すべきは、ptr
フラグの利用です。
新しいエラー報告ロジックは以下のようになります。
-
if(have && have->sym == missing->sym)
:- これは、メソッド名が一致するが、そのメソッドの型(シグネチャ)がインターフェースの要求と異なる場合を扱います。
- エラーメッセージは「
%T does not implement %T (wrong type for %S method)
」となり、have
とwant
の具体的な型情報がより詳細に表示されます。
-
else if(ptr)
:- これがこのコミットの主要な改善点です。
ptr
がtrue
の場合、つまり現在の型n->type
ではインターフェースを実装しないが、そのポインタ型*n->type
であれば実装できる場合にこのブロックに入ります。 - エラーメッセージは「
%T does not implement %T (%S method requires pointer receiver)
」となり、ポインタレシーバが必要であるという具体的なヒントが提供されます。
- これがこのコミットの主要な改善点です。
-
else if(have)
:- これは、
have
が存在するが、have->sym == missing->sym
の条件を満たさない場合(例えば、メソッド名が異なるが、implements
関数が何らかの関連性を見つけた場合など、より一般的な「間違った型」のケース)を扱います。 - エラーメッセージは「
%T does not implement %T (missing %S method)
」となり、have
とwant
の具体的な型情報が詳細に表示されます。
- これは、
-
else
:- 上記のどの条件にも当てはまらない場合、つまりメソッドが完全に欠落している最も一般的なケースを扱います。
- エラーメッセージは「
%T does not implement %T (missing %S method)
」となります。
これらの変更により、コンパイラは型アサーションの失敗原因をより正確に診断し、特にポインタレシーバの要件に関する一般的な誤解を解消するための具体的なガイダンスを提供できるようになりました。subr.c
との同期は、これらの新しいエラーメッセージのフォーマットがコンパイラ全体で一貫していることを保証するためのものです。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -735,14 +735,20 @@ reswitch:
}
if(n->type != T && n->type->etype != TINTER)
if(!implements(n->type, t, &missing, &have, &ptr)) {
-\t\t\tif(have)
-\t\t\t\tyyerror("impossible type assertion: %lN cannot have dynamic type %T"
-\t\t\t\t\t" (wrong type for %S method)\n\thave %S%hT\n\twant %S%hT",
-\t\t\t\t\tl, n->type, missing->sym, have->sym, have->type,
-\t\t\t\t\tmissing->sym, missing->type);
+\t\t\tif(have && have->sym == missing->sym)
+\t\t\t\tyyerror("impossible type assertion:\n\t%T does not implement %T (wrong type for %S method)\n"
+\t\t\t\t\t"\t\thave %S%hhT\n\t\twant %S%hhT", n->type, t, missing->sym,
+\t\t\t\t\thave->sym, have->type, missing->sym, missing->type);
+\t\t\telse if(ptr)
+\t\t\t\tyyerror("impossible type assertion:\n\t%T does not implement %T (%S method requires pointer receiver)",
+\t\t\t\t\tn->type, t, missing->sym);
+\t\t\telse if(have)
+\t\t\t\tyyerror("impossible type assertion:\n\t%T does not implement %T (missing %S method)\n"
+\t\t\t\t\t"\t\thave %S%hhT\n\t\twant %S%hhT", n->type, t, missing->sym,
+\t\t\t\t\thave->sym, have->type, missing->sym, missing->type);
\t\t\telse
-\t\t\t\tyyerror("impossible type assertion: %lN cannot have dynamic type %T"
-\t\t\t\t\t" (missing %S method)", l, n->type, missing->sym);
+\t\t\t\tyyerror("impossible type assertion:\n\t%T does not implement %T (missing %S method)",
+\t\t\t\t\tn->type, t, missing->sym);
\t\t\tgoto error;
\t\t}
\t\tgoto ret;
test/interface/explicit.go
--- a/test/interface/explicit.go
+++ b/test/interface/explicit.go
@@ -15,6 +15,10 @@ type T struct {
var t *T
+type X int
+
+func (x *X) M() {}
+
type I interface {
M()
}
@@ -66,6 +70,8 @@ func (Int) M(float64) {}
var _ = m.(Int) // ERROR "impossible type assertion"
+var _ = m.(X) // ERROR "pointer receiver"
+
var ii int
var jj Int
コアとなるコードの解説
src/cmd/gc/typecheck.c
の変更点
この変更の核心は、implements
関数がインターフェースの実装失敗を報告した際に、より具体的で役立つエラーメッセージを生成するための条件分岐の追加です。
-
変更前:
if(have)
: メソッド名が一致するがシグネチャが異なる場合に、wrong type for %S method
というメッセージを出力していました。else
: メソッドが完全に欠落している場合に、missing %S method
というメッセージを出力していました。- これらのメッセージは、ポインタレシーバの必要性など、具体的な原因を特定するのに十分ではありませんでした。
-
変更後:
if(have && have->sym == missing->sym)
: これは、インターフェースが要求するメソッドと、具象型が持つ同名のメソッドのシグネチャが異なる場合にトリガーされます。エラーメッセージは、wrong type for %S method
となり、have
とwant
の具体的な型情報(%hhT
フォーマット指定子を使用)が詳細に表示されるようになりました。これにより、ユーザーはシグネチャの不一致を正確に把握できます。else if(ptr)
: この新しい条件は、implements
関数がptr
フラグをtrue
に設定した場合にトリガーされます。これは、現在の型n->type
ではインターフェースを実装しないが、そのポインタ型*n->type
であれば実装できることを意味します。この場合、コンパイラは「%S method requires pointer receiver
」という非常に具体的なヒントを提供します。これは、Goのインターフェースとレシーバの仕組みに関する一般的な誤解を解消する上で非常に役立ちます。else if(have)
: この条件は、have
が存在するが、have->sym == missing->sym
の条件を満たさない場合(例えば、メソッド名が異なるが、implements
関数が何らかの関連性を見つけた場合など)を扱います。この場合も、missing %S method
というメッセージと共に、have
とwant
の具体的な型情報が詳細に表示されます。else
: 上記のどの条件にも当てはまらない場合、つまりメソッドが完全に欠落している最も一般的なケースを扱います。この場合も、missing %S method
というメッセージが出力されます。
これらの変更により、コンパイラは型アサーションの失敗原因をより正確に診断し、特にポインタレシーバの要件に関する一般的な誤解を解消するための具体的なガイダンスを提供できるようになりました。エラーメッセージのフォーマットも改善され、より読みやすくなっています(改行の追加など)。
test/interface/explicit.go
の変更点
このテストファイルには、新しい型 X
と、そのポインタレシーバメソッド M()
が追加されました。
type X int
func (x *X) M() {}
そして、既存のインターフェース I
(type I interface { M() }
) に対して、m.(X)
という型アサーションが追加されました。ここで m
は I
型の変数です。
var _ = m.(X) // ERROR "pointer receiver"
このテストケースは、m
が *X
型の値を保持している場合に m.(X)
が失敗し、新しいエラーメッセージ「pointer receiver
」が出力されることを検証します。これは、X
型自体は I
を実装しないが、*X
型は I
を実装するというGoのインターフェースのルールを反映しており、このコミットによって改善されたエラーメッセージが正しく機能することを確認しています。
関連リンク
- Go Issue #3984: https://github.com/golang/go/issues/3984
- Go CL (Change List) 6500064: https://golang.org/cl/6500064
参考にした情報源リンク
- Go言語の公式ドキュメント(インターフェース、型アサーションに関するセクション)
- Goコンパイラのソースコード(
src/cmd/gc/typecheck.c
および関連ファイル) - Go言語のブログ記事やチュートリアル(インターフェースとレシーバに関する解説)
- Go言語のIssueトラッカー(#3984 の議論)
- Go言語のコンパイラ設計に関する資料(もしあれば)
- Go言語のインターフェースとメソッドセットについて (Go Tour)
- Go言語の型アサーションについて (Go Tour)
- Go言語のインターフェースの内部動作 (Russ Cox のブログ記事)