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

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

このコミットは、Go言語のコンパイラ(gc)におけるインターフェースの取り扱いと、それに関連するメソッド解決ロジックの修正およびテストの追加に焦点を当てています。具体的には、メソッドのレシーバ型(値レシーバとポインタレシーバ)の処理を改善し、インターフェースの型チェックを遅延実行する新しいメカニズムを導入しています。これにより、より堅牢で正確な型チェックが可能になっています。

変更されたファイルは以下の通りです。

  • src/cmd/gc/dcl.c: 宣言処理、特にメソッドの追加とレシーバ型の検証ロジック。
  • src/cmd/gc/go.h: コンパイラのヘッダファイルで、関数プロトタイプと型定義。
  • src/cmd/gc/go.y: Go言語の文法定義(Yaccファイル)。
  • src/cmd/gc/lex.c: 字句解析器。
  • src/cmd/gc/subr.c: コンパイラのサブルーチン群。メソッド解決、型変換、インターフェースチェックのコアロジック。
  • src/cmd/gc/walk.c: 抽象構文木(AST)の走査処理。
  • test/bugs/bug046.go: 既存のバグテストケースの更新。
  • test/interface2.go: 既存のインターフェーステストケースの更新。
  • test/interface4.go: 新規追加されたインターフェーステストケース。様々なレシーバ型でのメソッド動作を検証。
  • test/interface5.go: 新規追加されたインターフェーステストケース。インターフェースの欠落メソッドに関するエラーチェックを検証。
  • test/method3.go: 新規追加されたメソッドテストケース。スライス型に対するメソッドを検証。

コミット

commit e512481b17d240d20f2800189ca5f22ea012906b
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jan 8 18:06:06 2009 -0800

    second pass on interface fixes and tests.
    
    R=ken
    OCL=22370
    CL=22372

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

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

元コミット内容

second pass on interface fixes and tests.

R=ken
OCL=22370
CL=22372

変更の背景

このコミットは、Go言語の初期開発段階におけるインターフェースとメソッドのセマンティクスを安定させるための「2回目のパス」として位置づけられています。Go言語のインターフェースは、型が特定のメソッドセットを実装していれば自動的にそのインターフェースを満たすという「暗黙的な実装」が特徴です。しかし、この暗黙的な実装は、特に値レシーバとポインタレシーバを持つメソッドが混在する場合や、型がまだ完全に定義されていない段階でのインターフェース適合性チェックにおいて、コンパイラにとって複雑な課題を提示します。

以前のコンパイラ実装では、インターフェースの適合性チェックが不完全であったり、特定のケースで誤った振る舞いをしたりする可能性がありました。例えば、型がインターフェースのすべてのメソッドを実装しているかどうかのチェックが、コンパイルプロセスの早い段階で行われすぎると、まだ定義されていない型やメソッドに関する情報が不足しているために、正確な判断ができないことが考えられます。

このコミットの主な目的は、以下の問題を解決することです。

  1. メソッドレシーバの厳密な処理: 値レシーバとポインタレシーバを持つメソッドがインターフェースを満たす際の規則を明確にし、コンパイラがこれを正しく処理できるようにする。特に、T 型と *T 型の両方にメソッドが定義されている場合の競合や、インターフェース型自体にメソッドを定義しようとした場合の誤りを検出する。
  2. インターフェース適合性チェックの改善: 型がインターフェースを実装しているかどうかのチェックを、コンパイルプロセスのより適切な段階(すべての型情報が利用可能になった後)に遅延させることで、より正確なチェックを保証する。これにより、前方参照や相互参照を含む複雑な型定義でも、インターフェースの適合性を正しく評価できるようになります。
  3. 堅牢なテストカバレッジ: 新しいテストケースを追加することで、様々なシナリオ(異なるレシーバ型、スライス型、欠落メソッドなど)におけるインターフェースとメソッドの動作が期待通りであることを確認し、将来的な回帰を防ぐ。

これらの変更は、Go言語の型システムとインターフェースのセマンティクスをより堅牢にし、開発者が直感的で信頼性の高いコードを書けるようにするための重要なステップでした。

前提知識の解説

このコミットの技術的詳細を理解するためには、以下のGo言語およびコンパイラの基本的な概念を把握しておく必要があります。

1. Go言語のメソッドとレシーバ

Go言語では、関数を型に関連付けることで「メソッド」を定義できます。メソッドは、そのメソッドが操作する値(レシーバ)を持つ点で通常の関数と異なります。レシーバには2つの種類があります。

  • 値レシーバ (Value Receiver): func (t MyType) MyMethod() {} のように定義されます。この場合、MyMethodMyType の値のコピーに対して動作します。元の値は変更されません。
  • ポインタレシーバ (Pointer Receiver): func (t *MyType) MyMethod() {} のように定義されます。この場合、MyMethodMyType のポインタに対して動作し、元の値を変更できます。

重要なのは、Goのインターフェース適合性において、値レシーバを持つメソッドは値とポインタの両方の型で呼び出せますが、ポインタレシーバを持つメソッドはポインタ型の値でしか呼び出せないという点です。このコミットは、この微妙な違いをコンパイラがどのように処理するかを改善しています。

2. Go言語のインターフェースと暗黙的な実装

Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースの最大の特徴は、型がインターフェースを「実装する」と明示的に宣言する必要がないことです。ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型は自動的にそのインターフェースを満たします(暗黙的な実装)。

例:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type MyFile struct { /* ... */ }
func (f MyFile) Read(p []byte) (n int, err error) { /* ... */ }

// MyFile は Reader インターフェースを自動的に満たす
var r Reader = MyFile{}

このコミットは、コンパイラがこの暗黙的な適合性をどのように効率的かつ正確にチェックするかを改善しています。

3. Goコンパイラ (gc) の基本的な構造

Goの公式コンパイラ(gc)は、伝統的なコンパイラのパイプラインに従っています。

  • 字句解析 (Lexing): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。src/cmd/gc/lex.c が関連します。
  • 構文解析 (Parsing): トークンのストリームを解析し、プログラムの構造を表す抽象構文木(AST)を構築します。src/cmd/gc/go.y(Yacc/Bisonの入力ファイル)が文法を定義し、yyparse() が解析を実行します。
  • 宣言処理 (Declaration): 変数、関数、型などの宣言を処理し、シンボルテーブルに登録します。src/cmd/gc/dcl.c が関連します。
  • 型チェック (Type Checking): ASTを走査し、型の整合性を検証します。このコミットの主要な変更点です。
  • ASTウォーク (AST Walking): ASTを走査しながら、様々な最適化やコード生成のための変換を行います。src/cmd/gc/walk.c が関連します。
  • サブルーチン (Subroutines): コンパイラの様々な段階で共通して使用されるユーティリティ関数群です。src/cmd/gc/subr.c が関連し、このコミットで最も多くの変更が行われています。

4. src/cmd/gc ディレクトリの役割

src/cmd/gc は、Go言語のコンパイラ本体のソースコードが格納されているディレクトリです。このディレクトリ内のファイルは、Goプログラムを機械語に変換するプロセスの中核を担っています。

技術的詳細

このコミットの技術的詳細は、主にGoコンパイラの型システムとインターフェース処理の内部ロジックの変更に集約されます。

1. メソッド解決ロジックの変更 (ismethod から dclmethod へ)

  • ismethod の廃止と dclmethod の導入: 以前の ismethod 関数は、与えられた型 t がメソッドレシーバとして有効かどうかを判断し、その際に t->methptr フィールドを更新していました。このコミットでは、この関数が dclmethod にリネームされ、そのロジックが大幅に改善されました。 dclmethod は、レシーバ型 t が値レシーバ (T) なのかポインタレシーバ (*T) なのかを区別し、t->methptr フィールドを使って、その型が値レシーバとしてのみ、ポインタレシーバとしてのみ、あるいはその両方でメソッドを持つことを追跡します。 methptr はビットフラグとして機能し、1 は値レシーバ、2 はポインタレシーバを示し、3 は両方を示します。もし型が T*T の両方でメソッドを持つことが検出された場合、yyerror("methods on both %T and *%T", t, t) というエラーが報告されます。これは、Go言語の仕様上、同じ型に対して値レシーバとポインタレシーバの両方でメソッドを定義することはできないためです(ただし、異なるメソッド名であれば可能ですが、この文脈ではインターフェース適合性における曖昧さを避けるためのチェックです)。 また、インターフェース型 (TINTER) はメソッドレシーバとして使用できないように明示的にチェックが追加されました。

  • methtype の変更: methtype 関数は、dclmethod と同様に、副作用なしでメソッドレシーバの基本型を決定するために使用されます。この関数も、ポインタレシーバの場合にポインタを剥がして基本型を返すように修正され、インターフェース型やシンボルを持たない型を適切に除外するようになりました。

  • needaddr から methconv への変更: needaddr 関数は methconv にリネームされ、その役割が拡張されました。この関数は、メソッド呼び出しの際にレシーバ型をどのように変換すべきかを示す操作コード(OADDR または OIND)を返します。

    • OADDR (Address): メソッドがポインタレシーバ (*T) を期待しているのに、呼び出し元が値 (T) を提供した場合、値のアドレスを取る必要があることを示します。
    • OIND (Indirect): メソッドが値レシーバ (T) を期待しているのに、呼び出し元がポインタ (*T) を提供した場合、ポインタを間接参照して値を取得する必要があることを示します。 この変更により、コンパイラはメソッド呼び出し時にレシーバの型変換をより正確に処理できるようになりました。

2. 遅延インターフェースチェック機構の導入

このコミットの最も重要な変更点の一つは、インターフェースの適合性チェックをコンパイルプロセスの終盤まで遅延させるメカニズムの導入です。

  • Icheck 構造体と icheck リスト: src/cmd/gc/subr.cIcheck という新しい構造体が定義されました。これは、インターフェース変換が行われる場所(lineno)、変換先のインターフェース型 (dst)、および変換元の具象型 (src) を記録するためのものです。 これらの Icheck エントリは、グローバルなリンクリスト icheck に追加されます。

  • ifacecheck 関数の導入: ifacecheck(Type *dst, Type *src, int lineno) 関数は、インターフェース変換(代入など)が発生した際に呼び出され、その変換情報を icheck リストに記録します。この時点では、実際の適合性チェックは行われません。

  • runifacechecks 関数の導入: src/cmd/gc/lex.cmainlex 関数(字句解析と構文解析のメインループ)の最後に、runifacechecks() の呼び出しが追加されました。これは、ソースファイル全体の構文解析が完了し、すべての型とメソッドの情報が利用可能になった後で、icheck リストに記録されたすべてのインターフェース変換をまとめてチェックすることを意味します。 runifacechecksicheck リストを走査し、各エントリに対して hasiface 関数を呼び出して、src 型が dst インターフェースを実際に満たしているかどうかを検証します。もし満たしていない場合、yyerror を使ってエラーを報告します。

  • hasiface 関数の導入: hasiface(Type *t, Type *iface, Type **m) 関数は、具象型 t がインターフェース iface を実装しているかどうかをチェックします。この関数は、インターフェース iface の各メソッドについて、具象型 t が対応するメソッドを持っているかを ifacelookdot を使って検索し、メソッドのシグネチャ(型ハッシュ)が一致するかを検証します。もし欠落しているメソッドがあれば、そのメソッド情報を *m に格納して 0 を返します。

この遅延チェックの導入により、コンパイラは前方参照や複雑な型依存関係がある場合でも、インターフェースの適合性を正確に評価できるようになりました。

3. インターフェース代入のロジック変更 (isandss から ifaceas へ)

  • isandss の廃止と ifaceas の導入: src/cmd/gc/walk.c および src/cmd/gc/subr.c で使用されていた isandss 関数は ifaceas にリネームされ、インターフェース間の代入 (I2I) および具象型からインターフェースへの代入 (T2I) のロジックが整理されました。 ifaceas は、代入元 (src) と代入先 (dst) の型がインターフェースであるかどうかに基づいて、適切な変換操作(I2I または T2I)を返します。特に、T2I のケースでは、新しい ifacecheck 関数を呼び出して、後で適合性チェックが行われるように情報を記録します。

4. その他の変更

  • src/cmd/gc/dcl.caddmethod 関数では、メソッドの再宣言エラーメッセージがより詳細になり、レシーバ型がローカルで定義されていない場合のチェックが強化されました。
  • src/cmd/gc/go.yfndcl ルール(関数宣言)から ismethod の直接呼び出しが削除され、より抽象化されたメソッド解決ロジックに依存するようになりました。
  • src/cmd/gc/walk.clookdot 関数は、methconv を使用してレシーバの型変換を処理するようになり、メソッド解決の堅牢性が向上しました。
  • test/bugs/bug046.go のエラーメッセージが更新され、ポインタレシーバに関するより正確なエラーが期待されるようになりました。
  • test/interface2.go では、interface{} 型を介したインターフェース代入のテストが追加されました。
  • 新しいテストファイル test/interface4.gotest/interface5.gotest/method3.go は、これらの変更が様々なシナリオで正しく機能することを検証するために不可欠です。特に interface4.go は、値レシーバとポインタレシーバ、大きな構造体と小さな構造体、組み込み型など、多様なレシーバ型でのインターフェースメソッドの動作を網羅的にテストしています。interface5.go は、インターフェースのメソッドが不足している場合にコンパイラが正しくエラーを報告するかを検証します。method3.go は、スライス型に対するメソッドの動作をテストします。

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

このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下の部分です。

1. src/cmd/gc/subr.c における dclmethodmethconv の定義

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -1525,60 +1525,50 @@ isddd(Type *t)
  * given receiver of type t (t == r or t == *r)
  * return type to hang methods off (r).
  */
 Type*
-ismethod(Type *t)
+dclmethod(Type *t)
 {
-	int a;
-	Sym *s;
+	int ptr;
 
 	if(t == T)
 		return T;
 
-	// no interfaces
-	if(t->etype == TINTER || (t->etype == tptr && t->type->etype == TINTER))
-		return T;
-
-	a = algtype(t);
-
-	// direct receiver
-	s = t->sym;
-	if(s != S) {
-		if(t->methptr == 2)
-			goto both;
-		t->methptr |= 1;
-		goto out;
+	// strip away pointer if it's there
+	ptr = 0;
+	if(isptr[t->etype]) {
+		if(t->sym != S)
+			return T;
+		ptr = 1;
+		t = t->type;
+		if(t == T)
+			return T;
 	}
 
-	// pointer receiver
-	if(!isptr[t->etype])
+	// need a type name
+	if(t->sym == S)
 		return T;
 
-	t = t->type;
-	if(t == T)
-		return T;
-
-	s = t->sym;
-	if(s != S) {
-		if(t->methptr == 1)
-			goto both;
-		t->methptr |= 2;
-		goto out;
+	// check that all method receivers are consistent
+	if(t->methptr != 0 && t->methptr != (1<<ptr)) {
+		if(t->methptr != 3) {
+			t->methptr = 3;
+			yyerror("methods on both %T and *%T", t, t);
+		}
 	}
+	t->methptr |= 1<<ptr;
 
-	return T;
-
-both:
-	yyerror("type %T used as both direct and indirect method", t);
-	t->methptr = 3;
-
-out:
-	switch(a) {
+	// check types
+	// TODO(rsc): map, chan etc are not quite right
+	if(!issimple[t->etype])
+	switch(t->etype) {
 	default:
-		yyerror("type %T cannot be used as a method", t);
-	case ASIMP:
-	case APTR:
-	case ASTRING:
-	case ASLICE:
+		return T;
+	case TSTRUCT:
+	case TARRAY:
 		break;
 	}
 
@@ -1586,47 +1576,46 @@ out:
 }
 
 /*
- * this is ismethod() without side effects
+ * this is dclmethod() without side effects.
  */
 Type*
 methtype(Type *t)
 {
-	Sym *s;
-
 	if(t == T)
 		return T;
-	if(t->etype == TINTER || (t->etype == tptr && t->type->etype == TINTER))
-		return T;
-	s = t->sym;
-	if(s != S)
-		return t;
-	if(!isptr[t->etype])
+	if(isptr[t->etype]) {
+		if(t->sym != S)
+			return T;
+		t = t->type;
+	}
+	if(t == T || t->etype == TINTER || t->sym == S)
 		return T;
-	t = t->type;
-	if(t == T)
-		return T;
-	s = t->sym;
-	if(s != S)
-		return t;
-	return T;
+	return t;
 }
 
 /*
- * this is another ismethod()
- * returns 1 if t=T and method wants *T
+ * given type t in a method call, returns op
+ * to convert t into appropriate receiver.
+ * returns OADDR if t==x and method takes *x
+ * returns OIND if t==*x and method takes x
  */
 int
-needaddr(Type *t)
+methconv(Type *t)
 {
-	Sym *s;
+	Type *m;
 
-	if(t == T)
+	m = methtype(t);
+	if(m == T)
 		return 0;
-	if(t->etype == TINTER || (t->etype == tptr && t->type->etype == TINTER))
+	if(m->methptr&2) {
+		// want pointer
+		if(t == m)
+			return OADDR;
 		return 0;
-	s = t->sym;
-	if(s != S && t->methptr == 2)
-		return 1;
+	}
+	// want non-pointer
+	if(t != m)
+		return OIND;
 	return 0;
 }

2. src/cmd/gc/subr.c における遅延インターフェースチェック関連の新規追加

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2735,3 +2724,111 @@ genptrtramp(Sym *method, Sym *oldname, Type *oldthis, Type *oldtype, Sym *newnam
 	funcbody(fn);
 }
 
+/*
+ * delayed interface type check.
+ * remember that there is an interface conversion
+ * on the given line.  once the file is completely read
+ * and all methods are known, we can check that
+ * the conversions are valid.
+ */
+
+typedef struct Icheck Icheck;
+struct Icheck
+{
+	Icheck *next;
+	Type *dst;
+	Type *src;
+	int lineno;
+};
+Icheck *icheck;
+Icheck *ichecktail;
+
+void
+ifacecheck(Type *dst, Type *src, int lineno)
+{
+	Icheck *p;
+
+	p = mal(sizeof *p);
+	if(ichecktail)
+		ichecktail->next = p;
+	else
+		icheck = p;
+	p->dst = dst;
+	p->src = src;
+	p->lineno = lineno;
+	ichecktail = p;
+}
+
+Type*
+ifacelookdot(Sym *s, Type *t)
+{
+	int c, d;
+	Type *m;
+
+	for(d=0; d<nelem(dotlist); d++) {
+		c = adddot1(s, t, d, &m);
+		if(c > 1) {
+			yyerror("%T.%S is ambiguous", t, s);
+			return T;
+		}
+		if(c == 1)
+			return m;
+	}
+	return T;
+}
+
+int
+hasiface(Type *t, Type *iface, Type **m)
+{
+	Type *im, *tm;
+	int imhash;
+
+	t = methtype(t);
+	if(t == T)
+		return 0;
+
+	// if this is too slow,
+	// could sort these first
+	// and then do one loop.
+
+	// could also do full type compare
+	// instead of using hash, but have to
+	// avoid checking receivers, and
+	// typehash already does that for us.
+	// also, it's what the runtime will do,
+	// so we can both be wrong together.
+
+	for(im=iface->type; im; im=im->down) {
+		imhash = typehash(im, 0);
+		tm = ifacelookdot(im->sym, t);
+		if(tm == T || typehash(tm, 0) != imhash) {
+			*m = im;
+			return 0;
+		}
+	}
+	return 1;
+}
+
+void
+runifacechecks(void)
+{
+	Icheck *p;
+	int lno;
+	Type *m, *l, *r;
+
+	lno = lineno;
+	for(p=icheck; p; p=p->next) {
+		lineno = p->lineno;
+		if(isinter(p->dst)) {
+			l = p->src;
+			r = p->dst;
+		} else {
+			l = p->dst;
+			r = p->src;
+		}
+		if(!hasiface(l, r, &m))
+			yyerror("%T is not %T - missing %S%hT",
+				l, r, m->sym, m->type);
+	}
+	lineno = lno;
+}

3. src/cmd/gc/lex.c における runifacechecks の呼び出し

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -90,6 +90,7 @@ mainlex(int argc, char *argv[])
 
 	nerrors = 0;
 	yyparse();
+	runifacechecks();
 
 	linehist(nil, 0);
 	if(curio.bin != nil)

4. src/cmd/gc/walk.c における ifaceas の使用

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -604,8 +604,8 @@ loop:
 			}
 		}
 
-		// interface and structure
-		et = isandss(n->type, l);
+		// interface assignment
+		et = ifaceas(n->type, l->type);
 		if(et != Inone) {
 			indir(n, ifaceop(n->type, l, et));
 			goto ret;
@@ -2817,33 +2817,33 @@ arrayop(Node *n, int top)
 	return r;
 }
 
+/*
+ * assigning src to dst involving interfaces?
+ * return op to use.
+ */
 int
-isandss(Type *lt, Node *r)
+ifaceas(Type *dst, Type *src)
 {
-\tType *rt;\
+\tif(src == T || dst == T)
+\t\treturn Inone;
 
-\trt = r->type;\
-\tif(isinter(lt)) {\
-\t\tif(isinter(rt)) {\
-\t\t\tif(isnilinter(lt) && isnilinter(rt))\
+\tif(isinter(dst)) {
+\t\tif(isinter(src)) {
+\t\t\tif(eqtype(dst, src, 0))\
 \t\t\t\treturn Inone;\
-\t\t\tif(!eqtype(rt, lt, 0))\
-\t\t\t\treturn I2I;\
-\t\t\treturn Inone;\
+\t\t\treturn I2I;\
 \t\t}\
-\t\tif(isnilinter(lt))\
-\t\t\treturn T2I;\
-\t\tif(ismethod(rt) != T)\
+\t\tif(isnilinter(dst))\
 \t\t\treturn T2I;\
-\t\treturn Inone;\
+\t\tifacecheck(dst, src, lineno);\
+\t\treturn T2I;\
 \t}\
-\n-\tif(isinter(rt)) {\
-\t\tif(isnilinter(rt) || ismethod(lt) != T)\
+\tif(isinter(src)) {
+\t\tif(isnilinter(src))\
 \t\t\treturn I2T;\
-\t\treturn Inone;\
+\t\tifacecheck(dst, src, lineno);\
+\t\treturn I2T;\
 \t}\
-\n \treturn Inone;\
 }
 
@@ -2988,7 +2988,7 @@ convas(Node *n)
 	if(eqtype(lt, rt, 0))
 		goto out;
 
-\tet = isandss(lt, r);\
+\tet = ifaceas(lt, rt);\
 	if(et != Inone) {
 		n->right = ifaceop(lt, r, et);\
 		goto out;

コアとなるコードの解説

1. dclmethodmethconv

dclmethod 関数は、Go言語の型がメソッドレシーバとしてどのように振る舞うかをコンパイラが理解するための中心的なロジックを含んでいます。

  • レシーバ型の正規化: まず、与えられた型 t がポインタ型 (*T) であれば、そのポインタを剥がして基本型 T を取得します。ptr 変数で元の型がポインタであったかどうかを記録します。
  • 型名のチェック: メソッドは名前付きの型にのみ定義できるため、t->sym == S (シンボルがない) の場合はエラーとして T を返します。
  • methptr の整合性チェック: t->methptr は、その型が値レシーバ (1)、ポインタレシーバ (2)、またはその両方 (3) でメソッドを持つことを示すビットフラグです。このコミットでは、t->methptr != 0 && t->methptr != (1<<ptr) という条件で、同じ型に対して値レシーバとポインタレシーバの両方でメソッドが定義されている場合にエラー ("methods on both %T and *%T") を報告します。これは、Goの仕様で許容されない曖昧さを排除するためです。
  • methptr の更新: 現在のレシーバ型(値またはポインタ)に応じて t->methptr を更新します。
  • 有効なレシーバ型のチェック: TSTRUCT (構造体) や TARRAY (配列) など、メソッドレシーバとして有効な型であるかを switch 文で確認します。

methconv 関数は、メソッド呼び出し時にレシーバの型変換が必要かどうか、そしてどのような変換が必要かを判断します。

  • methtype(t) を呼び出して、レシーバの基本型 m を取得します。
  • m->methptr&2 は、メソッドがポインタレシーバ (*T) を期待しているかどうかをチェックします。
    • もし期待していて、現在の型 t が値型 (t == m) であれば、アドレスを取る (OADDR) 必要があります。
  • それ以外の場合(メソッドが値レシーバ (T) を期待している場合)で、現在の型 t がポインタ型 (t != m) であれば、間接参照 (OIND) する必要があります。

これらの関数は、Goのメソッド呼び出しにおけるレシーバの自動的な値/ポインタ変換(例: var x T; x.Method()Method*T レシーバを持つ場合、&x.Method() と自動的に解釈される)をコンパイラが正しく処理するための基盤となります。

2. 遅延インターフェースチェック機構

Icheck 構造体と icheck リスト、そして ifacecheckhasifacerunifacechecks の組み合わせは、Goコンパイラのインターフェース適合性チェックのアーキテクチャを根本的に変更しました。

  • ifacecheck(Type *dst, Type *src, int lineno): この関数は、ソースコード内でインターフェースへの代入(例: var i MyInterface = myStructInstance)が見つかるたびに呼び出されます。しかし、この時点では実際の適合性チェックは行わず、単に代入元 (src) と代入先 (dst) の型情報、および行番号 (lineno) を Icheck 構造体に格納し、グローバルなリンクリスト icheck に追加するだけです。

  • runifacechecks(void): この関数は、src/cmd/gc/lex.cmainlex 関数内で、ソースファイル全体の字句解析と構文解析が完了した後に一度だけ呼び出されます。 runifacechecksicheck リストを最初から最後まで走査します。リスト内の各 Icheck エントリについて、hasiface 関数を呼び出して、記録された src 型が dst インターフェースを実際に満たしているかどうかを検証します。 もし hasiface0 を返した場合(つまり、インターフェースのメソッドが不足している場合)、yyerror を使って詳細なエラーメッセージ(例: "%T is not %T - missing %S%hT")を報告します。

  • hasiface(Type *t, Type *iface, Type **m): この関数は、具象型 t がインターフェース iface を実装しているかどうかを判断する核心部分です。 インターフェース iface が持つ各メソッド (im) についてループを回します。 ifacelookdot(im->sym, t) を呼び出して、具象型 tim と同じ名前のメソッドを持っているかを検索します。 もしメソッドが見つからない (tm == T) か、見つかったメソッドのシグネチャ(型ハッシュ typehash(tm, 0))がインターフェースのメソッドのシグネチャと一致しない場合、そのインターフェースは満たされていないと判断し、欠落しているメソッド情報を *m に格納して 0 を返します。 すべてのメソッドが一致すれば 1 を返します。

この遅延チェックの利点は、コンパイラがソースファイル全体を完全に解析し、すべての型とメソッドの定義を把握した上でインターフェース適合性を検証できる点にあります。これにより、前方参照や相互参照を含む複雑な型定義でも、正確なインターフェース適合性チェックが可能になります。

3. ifaceas 関数

ifaceas(Type *dst, Type *src) 関数は、代入操作におけるインターフェース関連の型変換の種類を決定します。

  • インターフェースからインターフェースへの代入 (I2I): dstsrc の両方がインターフェース型である場合、eqtype(dst, src, 0) で型が完全に一致すれば変換は不要 (Inone) ですが、そうでなければ I2I (Interface to Interface) 変換が必要であると判断します。

  • 具象型からインターフェースへの代入 (T2I): dst がインターフェース型で src が具象型である場合、isnilinter(dst) (nilインターフェース) でなければ、ifacecheck(dst, src, lineno) を呼び出して遅延チェックリストに情報を追加し、T2I (Type to Interface) 変換が必要であると判断します。

  • インターフェースから具象型への代入 (I2T): src がインターフェース型で dst が具象型である場合、isnilinter(src) (nilインターフェース) でなければ、ifacecheck(dst, src, lineno) を呼び出して遅延チェックリストに情報を追加し、I2T (Interface to Type) 変換が必要であると判断します。

この関数は、src/cmd/gc/walk.c のASTウォーク中に、代入ノード (OAS) や型変換ノード (OCONV) の処理で呼び出され、適切なインターフェース操作を生成するための情報を提供します。

これらの変更は、Go言語のインターフェースが持つ強力な機能(特に暗黙的な実装)を、コンパイラが正確かつ効率的に処理するための基盤を築きました。

関連リンク

参考にした情報源リンク

  • この解説は、提供されたコミットの差分情報と、Go言語の基本的な概念に関する一般的な知識に基づいて作成されました。特定の外部ウェブサイトや文献を直接参照したものではありません。Go言語のコンパイラ内部に関する詳細な情報は、主にGoのソースコード自体から読み解かれました。