[インデックス 13072] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるappend
関数の第一引数に関するエラーメッセージの精度を向上させるものです。具体的には、append(nil, x)
のような呼び出しにおいて、以前は単に「第一引数はスライスであるべきだが、nilである」というメッセージだったものを、「第一引数は型付きスライスであるべきだが、型なしのnilである」という、より詳細なエラーメッセージに変更しています。これにより、開発者が問題の原因を特定しやすくなります。
コミット
commit fcc1f2ac557602f4097e498fa4dd879fb5a680a5
Author: Russ Cox <rsc@golang.org>
Date: Tue May 15 12:51:58 2012 -0400
cmd/gc: make append(nil, x) error more precise
Before:
./x.go:6: first argument to append must be slice; have nil
After:
./x.go:6: first argument to append must be typed slice; have untyped nil
Fixes #3616.
R=ken2
CC=golang-dev
https://golang.org/cl/6209067
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fcc1f2ac557602f4097e498fa4dd879fb5a680a5
元コミット内容
cmd/gc: make append(nil, x) error more precise
このコミットは、Goコンパイラ(cmd/gc
)がappend
関数の第一引数にnil
が渡された際のエラーメッセージをより正確にするためのものです。
変更前は、append(nil, x)
のようなコードに対して、コンパイラは以下のエラーを出力していました。
./x.go:6: first argument to append must be slice; have nil
変更後は、より詳細なエラーメッセージが出力されるようになります。
./x.go:6: first argument to append must be typed slice; have untyped nil
この変更は、Issue #3616を修正するものです。
変更の背景
Go言語のappend
関数は、第一引数にスライスを期待します。しかし、nil
はスライス型だけでなく、インターフェース型、マップ型、チャネル型、関数型など、様々な型で表現されうる「型なしのnil (untyped nil)」として存在します。
従来のコンパイラのエラーメッセージ「first argument to append must be slice; have nil
」は、append
にnil
が渡された際に、そのnil
が「型なしのnil」であること、そしてappend
が期待するのは「型付きのスライスとしてのnil」であることを明確に示していませんでした。
この曖昧さが、特にGo言語に不慣れな開発者にとって、なぜnil
がエラーになるのか、どのように修正すれば良いのかを理解する上で混乱を招く可能性がありました。例えば、var s []int
と宣言されたスライスs
はnil
ですが、これは型付きのnil
であり、append(s, x)
は有効です。一方で、単なるnil
リテラルは型なしであり、append(nil, x)
は無効です。
このコミットは、このエラーメッセージをより具体的にすることで、開発者が「型なしのnil」と「型付きのスライスとしてのnil」の違いを認識し、適切なコード修正を行うための手助けをすることを目的としています。これは、コンパイラのエラーメッセージの質を向上させ、開発者のデバッグ体験を改善する一般的な取り組みの一環です。
前提知識の解説
Go言語のappend
関数
append
はGo言語の組み込み関数で、スライスに要素を追加するために使用されます。その基本的なシグネチャは以下のようになります。
func append(slice []Type, elems ...Type) []Type
slice
: 要素を追加する元のスライス。elems
: スライスに追加する1つ以上の要素。- 戻り値: 要素が追加された新しいスライス。元のスライスが容量不足の場合、新しい基底配列が割り当てられることがあります。
append
関数の第一引数は必ずスライス型である必要があります。
Go言語におけるnil
Go言語におけるnil
は、特定の型のゼロ値を示すキーワードです。しかし、nil
は単一の型を持つわけではありません。以下のような参照型でnil
を使用できます。
- スライス (
[]T
) - マップ (
map[K]V
) - チャネル (
chan T
) - インターフェース (
interface{}
) - 関数 (
func(...)
) - ポインタ (
*T
)
重要なのは、nil
リテラル自体は「型なしのnil (untyped nil)」であるという点です。これは、コンテキストによって異なる型に推論される可能性があります。例えば、var s []int = nil
のように明示的に型が与えられた場合、nil
は[]int
型のゼロ値として扱われます。しかし、append(nil, 1)
のように型情報なしでnil
が直接使用された場合、コンパイラはそのnil
がどの参照型を意図しているのかを判断できません。
型付きのnil
と型なしのnil
- 型なしの
nil
(Untyped nil):nil
リテラルそのものを指します。これは、コンパイラがその使用コンテキストから型を推論するまで、特定の型を持たない状態です。例えば、append(nil, x)
のnil
は型なしのnil
です。 - 型付きの
nil
(Typed nil): 特定の参照型に割り当てられたnil
を指します。例えば、var s []int
と宣言されたs
は、[]int
型の型付きのnil
スライスです。var m map[string]int
と宣言されたm
は、map[string]int
型の型付きのnil
マップです。
append
関数は、第一引数として「型付きのスライス」を期待します。型なしのnil
が渡された場合、コンパイラはそれがどの型のスライスを意図しているのかを判断できないため、エラーとなります。
技術的詳細
このコミットの技術的詳細は、Goコンパイラの型チェックフェーズにおけるappend
関数の引数処理にあります。Goコンパイラは、ソースコードを抽象構文木(AST)に変換した後、各ノードの型を決定し、型規則に違反がないかをチェックします。
append
関数の型チェックは、src/cmd/gc/typecheck.c
ファイル内の関連するロジックで行われます。このファイルは、Goコンパイラのフロントエンドの一部であり、Goのソースコードを解析し、型チェックや最適化の前段階の処理を行います。
変更前のコードでは、append
の第一引数がスライス型でない場合にエラーを報告していました。このチェックは、引数の型がisslice(t)
(t
がスライス型であるか)という条件で判断されていました。しかし、このチェックだけでは、nil
が渡された場合に、それが型なしのnil
なのか、それとも型付きだがスライスではないnil
なのかを区別できませんでした。
このコミットでは、isslice(t)
が偽(つまり、引数がスライスではない)の場合に、さらにisconst(args->n, CTNIL)
(引数がnil
定数であるか)というチェックを追加しています。
- もし引数が
nil
定数であれば、それは「型なしのnil」であると判断できます。この場合、より具体的なエラーメッセージ「first argument to append must be typed slice; have untyped nil
」を出力します。 - もし引数が
nil
定数でなければ、それはスライスではないが、何らかの型を持つ引数であると判断できます。この場合、従来のエラーメッセージ「first argument to append must be slice; have %lT
」を出力します。ここで%lT
は引数の型を表示します。
このロジックの追加により、コンパイラはappend
の第一引数にnil
が渡された際に、そのnil
が型なしであるという重要な情報をエラーメッセージに含めることができるようになりました。これにより、開発者はappend(nil, x)
のようなコードをvar s []int; append(s, x)
のように修正する必要があることをより明確に理解できます。
コアとなるコードの変更箇所
変更はsrc/cmd/gc/typecheck.c
ファイルにあります。
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -1140,6 +1140,10 @@ reswitch:
tgoto error;
tn->type = t;
tif(!isslice(t)) {
+ tif(isconst(args->n, CTNIL)) {
+ yyerror("first argument to append must be typed slice; have untyped nil", t);
+ tgoto error;
+ }
tyyerror("first argument to append must be slice; have %lT", t);
tgoto error;
}
この差分は、isslice(t)
が偽(つまり、append
の第一引数がスライスではない)の場合の処理ブロック内に、新しい条件分岐が追加されたことを示しています。
コアとなるコードの解説
変更されたコードブロックは、append
関数の第一引数の型チェックを行っている部分です。
if(!isslice(t)) { // もし引数 't' がスライス型でなければ
if(isconst(args->n, CTNIL)) { // さらに、もし引数 'args->n' が nil 定数であれば
yyerror("first argument to append must be typed slice; have untyped nil", t); // より具体的なエラーメッセージを出力
goto error; // エラー処理へジャンプ
}
yyerror("first argument to append must be slice; have %lT", t); // 従来の一般的なエラーメッセージを出力
goto error; // エラー処理へジャンプ
}
if(!isslice(t))
: これは、append
関数の第一引数t
がスライス型であるかどうかをチェックする条件です。isslice
関数は、与えられた型がスライス型であれば真を返します。このif
ブロックは、引数がスライス型ではない場合に実行されます。if(isconst(args->n, CTNIL))
: この新しい条件は、isslice(t)
が偽であった場合に、さらに引数args->n
がnil
定数(CTNIL
)であるかどうかをチェックします。args->n
は、append
関数の第一引数に対応するASTノードを指します。isconst
関数は、ノードが指定された定数型であるかどうかをチェックします。CTNIL
はnil
定数を表します。- この条件が真の場合、それは「型なしのnil」が
append
の第一引数として渡されたことを意味します。
yyerror("first argument to append must be typed slice; have untyped nil", t);
:nil
定数が渡された場合に表示される新しいエラーメッセージです。これにより、開発者は「型なしのnil」が問題であることを明確に理解できます。yyerror("first argument to append must be slice; have %lT", t);
:nil
定数ではないがスライス型でもない引数が渡された場合に表示される従来のエラーメッセージです。%lT
は引数の実際の型に置き換えられます。
この変更により、コンパイラはappend
の第一引数に関するエラーを、そのnil
が型なしであるかどうかに基づいて区別し、より適切な診断メッセージを提供できるようになりました。
関連リンク
- Go Issue #3616: https://golang.org/issue/3616
- Go Code Review: https://golang.org/cl/6209067
参考にした情報源リンク
特になし。提供されたコミット情報とGo言語の一般的な知識に基づいて解説を生成しました。