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

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

このコミットは、Goコンパイラ(gc)における、多値返却関数からの戻り値を単一の変数に代入しようとした際のエラーメッセージを改善するものです。以前は不明瞭な致命的エラーが出力されていましたが、この変更により、より具体的で理解しやすい「代入数の不一致」エラーが表示されるようになりました。

コミット

commit 3067781ab93deb9f7c92d0c119548b253d17a558
Author: Russ Cox <rsc@golang.org>
Date:   Tue Apr 7 22:20:37 2009 -0700

    func f() (int, int);
    x := f();
    
    used to give
            fatal error: dowidth fn struct struct { int; int }
    
    now gives
            assignment count mismatch: 1 = 2
    
    R=ken
    OCL=27198
    CL=27201

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

https://github.com/golang/go/commit/3067781ab93deb9f7c92d0c119548b253d17a558

元コミット内容

func f() (int, int);
x := f();

used to give
        fatal error: dowidth fn struct struct { int; int }

now gives
        assignment count mismatch: 1 = 2

変更の背景

このコミットが行われた2009年4月は、Go言語がまだ一般に公開される前の非常に初期の段階でした。当時のGoコンパイラ(gc)は、現在と比較してエラーメッセージの質や詳細度が低いことがありました。

このコミットの具体的な背景は、多値返却関数(例: func f() (int, int))の戻り値を、単一の変数に代入しようとした際に発生するコンパイラエラーの改善です。

例:

func f() (int, int) {
    return 1, 2
}

func main() {
    x := f() // ここでエラーが発生する
}

このコードは、f()が2つのint値を返すのに対し、xという1つの変数にしか代入しようとしていないため、代入数の不一致エラーとなるべきです。しかし、このコミット以前のコンパイラでは、以下のような不明瞭な致命的エラーが出力されていました。

fatal error: dowidth fn struct struct { int; int }

このエラーメッセージは、開発者にとって何が問題なのかを理解するのが非常に困難でした。dowidthfn struct structといった内部的なコンパイラの情報が漏れ出ており、ユーザーフレンドリーではありませんでした。

Go言語の設計哲学の一つに「エラーは明確であるべき」というものがあります。このコミットは、その哲学に沿って、コンパイラがより具体的で分かりやすいエラーメッセージを生成するように改善することを目的としています。これにより、開発者はコードの誤りを迅速に特定し、修正できるようになります。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

  1. Go言語の多値返却: Go言語の大きな特徴の一つに、関数が複数の値を返すことができる「多値返却(Multiple Return Values)」があります。例えば、func foo() (int, string)のように定義された関数は、intstringの2つの値を返します。これらの値を受け取るには、通常、複数の変数を用意する必要があります(例: a, b := foo())。

  2. Goコンパイラ(gc: Go言語の公式コンパイラは、主にgc(Go Compiler)と呼ばれます。gcは、Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスは複数のフェーズに分かれており、それぞれが特定のタスクを実行します。

  3. コンパイラの「ウォーク(Walk)」フェーズ: gcのコンパイルプロセスには、「ウォーク(Walk)」と呼ばれるフェーズがあります。このフェーズは、抽象構文木(AST: Abstract Syntax Tree)を走査し、型チェック、最適化、そして最終的なコード生成のための準備を行います。src/cmd/gc/walk.cファイルは、このウォークフェーズにおける重要な処理、特に式の評価や代入の処理を担当しています。

  4. ASTノードとオペレーション(NodeOp: コンパイラ内部では、GoのソースコードはASTとして表現されます。ASTは、コードの構造を木構造で表したものです。この木の各要素は「ノード(Node)」と呼ばれ、それぞれが特定の操作(オペレーション、Op)を表します。例えば、関数呼び出しはOCALL、メソッド呼び出しはOCALLMETH、インターフェースメソッド呼び出しはOCALLINTERといったOpで表現されます。

  5. 型システムと型チェック: Goは静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。関数呼び出しの引数や戻り値の型、代入の左右の型の整合性などがチェックされます。このコミットは、特に多値返却関数の戻り値と代入先の変数の数の整合性チェックに関連しています。

  6. colas関数: src/cmd/gc/walk.c内のcolas関数は、Goコンパイラのウォークフェーズにおいて、主に代入(assignment)や比較(comparison)などの二項演算子を処理する際に使用される関数です。この関数は、左辺(nl)と右辺(nr)の式のリストを受け取り、それらの整合性をチェックし、必要に応じてコードを生成します。listcountはリストの要素数を数えるヘルパー関数です。

これらの知識を前提として、コミットの変更内容を読み解いていきます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラのgcにおけるsrc/cmd/gc/walk.cファイルのcolas関数の変更にあります。colas関数は、代入文の右辺と左辺の式の数を比較し、型チェックを行う役割を担っています。

変更前は、多値返却関数からの戻り値を単一の変数に代入するようなケース(例: x := f() where f() returns (int, int))において、関数呼び出しの処理がcolas関数の後半にあるswitch文の中で行われていました。この処理パスでは、代入数の不一致を早期に検出して適切なエラーメッセージを生成するロジックが不足していました。その結果、コンパイラの内部的な型情報が不整合な状態になり、fatal error: dowidth fn struct struct { int; int }のような、コンパイラ内部の構造に関する不明瞭なエラーが出力されていました。これは、コンパイラが期待する型情報(struct { int; int })と、実際の代入コンテキスト(単一の変数)との間で幅(width)の計算に問題が生じたことを示唆しています。

このコミットでは、colas関数の冒頭に新しいチェックロジックが追加されました。

	/* check calls early, to give better message for a := f() */
	if(cr == 1) {
		switch(nr->op) {
		case OCALLMETH:
		case OCALLINTER:
		case OCALL:
			// ... (既存の関数呼び出しの型チェックロジック) ...
			if(t->outtuple != cl) {
				cr = t->outtuple;
				goto badt; // 代入数の不一致を検出したらbadtへジャンプ
			}
			// ...
		}
	}

この新しいロジックは、以下の点を改善しています。

  1. 早期チェックの導入: cr == 1という条件は、代入文の右辺が単一の式である場合に真となります。この条件が満たされ、かつ右辺が関数呼び出し(OCALLMETH, OCALLINTER, OCALL)である場合に、特別な処理が実行されます。
  2. 多値返却のチェック: t->outtupleは関数の戻り値の数を表し、clは代入先の変数の数を表します。t->outtuple != clという条件で、戻り値の数と代入先の変数の数が一致しない場合に、goto badt;によってエラー処理ルーチンにジャンプします。
  3. エラーメッセージの改善: badtラベルにジャンプすることで、コンパイラは「代入数の不一致」という、より具体的でユーザーフレンドリーなエラーメッセージを生成できるようになります。以前の不明瞭な致命的エラーではなく、問題の根本原因を直接示すメッセージが出力されるようになります。
  4. コードの再配置: 以前switch文の後半にあったOCALLMETH, OCALLINTER, OCALLのケースは、この新しい早期チェックブロックに移動されました。これにより、多値返却関数の代入に関するエラーチェックが、より適切なタイミングで行われるようになりました。

この変更により、コンパイラは代入の整合性に関する問題を、より早い段階で、より正確なコンテキストで検出できるようになり、結果として開発者にとって理解しやすいエラーメッセージを提供できるようになりました。

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

変更は主にsrc/cmd/gc/walk.cファイルに集中しています。

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -3067,6 +3067,41 @@ colas(Node *nl, Node *nr)
  	n = N;
  	cr = listcount(nr);
  	cl = listcount(nl);
 +
 +	/* check calls early, to give better message for a := f() */
 +	if(cr == 1) {
 +		switch(nr->op) {
 +		case OCALLMETH:
 +		case OCALLINTER:
 +		case OCALL:
 +			walktype(nr->left, Erv);
 +			convlit(nr->left, types[TFUNC]);
 +			t = nr->left->type;
 +			if(t == T)
 +				return;	// error already printed
 +			if(t->etype == tptr)
 +				t = t->type;
 +			if(t == T || t->etype != TFUNC) {
 +				yyerror("cannot call %T", t);
 +				return;
 +			}
 +			if(t->outtuple != cl) {
 +				cr = t->outtuple;
 +				goto badt;
 +			}
 +			// finish call - first half above
 +			l = listfirst(&savel, &nl);
 +			t = structfirst(&saver, getoutarg(t));
 +			while(l != N) {
 +				a = old2new(l, t->type);
 +				n = list(n, a);
 +				l = listnext(&savel);
 +				t = structnext(&saver);
 +			}
 +			n = rev(n);
 +			return n;
 +		}
 +	}
  	if(cl != cr) {
  	 	if(cr == 1)
  	 		goto multi;
@@ -3099,29 +3134,6 @@ multi:
  	default:
  	 	goto badt;
  
 -	case OCALLMETH:
 -	case OCALLINTER:
 -	case OCALL:
 -	 	walktype(nr->left, Erv);
 -	 	convlit(nr->left, types[TFUNC]);
 -	 	t = nr->left->type;
 -	 	if(t != T && t->etype == tptr)
 -	 	 	t = t->type;
 -	 	if(t == T || t->etype != TFUNC)
 -	 	 	goto badt;
 -	 	if(t->outtuple != cl)
 -	 	 	goto badt;
 -
 -	 	l = listfirst(&savel, &nl);
 -	 	t = structfirst(&saver, getoutarg(t));
 -	 	while(l != N) {
 -	 	 	a = old2new(l, t->type);
 -	 	 	n = list(n, a);
 -	 	 	l = listnext(&savel);
 -	 	 	t = structnext(&saver);
 -	 	}
 -	 	break;
 -
  	case OINDEX:
  	 	// check if rhs is a map index.
  	 	// if so, types are valuetype,bool

また、test/golden.outファイルも更新されており、fixedbugs/bug103.goのテストケースにおける期待されるエラーメッセージが、新しい「assignment count mismatch」に変更されています。

--- a/test/golden.out
+++ b/test/golden.out
@@ -145,8 +145,6 @@ fixedbugs/bug035.go:7: variable f redeclared in this block
  
 =========== fixedbugs/bug037.go
 fixedbugs/bug037.go:6: vlong: undefined
-fixedbugs/bug037.go:6: illegal types for operand: AS
-	undefined
  
 =========== fixedbugs/bug039.go
 fixedbugs/bug039.go:6: variable x redeclared in this block
@@ -219,7 +217,11 @@ fixedbugs/bug091.go:15: illegal types for operand: AS
 M
  
 =========== fixedbugs/bug103.go
+fixedbugs/bug103.go:8: assignment count mismatch: 1 = 0
+fixedbugs/bug103.go:8: x: undefined
 fixedbugs/bug103.go:8: function requires a return type
+fixedbugs/bug103.go:8: illegal types for operand: AS
+	int
  
 =========== fixedbugs/bug113.go
 main.I is int, not int32

コアとなるコードの解説

src/cmd/gc/walk.ccolas関数は、代入文の右辺と左辺の式の数を調整し、型チェックを行うGoコンパイラの重要な部分です。

このコミットの主要な変更点は、colas関数の冒頭に挿入された新しいif(cr == 1)ブロックです。

  • cr = listcount(nr); は、代入文の右辺(nr)の式の数を取得します。
  • cl = listcount(nl); は、代入文の左辺(nl)の式の数を取得します。

新しいブロックの目的は、x := f()のように、多値返却関数f()の戻り値を単一の変数xに代入しようとするケースを早期に捕捉し、より適切なエラーメッセージを生成することです。

  1. if(cr == 1): この条件は、代入文の右辺に単一の式がある場合に真となります。つまり、x := ...のような形式の代入を対象としています。

  2. switch(nr->op): 右辺の式が関数呼び出しであるかどうかをチェックします。

    • OCALLMETH: メソッド呼び出し(例: obj.Method()
    • OCALLINTER: インターフェースメソッド呼び出し(例: iface.Method()
    • OCALL: 通常の関数呼び出し(例: f()
  3. 関数呼び出しの型チェックと戻り値の数確認: walktype(nr->left, Erv);convlit(nr->left, types[TFUNC]); は、関数呼び出しの対象が有効な関数であることを確認し、その型情報を取得します。 t = nr->left->type; で関数の型を取得し、t->outtupleでその関数の戻り値の数を取得します。

  4. 代入数の不一致チェックとエラーハンドリング: if(t->outtuple != cl) がこの変更の核心です。

    • t->outtuple: 関数が返す値の数。
    • cl: 代入先の変数の数(このブロックではcr == 1なので、通常は1)。 もし、関数の戻り値の数と代入先の変数の数が一致しない場合(例: 2つの値を返す関数を1つの変数に代入しようとした場合)、cr = t->outtuple;crを関数の戻り値の数に設定し、goto badt; を実行します。
  5. goto badt;: badtラベルは、colas関数内で代入の型や数の不一致を報告するための共通のエラー処理ルーチンです。このルーチンにジャンプすることで、コンパイラは「assignment count mismatch: [代入先の変数数] = [関数の戻り値数]」という形式の、より具体的で分かりやすいエラーメッセージを出力できるようになります。

この新しい早期チェックブロックが追加されたことで、以前はswitch文の後半で処理されていたOCALLMETH, OCALLINTER, OCALLのケースは削除されました。これにより、多値返却関数の代入に関するエラーチェックが、より適切なタイミングで、より詳細な情報に基づいて行われるようになり、結果としてコンパイラのエラー診断能力が向上しました。

test/golden.outの変更は、このコンパイラの挙動変更を反映したものです。fixedbugs/bug103.goというテストケースが、以前は異なるエラーを出力していたものが、このコミットによって期待通り「assignment count mismatch」エラーを出すようになったことを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語のコンパイラ設計に関する一般的な情報(GoのコンパイラはC言語で書かれていた初期の歴史があるため、C言語のコンパイラ設計に関する一般的な知識も参考になる)
  • Go言語の初期のコミット履歴と関連する議論(Goのメーリングリストや初期のIssueトラッカーなど)
  • Go言語の多値返却に関する概念説明(Goのチュートリアルや言語仕様など)
  • Goコンパイラのgcの内部構造に関する資料(もし公開されているものがあれば)
  • src/cmd/gc/walk.cのコード自体
  • test/golden.outの変更内容I have generated the detailed explanation in Markdown format, following all the specified instructions and sections. I have used the commit message, diff, and metadata to construct the content, and explained the technical details, background, and prerequisite knowledge. I have also included the GitHub URL and placeholders for related and referenced links.

I will now output the generated explanation to standard output.

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

このコミットは、Goコンパイラ(`gc`)における、多値返却関数からの戻り値を単一の変数に代入しようとした際のエラーメッセージを改善するものです。以前は不明瞭な致命的エラーが出力されていましたが、この変更により、より具体的で理解しやすい「代入数の不一致」エラーが表示されるようになりました。

## コミット

commit 3067781ab93deb9f7c92d0c119548b253d17a558 Author: Russ Cox rsc@golang.org Date: Tue Apr 7 22:20:37 2009 -0700

func f() (int, int);
x := f();

used to give
        fatal error: dowidth fn struct struct { int; int }

now gives
        assignment count mismatch: 1 = 2

R=ken
OCL=27198
CL=27201

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

[https://github.com/golang/go/commit/3067781ab93deb9f7c92d0c119548b253d17a558](https://github.com/golang/go/commit/3067781ab93deb9f7c92d0c119548b253d17a558)

## 元コミット内容

func f() (int, int); x := f();

used to give fatal error: dowidth fn struct struct { int; int }

now gives assignment count mismatch: 1 = 2


## 変更の背景

このコミットが行われた2009年4月は、Go言語がまだ一般に公開される前の非常に初期の段階でした。当時のGoコンパイラ(`gc`)は、現在と比較してエラーメッセージの質や詳細度が低いことがありました。

このコミットの具体的な背景は、多値返却関数(例: `func f() (int, int)`)の戻り値を、単一の変数に代入しようとした際に発生するコンパイラエラーの改善です。

例:
```go
func f() (int, int) {
    return 1, 2
}

func main() {
    x := f() // ここでエラーが発生する
}

このコードは、f()が2つのint値を返すのに対し、xという1つの変数にしか代入しようとしていないため、代入数の不一致エラーとなるべきです。しかし、このコミット以前のコンパイラでは、以下のような不明瞭な致命的エラーが出力されていました。

fatal error: dowidth fn struct struct { int; int }

このエラーメッセージは、開発者にとって何が問題なのかを理解するのが非常に困難でした。dowidthfn struct structといった内部的なコンパイラの情報が漏れ出ており、ユーザーフレンドリーではありませんでした。

Go言語の設計哲学の一つに「エラーは明確であるべき」というものがあります。このコミットは、その哲学に沿って、コンパイラがより具体的で分かりやすいエラーメッセージを生成するように改善することを目的としています。これにより、開発者はコードの誤りを迅速に特定し、修正できるようになります。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

  1. Go言語の多値返却: Go言語の大きな特徴の一つに、関数が複数の値を返すことができる「多値返却(Multiple Return Values)」があります。例えば、func foo() (int, string)のように定義された関数は、intstringの2つの値を返します。これらの値を受け取るには、通常、複数の変数を用意する必要があります(例: a, b := foo())。

  2. Goコンパイラ(gc: Go言語の公式コンパイラは、主にgc(Go Compiler)と呼ばれます。gcは、Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスは複数のフェーズに分かれており、それぞれが特定のタスクを実行します。

  3. コンパイラの「ウォーク(Walk)」フェーズ: gcのコンパイルプロセスには、「ウォーク(Walk)」と呼ばれるフェーズがあります。このフェーズは、抽象構文木(AST: Abstract Syntax Tree)を走査し、型チェック、最適化、そして最終的なコード生成のための準備を行います。src/cmd/gc/walk.cファイルは、このウォークフェーズにおける重要な処理、特に式の評価や代入の処理を担当しています。

  4. ASTノードとオペレーション(NodeOp: コンパイラ内部では、GoのソースコードはASTとして表現されます。ASTは、コードの構造を木構造で表したものです。この木の各要素は「ノード(Node)」と呼ばれ、それぞれが特定の操作(オペレーション、Op)を表します。例えば、関数呼び出しはOCALL、メソッド呼び出しはOCALLMETH、インターフェースメソッド呼び出しはOCALLINTERといったOpで表現されます。

  5. 型システムと型チェック: Goは静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。関数呼び出しの引数や戻り値の型、代入の左右の型の整合性などがチェックされます。このコミットは、特に多値返却関数の戻り値と代入先の変数の数の整合性チェックに関連しています。

  6. colas関数: src/cmd/gc/walk.c内のcolas関数は、Goコンパイラのウォークフェーズにおいて、主に代入(assignment)や比較(comparison)などの二項演算子を処理する際に使用される関数です。この関数は、左辺(nl)と右辺(nr)の式のリストを受け取り、それらの整合性をチェックし、必要に応じてコードを生成します。listcountはリストの要素数を数えるヘルパー関数です。

これらの知識を前提として、コミットの変更内容を読み解いていきます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラのgcにおけるsrc/cmd/gc/walk.cファイルのcolas関数の変更にあります。colas関数は、代入文の右辺と左辺の式の数を比較し、型チェックを行う役割を担っています。

変更前は、多値返却関数からの戻り値を単一の変数に代入するようなケース(例: x := f() where f() returns (int, int))において、関数呼び出しの処理がcolas関数の後半にあるswitch文の中で行われていました。この処理パスでは、代入数の不一致を早期に検出して適切なエラーメッセージを生成するロジックが不足していました。その結果、コンパイラの内部的な型情報が不整合な状態になり、fatal error: dowidth fn struct struct { int; int }のような、コンパイラ内部の構造に関する不明瞭なエラーが出力されていました。これは、コンパイラが期待する型情報(struct { int; int })と、実際の代入コンテキスト(単一の変数)との間で幅(width)の計算に問題が生じたことを示唆しています。

このコミットでは、colas関数の冒頭に新しいチェックロジックが追加されました。

	/* check calls early, to give better message for a := f() */
	if(cr == 1) {
		switch(nr->op) {
		case OCALLMETH:
		case OCALLINTER:
		case OCALL:
			// ... (既存の関数呼び出しの型チェックロジック) ...
			if(t->outtuple != cl) {
				cr = t->outtuple;
				goto badt; // 代入数の不一致を検出したらbadtへジャンプ
			}
			// ...
		}
	}

この新しいロジックは、以下の点を改善しています。

  1. 早期チェックの導入: cr == 1という条件は、代入文の右辺が単一の式である場合に真となります。この条件が満たされ、かつ右辺が関数呼び出し(OCALLMETH, OCALLINTER, OCALL)である場合に、特別な処理が実行されます。
  2. 多値返却のチェック: t->outtupleは関数の戻り値の数を表し、clは代入先の変数の数を表します。t->outtuple != clという条件で、戻り値の数と代入先の変数の数が一致しない場合に、goto badt;によってエラー処理ルーチンにジャンプします。
  3. エラーメッセージの改善: badtラベルにジャンプすることで、コンパイラは「代入数の不一致」という、より具体的でユーザーフレンドリーなエラーメッセージを生成できるようになります。以前の不明瞭な致命的エラーではなく、問題の根本原因を直接示すメッセージが出力されるようになります。
  4. コードの再配置: 以前switch文の後半にあったOCALLMETH, OCALLINTER, OCALLのケースは、この新しい早期チェックブロックに移動されました。これにより、多値返却関数の代入に関するエラーチェックが、より適切なタイミングで行われるようになりました。

この変更により、コンパイラは代入の整合性に関する問題を、より早い段階で、より正確なコンテキストで検出できるようになり、結果として開発者にとって理解しやすいエラーメッセージを提供できるようになりました。

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

変更は主にsrc/cmd/gc/walk.cファイルに集中しています。

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -3067,6 +3067,41 @@ colas(Node *nl, Node *nr)
  	n = N;
  	cr = listcount(nr);
  	cl = listcount(nl);
 +
 +	/* check calls early, to give better message for a := f() */
 +	if(cr == 1) {
 +		switch(nr->op) {
 +		case OCALLMETH:
 +		case OCALLINTER:
 +		case OCALL:
 +			walktype(nr->left, Erv);
 +			convlit(nr->left, types[TFUNC]);
 +			t = nr->left->type;
 +			if(t == T)
 +				return;	// error already printed
 +			if(t->etype == tptr)
 +				t = t->type;
 +			if(t == T || t->etype != TFUNC) {
 +				yyerror("cannot call %T", t);
 +				return;
 +			}
 +			if(t->outtuple != cl) {
 +				cr = t->outtuple;
 +				goto badt;
 +			}
 +			// finish call - first half above
 +			l = listfirst(&savel, &nl);
 +			t = structfirst(&saver, getoutarg(t));
 +			while(l != N) {
 +				a = old2new(l, t->type);
 +				n = list(n, a);
 +				l = listnext(&savel);
 +				t = structnext(&saver);
 +			}
 +			n = rev(n);
 +			return n;
 +		}
 +	}
  	if(cl != cr) {
  	 	if(cr == 1)
  	 		goto multi;
@@ -3099,29 +3134,6 @@ multi:
  	default:
  	 	goto badt;
  
 -	case OCALLMETH:
 -	case OCALLINTER:
 -	case OCALL:
 -	 	walktype(nr->left, Erv);
 -	 	convlit(nr->left, types[TFUNC]);
 -	 	t = nr->left->type;
 -	 	if(t != T && t->etype == tptr)
 -	 	 	t = t->type;
 -	 	if(t == T || t->etype != TFUNC)
 -	 	 	goto badt;
 -	 	if(t->outtuple != cl)
 -	 	 	goto badt;
 -
 -	 	l = listfirst(&savel, &nl);
 -	 	t = structfirst(&saver, getoutarg(t));
 -	 	while(l != N) {
 -	 	 	a = old2new(l, t->type);
 -	 	 	n = list(n, a);
 -	 	 	l = listnext(&savel);
 -	 	 	t = structnext(&saver);
 -	 	}
 -	 	break;
 -
  	case OINDEX:
  	 	// check if rhs is a map index.
  	 	// if so, types are valuetype,bool

また、test/golden.outファイルも更新されており、fixedbugs/bug103.goのテストケースにおける期待されるエラーメッセージが、新しい「assignment count mismatch」に変更されています。

--- a/test/golden.out
+++ b/test/golden.out
@@ -145,8 +145,6 @@ fixedbugs/bug035.go:7: variable f redeclared in this block
  
 =========== fixedbugs/bug037.go
 fixedbugs/bug037.go:6: vlong: undefined
-fixedbugs/bug037.go:6: illegal types for operand: AS
-	undefined
  
 =========== fixedbugs/bug039.go
 fixedbugs/bug039.go:6: variable x redeclared in this block
@@ -219,7 +217,11 @@ fixedbugs/bug091.go:15: illegal types for operand: AS
 M
  
 =========== fixedbugs/bug103.go
+fixedbugs/bug103.go:8: assignment count mismatch: 1 = 0
+fixedbugs/bug103.go:8: x: undefined
 fixedbugs/bug103.go:8: function requires a return type
+fixedbugs/bug103.go:8: illegal types for operand: AS
+	int
  
 =========== fixedbugs/bug113.go
 main.I is int, not int32

コアとなるコードの解説

src/cmd/gc/walk.ccolas関数は、代入文の右辺と左辺の式の数を調整し、型チェックを行うGoコンパイラの重要な部分です。

このコミットの主要な変更点は、colas関数の冒頭に挿入された新しいif(cr == 1)ブロックです。

  • cr = listcount(nr); は、代入文の右辺(nr)の式の数を取得します。
  • cl = listcount(nl); は、代入文の左辺(nl)の式の数を取得します。

新しいブロックの目的は、x := f()のように、多値返却関数f()の戻り値を単一の変数xに代入しようとするケースを早期に捕捉し、より適切なエラーメッセージを生成することです。

  1. if(cr == 1): この条件は、代入文の右辺に単一の式がある場合に真となります。つまり、x := ...のような形式の代入を対象としています。

  2. switch(nr->op): 右辺の式が関数呼び出しであるかどうかをチェックします。

    • OCALLMETH: メソッド呼び出し(例: obj.Method()
    • OCALLINTER: インターフェースメソッド呼び出し(例: iface.Method()
    • OCALL: 通常の関数呼び出し(例: f()
  3. 関数呼び出しの型チェックと戻り値の数確認: walktype(nr->left, Erv);convlit(nr->left, types[TFUNC]); は、関数呼び出しの対象が有効な関数であることを確認し、その型情報を取得します。 t = nr->left->type; で関数の型を取得し、t->outtupleでその関数の戻り値の数を取得します。

  4. 代入数の不一致チェックとエラーハンドリング: if(t->outtuple != cl) がこの変更の核心です。

    • t->outtuple: 関数が返す値の数。
    • cl: 代入先の変数の数(このブロックではcr == 1なので、通常は1)。 もし、関数の戻り値の数と代入先の変数の数が一致しない場合(例: 2つの値を返す関数を1つの変数に代入しようとした場合)、cr = t->outtuple;crを関数の戻り値の数に設定し、goto badt; を実行します。
  5. goto badt;: badtラベルは、colas関数内で代入の型や数の不一致を報告するための共通のエラー処理ルーチンです。このルーチンにジャンプすることで、コンパイラは「assignment count mismatch: [代入先の変数数] = [関数の戻り値数]」という形式の、より具体的で分かりやすいエラーメッセージを出力できるようになります。

この新しい早期チェックブロックが追加されたことで、以前はswitch文の後半で処理されていたOCALLMETH, OCALLINTER, OCALLのケースは削除されました。これにより、多値返却関数の代入に関するエラーチェックが、より適切なタイミングで、より詳細な情報に基づいて行われるようになり、結果としてコンパイラのエラー診断能力が向上しました。

test/golden.outの変更は、このコンパイラの挙動変更を反映したものです。fixedbugs/bug103.goというテストケースが、以前は異なるエラーを出力していたものが、このコミットによって期待通り「assignment count mismatch」エラーを出すようになったことを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語のコンパイラ設計に関する一般的な情報
  • Go言語の多値返却に関する概念説明
  • src/cmd/gc/walk.cのコード自体
  • test/golden.outの変更内容