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

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

このコミットは、Goコンパイラ(cmd/gc)におけるインターフェース満足度チェックのエラーメッセージを改善するものです。具体的には、ポインタレシーバを持つメソッドに関連するエラーメッセージの文言を修正し、ユーザーが誤解する可能性を排除することを目的としています。

コミット

commit 2254785c3e691cc95d84cca4864d20b6815f25d7
Author: Russ Cox <rsc@golang.org>
Date:   Mon Jul 15 20:39:07 2013 -0400

    cmd/gc: tweak interface satisfaction error message
    
    "M requires pointer receiver" can be misinterpreted to
    mean that method M should have a pointer receiver but
    does not. In fact the message means "M has a pointer
    receiver" (and you don't have a pointer).
    
    Fixes #5891.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/11313043

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

https://github.com/golang/go/commit/2254785c3e691cc95d84cca4864d20b6815f25d7

元コミット内容

cmd/gc: tweak interface satisfaction error message

"M requires pointer receiver" というメッセージは、メソッド M がポインタレシーバを持つべきだが持っていない、と誤解される可能性があります。実際には、このメッセージは「メソッド M はポインタレシーバを持っている(そして、あなたが持っている型はポインタではない)」という意味です。

この修正は #5891 を解決します。

変更の背景

Go言語において、型がインターフェースを「満足する(implement)」ためには、インターフェースが要求するすべてのメソッドをその型が持っている必要があります。この際、メソッドのレシーバの型(値レシーバかポインタレシーバか)が重要な役割を果たします。

Goコンパイラは、型がインターフェースを満足しない場合にエラーメッセージを出力します。このコミット以前は、特定のシナリオ(インターフェースのメソッドがポインタレシーバを持ち、そのインターフェースを満足しようとする型が値型である場合)において、エラーメッセージが "M requires pointer receiver" と表示されていました。

このメッセージは、以下のような誤解を招く可能性がありました。

  1. メソッド M の定義が間違っているという誤解: ユーザーは、インターフェースのメソッド M がポインタレシーバを持つべきなのに持っていない、と解釈してしまうかもしれません。しかし、実際にはメソッド M は正しくポインタレシーバとして定義されています。
  2. インターフェースを満足させたい型がポインタレシーバを持つべきという誤解: ユーザーは、自分の型がインターフェースを満足するために、メソッド M をポインタレシーバで定義し直す必要があると解釈してしまうかもしれません。しかし、問題はメソッド M の定義ではなく、インターフェースを満足させようとしている「値型」が、ポインタレシーバを持つメソッドを呼び出せない点にありました。

このコミットは、この曖昧さを解消し、エラーメッセージをより正確で分かりやすいものにすることで、開発者のデバッグ体験を向上させることを目的としています。

前提知識の解説

Goのインターフェース

Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。型がインターフェースを満足するには、そのインターフェースが定義するすべてのメソッドを実装している必要があります。Goでは、型が明示的にインターフェースを実装すると宣言する必要はなく、必要なメソッドをすべて持っていれば自動的にそのインターフェースを満足します(構造的型付け)。

メソッドセット

Goの型には「メソッドセット」という概念があります。これは、その型の値が呼び出すことができるメソッドの集合です。メソッドセットは、レシーバの型によって異なります。

  • 値型 T のメソッドセット: レシーバが T のメソッドと、レシーバが *T のメソッドの両方を含みます。ただし、*T のメソッドは、T の値がアドレス可能(addressable)な場合にのみ呼び出し可能です。
  • ポインタ型 *T のメソッドセット: レシーバが T のメソッドと、レシーバが *T のメソッドの両方を含みます。

インターフェースの満足度チェックにおいては、このメソッドセットのルールが非常に重要になります。

ポインタレシーバと値レシーバ

Goのメソッドは、レシーバが値型かポインタ型かによって動作が異なります。

  • 値レシーバ (func (t T) Method()): メソッドが呼び出される際、レシーバの値のコピーが渡されます。メソッド内でレシーバのフィールドを変更しても、元の値には影響しません。
  • ポインタレシーバ (func (t *T) Method()): メソッドが呼び出される際、レシーバのポインタが渡されます。メソッド内でレシーバのフィールドを変更すると、元の値に影響します。これは、構造体の状態を変更するメソッドによく使われます。

インターフェース満足度とレシーバの型

T がインターフェース I を満足するためには、I のメソッドセットが T のメソッドセットのサブセットである必要があります。

  • インターフェースのメソッドが値レシーバを要求する場合:

    • 値型 T は、レシーバが T のメソッドを実装していればインターフェースを満足します。
    • ポインタ型 *T は、レシーバが T のメソッドを実装していればインターフェースを満足します(ポインタをデリファレンスして値としてメソッドを呼び出せるため)。
  • インターフェースのメソッドがポインタレシーバを要求する場合:

    • 値型 T は、レシーバが *T のメソッドを実装していても、通常はインターフェースを満足しません。これは、T のメソッドセットにはレシーバが *T のメソッドが含まれないためです。ただし、T の値がアドレス可能であれば、コンパイラが一時的にポインタを作成してメソッドを呼び出すことができますが、これはインターフェース満足度チェックの文脈では考慮されません。
    • ポインタ型 *T は、レシーバが *T のメソッドを実装していればインターフェースを満足します。

このコミットで修正されたエラーメッセージは、まさに「インターフェースのメソッドがポインタレシーバを要求しているにもかかわらず、インターフェースを満足しようとしているのが値型である」という状況で発生していました。

技術的詳細

Goコンパイラ(cmd/gc)は、型チェックの段階でインターフェースの満足度を検証します。このプロセスは、src/cmd/gc/subr.csrc/cmd/gc/typecheck.c のようなファイルで定義されている関数によって行われます。

具体的には、assignop 関数(subr.c 内)は、型間の代入可能性、特にインターフェースへの代入をチェックします。また、typecheck.c 内の関連するロジックは、型アサーション(x.(I))の際に同様のチェックを行います。

インターフェース満足度チェックの際、コンパイラはインターフェースが要求するメソッドと、提供される型のメソッドセットを比較します。もし、インターフェースがポインタレシーバを持つメソッド M を要求しているにもかかわらず、提供される型が値型 T であり、その T のメソッドセットに M が含まれない場合、コンパイラはエラーを報告します。

元のエラーメッセージ "M requires pointer receiver" は、この状況で出力されていました。このメッセージは、あたかもメソッド M 自体がポインタレシーバを持つべきなのに持っていないかのように聞こえますが、実際にはメソッド M は正しくポインタレシーバを持っています。問題は、インターフェースを満足させようとしている値型が、そのポインタレシーバメソッドを呼び出すことができない点にありました。

このコミットは、この誤解を招く表現を "M has pointer receiver" に変更することで、より正確な情報を提供します。これにより、ユーザーはメソッド M の定義が正しいことを理解し、インターフェースを満足させるためには、インターフェースを満足させようとしている型をポインタ型にする必要がある、あるいはメソッド M のレシーバを値レシーバに変更する必要がある、といった適切な解決策を導き出しやすくなります。

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

このコミットでは、以下の2つのファイルが変更されています。

  1. src/cmd/gc/subr.c
  2. src/cmd/gc/typecheck.c

それぞれのファイルで、インターフェース満足度チェックに関連するエラーメッセージの文字列が変更されています。

src/cmd/gc/subr.c の変更点:

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -1260,7 +1260,7 @@ assignop(Type *src, Type *dst, char **why)
 			"\\t\\thave %S%hhT\\n\\t\\twant %S%hhT", src, dst, missing->sym,
 			have->sym, have->type, missing->sym, missing->type);\
 		else if(ptr)
-			*why = smprint(":\\n\\t%T does not implement %T (%S method requires pointer receiver)",
+			*why = smprint(":\\n\\t%T does not implement %T (%S method has pointer receiver)",
 				src, dst, missing->sym);\
 		else if(have)
 			*why = smprint(":\\n\\t%T does not implement %T (missing %S method)\\n"

変更された行: - *why = smprint(":\\n\\t%T does not implement %T (%S method requires pointer receiver)", + *why = smprint(":\\n\\t%T does not implement %T (%S method has pointer receiver)",

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

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -839,7 +839,7 @@ reswitch:
 			"\\t\\thave %S%hhT\\n\\t\\twant %S%hhT", n->type, t, missing->sym,
 			have->sym, have->type, missing->sym, missing->type);\
 		else if(ptr)
-			yyerror("impossible type assertion:\\n\\t%T does not implement %T (%S method requires pointer receiver)",
+			yyerror("impossible type assertion:\\n\\t%T does not implement %T (%S method has pointer receiver)",
 				n->type, t, missing->sym);\
 		else if(have)
 			yyerror("impossible type assertion:\\n\\t%T does not implement %T (missing %S method)\\n"

変更された行: - yyerror("impossible type assertion:\\n\\t%T does not implement %T (%S method requires pointer receiver)", + yyerror("impossible type assertion:\\n\\t%T does not implement %T (%S method has pointer receiver)",

コアとなるコードの解説

これらの変更は、GoコンパイラのC言語ソースコード内の文字列リテラルを直接修正しています。

  • smprint は、Goコンパイラ内部で使用される文字列フォーマット関数です。assignop 関数内で、型がインターフェースを満足しない理由を説明するメッセージを生成するために使用されます。
  • yyerror は、コンパイラのエラー報告関数です。typecheck.c 内で、不可能な型アサーション(例えば、値型をポインタレシーバを持つインターフェースにアサートしようとする場合)のエラーメッセージを出力するために使用されます。

どちらの変更も、エラーメッセージの %S method requires pointer receiver の部分を %S method has pointer receiver に置き換えています。

  • %S: これは、不足しているメソッドの名前(missing->sym)に置き換えられるプレースホルダーです。
  • requires pointer receiver (修正前): 「ポインタレシーバを必要とする」という意味で、メソッド M の定義自体に問題があるかのような印象を与えていました。
  • has pointer receiver (修正後): 「ポインタレシーバを持っている」という意味で、メソッド M の定義は正しいが、インターフェースを満足させようとしている型がそのメソッドを呼び出せない状況であることを明確に示します。

この修正により、開発者はエラーメッセージを見たときに、問題がインターフェースのメソッド定義にあるのではなく、インターフェースを満足させようとしている型のレシーバの型にあることをすぐに理解できるようになります。これは、Goのインターフェースとメソッドセットのセマンティクスをより正確に反映したメッセージであり、デバッグの効率化に貢献します。

関連リンク

参考にした情報源リンク

  • コミットハッシュ: 2254785c3e691cc95d84cca4864d20b6815f25d7 のコミット情報
  • Go言語の公式ドキュメント(インターフェース、メソッドセット、レシーバに関する一般的な知識)
  • Goコンパイラのソースコード(src/cmd/gc/subr.c, src/cmd/gc/typecheck.c
  • Go言語のインターフェース満足度に関する一般的な解説記事