[インデックス 19190] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における引数カウントの誤りに対するエラー報告を改善するものです。具体的には、関数呼び出し時の引数の数が期待される数と異なる場合に、より少なく、かつ正確なエラーメッセージを生成するように修正が加えられました。特に可変引数(variadic functions)を持つ関数に対する型チェックのロジックが強化されています。
コミット
commit 1d2b71ce83ac66c268985a6a9ddf8f062e71821f
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Wed Apr 16 22:42:09 2014 -0400
cmd/gc: fewer errors for wrong argument count
Fixes #7675
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/85040044
---
src/cmd/gc/typecheck.c | 55 +++++++++++++++++++++++++++++++++++++++++++++
test/fixedbugs/issue7675.go | 24 ++++++++++++++++++++\
2 files changed, 79 insertions(+)
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 7eda63bad1..d7a2637224 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2167,6 +2167,31 @@ nokeys(NodeList *l)\n return 1;\n }\n \n+static int\n+hasddd(Type *t)\n+{\n+\tType *tl;\n+\n+\tfor(tl=t->type; tl; tl=tl->down) {\n+\t\tif(tl->isddd)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int\n+downcount(Type *t)\n+{\n+\tType *tl;\n+\tint n;\n+\n+\tn = 0;\n+\tfor(tl=t->type; tl; tl=tl->down) {\n+\t\tn++;\n+\t}\n+\treturn n;\n+}\n+\n /*\n * typecheck assignment: type list = expression list\n */\n@@ -2177,6 +2202,7 @@ typecheckaste(int op, Node *call, int isddd, Type *tstruct, NodeList *nl, char *\n \tNode *n;\n \tint lno;\n \tchar *why;\n+\tint n1, n2;\n \n \tlno = lineno;\n \n@@ -2186,6 +2212,15 @@ typecheckaste(int op, Node *call, int isddd, Type *tstruct, NodeList *nl, char *\n \tn = N;\n \tif(nl != nil && nl->next == nil && (n = nl->n)->type != T)\n \tif(n->type->etype == TSTRUCT && n->type->funarg) {\n+\t\tif(!hasddd(tstruct)) {\n+\t\t\tn1 = downcount(tstruct);\n+\t\t\tn2 = downcount(n->type);\n+\t\t\tif(n2 > n1)\n+\t\t\t\tgoto toomany;\n+\t\t\tif(n2 < n1)\n+\t\t\t\tgoto notenough;\n+\t\t}\n+\t\t\n \t\ttn = n->type->type;\n \t\tfor(tl=tstruct->type; tl; tl=tl->down) {\n \t\t\tif(tl->isddd) {\n@@ -2214,6 +2249,26 @@ typecheckaste(int op, Node *call, int isddd, Type *tstruct, NodeList *nl, char *\n \t\tgoto out;\n \t}\n \n+\tn1 = downcount(tstruct);\n+\tn2 = count(nl);\n+\tif(!hasddd(tstruct)) {\n+\t\tif(n2 > n1)\n+\t\t\tgoto toomany;\n+\t\t\tif(n2 < n1)\n+\t\t\t\tgoto notenough;\n+\t}\n+\telse {\n+\t\tif(!isddd) {\n+\t\t\tif(n2 < n1-1)\n+\t\t\t\tgoto notenough;\n+\t\t} else {\n+\t\t\tif(n2 > n1)\n+\t\t\t\tgoto toomany;\n+\t\t\tif(n2 < n1)\n+\t\t\t\tgoto notenough;\n+\t\t}\n+\t}\n+\n \tfor(tl=tstruct->type; tl; tl=tl->down) {\n \t\tt = tl->type;\n \t\tif(tl->isddd) {\ndiff --git a/test/fixedbugs/issue7675.go b/test/fixedbugs/issue7675.go
new file mode 100644
index 0000000000..d97ee357a2
--- /dev/null
+++ b/test/fixedbugs/issue7675.go
@@ -0,0 +1,24 @@
+// errorcheck
+\n// Copyright 2014 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.
+\n// Issue 7675: fewer errors for wrong argument count
+\n+package p
+\n+func f(string, int, float64, string)
+\n+func g(string, int, float64, ...string)
+\n+func main() {
+\tf(1, 0.5, "hello") // ERROR "not enough arguments"
+\tf("1", 2, 3.1, "4")
+\tf(1, 0.5, "hello", 4, 5) // ERROR "too many arguments"
+\tg(1, 0.5) // ERROR "not enough arguments"
+\tg("1", 2, 3.1)
+\tg(1, 0.5, []int{3, 4}...) // ERROR "not enough arguments"
+\tg("1", 2, 3.1, "4", "5")
+\tg(1, 0.5, "hello", 4, []int{5, 6}...) // ERROR "too many arguments"
+}\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1d2b71ce83ac66c268985a6a9ddf8f062e71821f
元コミット内容
このコミットは、Goコンパイラのcmd/gc
部分において、関数呼び出し時の引数カウントが誤っている場合に発生するエラーの数を減らすことを目的としています。具体的には、Fixes #7675
とあるように、Goの旧Issueトラッカーで報告された問題7675を修正するものです。この修正により、コンパイラがより正確で、かつ冗長でないエラーメッセージを生成するようになります。
変更の背景
Goコンパイラは、プログラムの型チェックを行う際に、関数呼び出しの引数の数が関数の定義と一致するかどうかを検証します。しかし、以前の実装では、引数の数が誤っている場合に、複数のエラーメッセージが出力されたり、エラーメッセージが不明瞭であったりするケースが存在したと考えられます。特に、可変引数(variadic arguments)を持つ関数に対しては、その特性上、引数の解釈が複雑になるため、より正確なエラーハンドリングが求められていました。
このコミットは、このような状況を改善し、開発者にとってより分かりやすいエラーメッセージを提供することで、デバッグの効率を向上させることを目的としています。test/fixedbugs/issue7675.go
で示されているテストケースは、まさにこの問題が修正されたことを確認するためのものです。
前提知識の解説
- Goコンパイラ (
cmd/gc
): Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要なツールチェーンです。gc
はGoコンパイラのフロントエンドであり、構文解析、型チェック、最適化、コード生成などの段階を担当します。 - 型チェック (
typecheck.c
): コンパイラの重要なフェーズの一つで、プログラムが型システムの一貫性ルールに従っていることを検証します。これにより、型に関するエラー(例: 整数型変数に文字列を代入しようとする、引数の型が異なる関数を呼び出すなど)をコンパイル時に検出します。src/cmd/gc/typecheck.c
は、この型チェックロジックが実装されているC言語のソースファイルです。 - 可変引数 (Variadic Functions): Go言語において、関数が不定数の引数を受け入れることを可能にする機能です。関数の最後のパラメータの型に
...
を付けることで定義されます(例:func foo(args ...int)
)。可変引数は関数内でスライスとして扱われます。 Node
とType
構造体: Goコンパイラの内部では、ソースコードは抽象構文木(AST)として表現され、各ノードはNode
構造体で表されます。また、型情報はType
構造体で管理されます。TSTRUCT
とfunarg
:Type
構造体において、etype
がTSTRUCT
である場合、それは構造体型を表します。funarg
フラグは、そのTSTRUCT
が関数の引数リストまたは戻り値リストを表す特殊な構造体であることを示します。isddd
:Type
構造体内のフィールドで、その型が可変引数(...
)であることを示すフラグです。
技術的詳細
このコミットの主要な変更は、src/cmd/gc/typecheck.c
内のtypecheckaste
関数に集中しています。この関数は、代入文や関数呼び出しにおける引数とパラメータの型チェックを担当します。
修正の核心は、引数の数をより厳密に、かつ可変引数の特性を考慮して検証する新しいロジックの導入です。
-
hasddd
関数の追加:- この関数は、与えられた
Type
構造体が可変引数(...
)を持つかどうかをチェックします。 - 関数のパラメータリストを表す
Type
構造体(t->type
がパラメータのリストを指す)を走査し、いずれかのパラメータがisddd
フラグを持っている場合に1
を返します。
- この関数は、与えられた
-
downcount
関数の追加:- この関数は、与えられた
Type
構造体(通常はパラメータリストを表すTSTRUCT
)に含まれる要素(パラメータ)の数をカウントします。 t->type
から始まるリンクリストをtl=tl->down
で辿り、要素の数を数えます。
- この関数は、与えられた
-
typecheckaste
関数の変更:- 単一の構造体引数に対する特殊なケースの改善:
- 関数呼び出しの引数が単一の構造体であり、それが関数の引数リストを表す場合(
n->type->etype == TSTRUCT && n->type->funarg
)、新しいチェックが導入されました。 !hasddd(tstruct)
: 呼び出される関数が可変引数を持たない場合、引数の数(n2 = downcount(n->type)
)と期待されるパラメータの数(n1 = downcount(tstruct)
)を比較します。n2 > n1
であればgoto toomany;
(引数が多すぎる)、n2 < n1
であればgoto notenough;
(引数が少なすぎる)にジャンプし、適切なエラーメッセージを出力します。これにより、以前は型ミスマッチとして扱われていたようなケースでも、より具体的な引数カウントのエラーを報告できるようになります。
- 関数呼び出しの引数が単一の構造体であり、それが関数の引数リストを表す場合(
- 一般的な引数カウントチェックの強化:
- 関数のパラメータの数(
n1 = downcount(tstruct)
)と、実際に渡された引数の数(n2 = count(nl)
)を比較します。 !hasddd(tstruct)
: 呼び出される関数が可変引数を持たない場合、n2 > n1
であればgoto toomany;
、n2 < n1
であればgoto notenough;
にジャンプします。これは、厳密な引数の一致を要求します。else
(hasddd(tstruct)
が真の場合、つまり可変引数を持つ関数):!isddd
: 呼び出し側が可変引数を展開していない場合(例:f(a, b, c)
でc
が可変引数に相当)、可変引数を除く必須引数の数(n1-1
)と比較します。n2 < n1-1
であればgoto notenough;
にジャンプします。これは、可変引数部分が空である可能性を考慮しています。else
(isddd
が真の場合、つまり呼び出し側が可変引数を展開している場合、例:f(args...)
):n2 > n1
であればgoto toomany;
、n2 < n1
であればgoto notenough;
にジャンプします。これは、展開された引数の数が、可変引数を含むパラメータの総数と一致するかを厳密にチェックします。
- 関数のパラメータの数(
- 単一の構造体引数に対する特殊なケースの改善:
これらの変更により、Goコンパイラは、引数カウントの誤りに対して、より早期に、より正確なエラーメッセージを生成できるようになりました。特に可変引数を持つ関数に対するエラー報告が改善され、開発者が問題を特定しやすくなっています。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.c
// 新規追加関数: hasddd
static int
hasddd(Type *t)
{
Type *tl;
for(tl=t->type; tl; tl=tl->down) {
if(tl->isddd)
return 1;
}
return 0;
}
// 新規追加関数: downcount
static int
downcount(Type *t)
{
Type *tl;
int n;
n = 0;
for(tl=t->type; tl; tl=tl->down) {
n++;
}
return n;
}
// typecheckaste 関数内の変更点
// 既存のコードブロック内に追加された部分
// (nl != nil && nl->next == nil && (n = nl->n)->type != T)
// if(n->type->etype == TSTRUCT && n->type->funarg) {
if(!hasddd(tstruct)) { // 呼び出される関数が可変引数を持たない場合
n1 = downcount(tstruct); // 期待されるパラメータ数
n2 = downcount(n->type); // 渡された引数(構造体)の要素数
if(n2 > n1)
goto toomany;
if(n2 < n1)
goto notenough;
}
// }
// typecheckaste 関数内の別の変更点
// 既存のコードブロック内に追加された部分
n1 = downcount(tstruct); // 期待されるパラメータ数
n2 = count(nl); // 実際に渡された引数の数
if(!hasddd(tstruct)) { // 呼び出される関数が可変引数を持たない場合
if(n2 > n1)
goto toomany;
if(n2 < n1)
goto notenough;
}
else { // 呼び出される関数が可変引数を持つ場合
if(!isddd) { // 呼び出し側が可変引数を展開していない場合
if(n2 < n1-1) // 必須引数の数と比較
goto notenough;
} else { // 呼び出し側が可変引数を展開している場合
if(n2 > n1)
goto toomany;
if(n2 < n1)
goto notenough;
}
}
test/fixedbugs/issue7675.go
// errorcheck
// Copyright 2014 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.
// Issue 7675: fewer errors for wrong argument count
package p
func f(string, int, float64, string) // 通常の関数
func g(string, int, float64, ...string) // 可変引数関数
func main() {
f(1, 0.5, "hello") // ERROR "not enough arguments" - fは4引数必要だが3つしか渡されていない
f("1", 2, 3.1, "4") // OK
f(1, 0.5, "hello", 4, 5) // ERROR "too many arguments" - fは4引数必要だが5つ渡されている
g(1, 0.5) // ERROR "not enough arguments" - gは最低3引数必要だが2つしか渡されていない
g("1", 2, 3.1) // OK - 可変引数部分が空
g(1, 0.5, []int{3, 4}...) // ERROR "not enough arguments" - 型が異なるため、可変引数部分が認識されず必須引数が不足
g("1", 2, 3.1, "4", "5") // OK - 可変引数部分に2つの文字列
g(1, 0.5, "hello", 4, []int{5, 6}...) // ERROR "too many arguments" - 型が異なるため、可変引数部分が認識されず引数が多すぎる
}
コアとなるコードの解説
-
hasddd
関数: この関数は、Goの型システムにおいて、特定の型が可変引数(...
)のセマンティクスを持っているかどうかを効率的に判断するために導入されました。Type
構造体のリンクリストを走査し、isddd
フラグがセットされている要素があるかをチェックします。これにより、型チェックロジック内で可変引数を持つ関数とそうでない関数を区別し、それぞれに適切な引数カウントの検証を適用できるようになります。 -
downcount
関数: この関数は、関数のパラメータリストや、関数呼び出しで渡される引数のリストなど、Type
構造体で表現されるリンクリストの要素数をカウントします。これは、期待される引数の数と実際に渡された引数の数を比較する際に不可欠なユーティリティ関数です。 -
typecheckaste
関数内の変更:- 単一の構造体引数に対する改善: 以前は、関数呼び出しの引数が単一の構造体として渡され、それが関数の引数リストを表す特殊なケースにおいて、引数カウントの誤りが適切に報告されない可能性がありました。この修正では、
hasddd
とdowncount
を使用して、この特殊なケースでも引数の過不足を正確に検出し、toomany
またはnotenough
エラーに分岐させることで、より明確なエラーメッセージを生成するようにしました。 - 一般的な引数カウントチェックの強化:
typecheckaste
の後半部分では、関数が可変引数を持つかどうかに応じて、引数カウントの検証ロジックが分岐します。- 可変引数を持たない関数に対しては、渡された引数の数と期待されるパラメータの数が厳密に一致することを要求します。
- 可変引数を持つ関数に対しては、呼び出し側が可変引数を展開しているかどうか(
isddd
フラグ)によってさらに分岐します。これにより、可変引数部分が空である場合や、展開された引数の数が多すぎる/少なすぎる場合など、可変引数特有のシナリオにおける引数カウントの誤りを正確に検出できるようになりました。
- 単一の構造体引数に対する改善: 以前は、関数呼び出しの引数が単一の構造体として渡され、それが関数の引数リストを表す特殊なケースにおいて、引数カウントの誤りが適切に報告されない可能性がありました。この修正では、
これらの変更は、Goコンパイラの型チェックの堅牢性を高め、特に引数カウントに関するエラーメッセージの品質を向上させることに貢献しています。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/85040044
参考にした情報源リンク
- コミット情報:
commit_data/19190.txt
- Go言語のコンパイラ設計に関する一般的な知識
- Go言語の可変引数に関するドキュメント
- Go言語の型システムに関する一般的な知識
src/cmd/gc/typecheck.c
のソースコード分析test/fixedbugs/issue7675.go
のテストケース分析