[インデックス 16776] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における、可変長引数(...
args)を持つ関数呼び出しにおいて、f(g())
のような形式で引数を渡した際に発生する誤ったエラーメッセージを修正するものです。具体的には、型不一致のエラーメッセージが、期待される型を正しく表示しない問題を解決します。
コミット
commit 7e270cf6c4be9ddc240c2661c18f163728cbb897
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Tue Jul 16 11:43:11 2013 +0200
cmd/gc: fix incorrect error when using f(g()) form on ... args
Fixes #5358.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/11282044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e270cf6c4be9ddc240c2661c18f163728cbb897
元コミット内容
cmd/gc: fix incorrect error when using f(g()) form on ... args
このコミットは、Goコンパイラ(cmd/gc
)において、可変長引数(...
args)を使用する関数に、別の関数呼び出しの結果(f(g())
の形式)を渡す際に、誤ったエラーメッセージが表示される問題を修正します。具体的には、型不一致のエラーメッセージで、期待される型が正しく表示されないバグを修正します。
この修正は、GoのIssue 5358を解決します。
変更の背景
Go言語には、可変長引数(variadic functions)という機能があります。これは、関数が不定数の引数を受け取ることができるようにするものです。例えば、func f(x int, y ...int)
のように定義された関数 f
は、f(1)
、f(1, 2)
、f(1, 2, 3)
のように呼び出すことができます。可変長引数は、関数内でスライスとして扱われます。
このコミットが修正する問題は、このような可変長引数を持つ関数に対して、別の関数呼び出しの結果を引数として渡す場合に発生していました。具体的には、f(g())
のような形式で、g()
が複数の戻り値を返す場合や、可変長引数に展開されるべきスライスを返す場合に、コンパイラが型チェックを行う際に、エラーメッセージが不正確になるというバグがありました。
元のコンパイラの実装では、型不一致のエラーメッセージを生成する際に、期待される型を誤って参照していました。これにより、ユーザーは「cannot use T1 as type T2
」というエラーを受け取った際に、T2
が実際には期待される型とは異なる、より高レベルの型(例えば、スライス型そのもの)として表示されてしまい、問題の特定が困難になっていました。
このバグは、GoのIssue 5358として報告されており、このコミットはその問題を解決するために作成されました。正確なエラーメッセージは、開発者がコードのバグを迅速に特定し、修正するために不可欠です。
前提知識の解説
Go言語の型システムと型チェック
Go言語は静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。これにより、多くの型関連のエラーが実行時ではなくコンパイル時に検出され、プログラムの堅牢性が向上します。
可変長引数(Variadic Functions)
Goの関数は、最後のパラメータに ...
を付けることで、可変長引数を受け取ることができます。
例: func sum(nums ...int) int
この場合、nums
は関数内で []int
型のスライスとして扱われます。
関数の戻り値と引数への展開
Goの関数は複数の戻り値を返すことができます。また、スライスを可変長引数に展開して渡すことも可能です。
例: f(g()...)
のように、g()
がスライスを返し、それを f
の可変長引数に展開するケース。
Goコンパイラの構造(cmd/gc
)
cmd/gc
はGo言語の公式コンパイラです。コンパイラは、ソースコードを解析し、抽象構文木(AST)を構築し、型チェックを行い、最終的に実行可能なバイナリを生成します。
src/cmd/gc/typecheck.c
は、コンパイラの型チェックフェーズを担当するC言語のソースファイルです。このファイルには、Goプログラムの各ノード(式、ステートメントなど)の型を検証し、型規則に違反している場合にエラーを報告するロジックが含まれています。
yyerror
関数
yyerror
は、Goコンパイラ内でコンパイルエラーメッセージを出力するために使用される関数です。C言語の printf
に似た形式で、フォーマット文字列と可変個の引数を受け取り、エラーメッセージを標準エラー出力に表示します。正確なエラーメッセージは、開発者が問題を理解し、修正するために非常に重要です。
技術的詳細
このコミットの技術的な核心は、src/cmd/gc/typecheck.c
ファイル内の typecheckaste
関数におけるエラーメッセージの生成ロジックの修正にあります。
typecheckaste
関数は、Goコンパイラの型チェックフェーズにおいて、関数呼び出しの引数(特に可変長引数を含む場合)の型を検証する役割を担っています。この関数は、引数の型と関数のパラメータの型が一致するかどうかを assignop
関数を使ってチェックします。型が一致しない場合、yyerror
を呼び出してエラーメッセージを生成します。
問題は、可変長引数に渡される値の型が、期待される要素型ではなく、スライス型そのものとしてエラーメッセージに表示されてしまうことでした。
具体的には、以下の行が修正されました。
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2131,9 +2131,9 @@ typecheckaste(int op, Node *call, int isddd, Type *tstruct, NodeList *nl, char *
for(; tn; tn=tn->down) {
if(assignop(tn->type, tl->type->type, &why) == 0) {
if(call != N)
- yyerror("cannot use %T as type %T in argument to %N%s", tn->type, tl->type, call, why);
+ yyerror("cannot use %T as type %T in argument to %N%s", tn->type, tl->type->type, call, why);
else
- yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type, desc, why);
+ yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type->type, desc, why);
}
}
goto out;
変更前は、yyerror
の第3引数(%T
に対応する型情報)として tl->type
が渡されていました。ここで tl
は、可変長引数に対応するパラメータの型情報を持つノードです。可変長引数の場合、tl->type
は []int
のようなスライス型そのものを指していました。
しかし、エラーメッセージで本当に表示すべきは、そのスライスが期待する「要素の型」です。例えば、...int
の場合、期待されるのは int
型です。Goの型システムでは、スライス型 []T
の要素型は T
であり、これは Type
構造体の type
フィールドを通じてアクセスできます。
したがって、修正では tl->type
を tl->type->type
に変更することで、スライス型そのものではなく、その要素型(例: []int
の場合は int
)をエラーメッセージに表示するようにしました。これにより、エラーメッセージがより正確になり、開発者が型不一致の原因を特定しやすくなりました。
テストケース test/fixedbugs/issue5358.go
は、この修正が正しく機能することを確認するために追加されました。このテストケースでは、f(g())
の形式で可変長引数を持つ関数を呼び出し、g()
の戻り値の型が f
の期待する引数の型と一致しない場合に、正しいエラーメッセージ「as type int in
」が出力されることを検証しています。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.c
ファイルの以下の2箇所が変更されました。
// 変更前
yyerror("cannot use %T as type %T in argument to %N%s", tn->type, tl->type, call, why);
// 変更後
yyerror("cannot use %T as type %T in argument to %N%s", tn->type, tl->type->type, call, why);
// 変更前
yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type, desc, why);
// 変更後
yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type->type, desc, why);
また、この修正を検証するための新しいテストファイルが追加されました。
test/fixedbugs/issue5358.go
// errorcheck
// Copyright 2013 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 5358: incorrect error message when using f(g()) form on ... args.
package main
func f(x int, y ...int) {}
func g() (int, []int)
func main() {
f(g()) // ERROR "as type int in"
}
コアとなるコードの解説
変更の核心は、yyerror
関数に渡される第3引数(%T
フォーマット指定子に対応する型情報)が tl->type
から tl->type->type
に変更された点です。
tn->type
: これは、実際に渡された引数の型を表します。tl
: これは、関数パラメータのリスト内の現在のノード(可変長引数に対応するノード)を指します。tl->type
: 変更前は、これが可変長引数に対応する「スライス型そのもの」(例:[]int
)を指していました。tl->type->type
: 変更後は、これがスライス型の「要素の型」(例:int
)を指すようになります。
Goコンパイラの内部では、型情報は Type
構造体で表現されます。スライス型 []T
の場合、Type
構造体はスライス型全体を表し、その type
フィールドが要素型 T
を指すように設計されています。
したがって、この修正は、エラーメッセージにおいて「期待される型」として、スライス型全体ではなく、その要素型を正確に表示するようにすることで、ユーザーにとってより分かりやすいエラーメッセージを提供します。
追加されたテストケース issue5358.go
は、この修正の意図を明確に示しています。
func f(x int, y ...int)
は、最初の引数が int
、残りが可変長の int
型のスライスとして扱われます。
func g() (int, []int)
は、int
と []int
の2つの戻り値を返します。
f(g())
の呼び出しでは、g()
の最初の戻り値 int
は f
の x
に対応しますが、g()
の2番目の戻り値 []int
は f
の可変長引数 y ...int
に直接渡されることになります。しかし、f
の y
は int
型の要素を期待しているため、[]int
型のスライスが直接渡されると型不一致が発生します。
修正前は、このエラーメッセージが「cannot use []int as type []int in argument to f
」のように、期待される型も []int
と表示されてしまい、混乱を招いていました。修正後は、「cannot use []int as type int in argument to f
」のように、[]int
が int
型として使えないという、より正確なメッセージが表示されるようになります。これは、f
の可変長引数 y ...int
が、内部的には []int
として扱われるものの、その要素は int
であるべきというGoの型規則を反映したものです。
関連リンク
- Go Issue 5358: https://github.com/golang/go/issues/5358
- Go CL 11282044: https://golang.org/cl/11282044
参考にした情報源リンク
- Go言語の公式ドキュメント(可変長引数、型システムに関する情報)
- Goコンパイラのソースコード(
src/cmd/gc/typecheck.c
の詳細な分析) - Go Issue Tracker(Issue 5358の議論内容)
- Go Code Review Comments (CL 11282044のレビューコメント)
- Go言語のコンパイラに関する一般的な知識