[インデックス 1382] ファイルの概要
このコミットは、Goコンパイラのnew
組み込み関数に関連するエラーメッセージの修正に関するものです。具体的には、new(bool)
のような無効な型に対してnew
関数が呼び出された際に表示されるエラーメッセージが、より正確な情報を示すように改善されています。
コミット
commit cd40cd243535e1662dda14f238adf59535cc045c
Author: Russ Cox <rsc@golang.org>
Date: Fri Dec 19 12:18:44 2008 -0800
fix new(bool) error message
R=ken
OCL=21616
CL=21616
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cd40cd243535e1662dda14f238adf59535cc045c
元コミット内容
fix new(bool) error message
このコミットは、new(bool)
のような無効な型に対してnew
関数が使用された場合に表示されるエラーメッセージを修正することを目的としています。
変更の背景
Go言語のnew
組み込み関数は、指定された型のゼロ値へのポインタを割り当てて返します。しかし、new
関数はポインタ型ではない型(例えば、bool
やstring
など)に対して直接使用することはできません。このような誤った使用があった場合、コンパイラはエラーを報告する必要があります。
このコミット以前は、new(bool)
のようなコードが書かれた際に、コンパイラが出力するエラーメッセージが不正確でした。具体的には、エラーメッセージがnew(*bool)
のように、あたかもポインタ型を期待しているかのような表示になっていました。これは、コンパイラ内部で型情報を処理する際に、元の型ではなく、そのポインタ型(または内部で一時的に生成されたポインタ型)を参照してしまっていたためと考えられます。
開発者は、ユーザーがより理解しやすい、正確なエラーメッセージを受け取るべきであると考え、この修正が行われました。正確なエラーメッセージは、開発者がコードの誤りを迅速に特定し、修正するのに役立ちます。
前提知識の解説
Go言語のnew
組み込み関数
Go言語には、メモリ割り当てを行うための2つの組み込み関数、new
とmake
があります。
new(Type)
:new
は、指定されたType
のゼロ値を格納するのに十分なメモリを割り当て、そのメモリへのポインタを返します。例えば、p := new(int)
はint
型のゼロ値(0)を格納するメモリを割り当て、そのアドレスをp
に代入します。p
の型は*int
になります。new
は、スライス、マップ、チャネル以外の任意の型に使用できます。make(Type, ...)
:make
は、スライス、マップ、チャネルといった参照型を初期化するために使用されます。これらは内部的にデータ構造を必要とするため、new
では不十分です。例えば、s := make([]int, 10)
は、要素数10のスライスを初期化します。
このコミットで問題となっているのはnew
関数であり、特にnew(bool)
のように、new
がポインタ型を返すにもかかわらず、その引数にポインタ型ではない基本型を直接渡した場合の挙動です。Goの設計上、new(T)
は*T
を返すため、new(bool)
は*bool
を返しますが、new
の引数として渡される型が、new
が内部的に処理するポインタ型と混同されることがありました。
Goコンパイラの構造(src/cmd/gc/walk.c
の役割)
Goコンパイラは複数のステージで構成されており、src/cmd/gc
ディレクトリはGoコンパイラの主要な部分(フロントエンドと一部のバックエンド)を含んでいます。
walk.c
: このファイルは、コンパイラの「ウォーカー(Walker)」または「ツリーウォーカー(Tree Walker)」と呼ばれる部分の実装を含んでいます。ウォーカーは、抽象構文木(AST: Abstract Syntax Tree)を走査し、型チェック、定数畳み込み、最適化、コード生成のための準備など、様々な変換や処理を行います。newcompat
関数は、new
組み込み関数の呼び出しを処理する部分です。この関数は、new
に渡された引数の型を検証し、適切なメモリ割り当てコードを生成するか、無効な型が渡された場合にはエラーを報告します。
型システムとポインタ
Go言語は静的型付け言語であり、すべての変数には型があります。ポインタは、メモリ上の特定のアドレスを指す変数です。*Type
という構文は、Type
へのポインタを表します。
このコミットの文脈では、newcompat
関数内で、n->type
がnew
に渡された元の型を表し、t->type
がその型がポインタ型であった場合にそのポインタが指す先の型(つまり、デリファレンスされた型)を表すという区別が重要になります。
技術的詳細
このコミットの核心は、src/cmd/gc/walk.c
内のnewcompat
関数における型変数の使用方法の修正です。
newcompat
関数は、new
組み込み関数の呼び出しを処理します。この関数は、new(T)
のT
の部分、つまりnew
に渡された引数の型を検証し、有効な場合はメモリ割り当てのためのコードを生成し、無効な場合はエラーを報告します。
修正前のコードでは、newcompat
関数内でn->type
(new
に渡された元の型)を変数t
に代入し、その後、t
をポインタのデリファレンスのようにt = t->type
として更新していました。これにより、t
は元の型ではなく、そのポインタが指す先の型(または、ポインタではない型の場合は未定義の動作)を示すようになっていました。
問題は、エラーメッセージを出力するyyerror
関数が、この更新されたt
変数を使用していた点にありました。例えば、new(bool)
という呼び出しがあった場合、n->type
はbool
型を指します。しかし、t = t->type
の行が実行されると、t
はbool
型から別の(おそらく無効な)型に変わってしまいます。その結果、エラーメッセージはcannot new(*%T)
のように、bool
ではなく*bool
(または他の不正確な型)を表示してしまっていました。
この修正では、新しい変数t0
が導入されました。
t0 = n->type;
:new
に渡された元の型をt0
に保存します。t = t0->type;
:t
は引き続き、ポインタのデリファレンス後の型を処理するために使用されます。- エラーメッセージを出力する
yyerror
の呼び出し箇所で、t
の代わりにt0
を使用するように変更されました。
これにより、new(bool)
のような無効な呼び出しがあった場合でも、エラーメッセージはcannot new(bool)
のように、new
に渡された元の型を正確に表示するようになります。これは、ユーザーにとってより明確で役立つエラーメッセージとなります。
isptr
配列は、Goコンパイラ内部で型がポインタであるかどうかを判断するために使用されるフラグの配列です。TARRAY
やTSTRING
は、Goコンパイラ内部で配列型や文字列型を表す定数です。これらのチェックは、new
関数が特定の型に対してどのように振る舞うべきかを決定するために行われます。
コアとなるコードの変更箇所
src/cmd/gc/walk.c
ファイルのnewcompat
関数が変更されました。
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -2000,19 +2000,19 @@ Node*
newcompat(Node *n)
{
Node *r, *on;
- Type *t;\n+ Type *t, *t0;
- t = n->type;
- if(t == T)
+ t0 = n->type;
+ if(t0 == T)
goto bad;
- if(t->etype == TARRAY)
+ if(t0->etype == TARRAY)
return arrayop(n, Erv);
- if(!isptr[t->etype] || t->type == T)
+ if(!isptr[t0->etype] || t0->type == T)
goto bad;
- t = t->type;
+ t = t0->type;
switch(t->etype) {
case TSTRING:
\tgoto bad;
@@ -2031,7 +2031,7 @@ newcompat(Node *n)
default:
\tif(n->left != N)
-\t\t\tyyerror(\"cannot new(*%T, expr)\", t);
+\t\t\tyyerror(\"cannot new(%T, expr)\", t0);
\tdowidth(t);
\ton = syslook(\"mal\", 1);
\targtype(on, t);
@@ -2044,7 +2044,7 @@ newcompat(Node *n)
return r;
bad:
-\tyyerror(\"cannot new(*%T)\", t);
+\tyyerror(\"cannot new(%T)\", t0);
return n;
}
コアとなるコードの解説
変更は主に以下の点に集約されます。
-
新しい型変数
t0
の導入:- 変更前:
Type *t;
- 変更後:
Type *t, *t0;
newcompat
関数の冒頭で、Type
型のポインタ変数t0
が新しく宣言されました。これは、new
に渡された元の型を保持するために使用されます。
- 変更前:
-
元の型の保存:
- 変更前:
t = n->type;
- 変更後:
t0 = n->type;
n
はnew
呼び出しのASTノードを表し、n->type
はそのノードの型(つまり、new
に渡された引数の型)を指します。この元の型が、新しく導入されたt0
に代入されるようになりました。これにより、t0
はnew
に渡された実際の型を常に保持します。
- 変更前:
-
型チェックの対象変更:
- 変更前:
if(t == T)
、if(t->etype == TARRAY)
、if(!isptr[t->etype] || t->type == T)
- 変更後:
if(t0 == T)
、if(t0->etype == TARRAY)
、if(!isptr[t0->etype] || t0->type == T)
- これらの型チェックは、
new
に渡された型が有効であるか、または特定の特性(配列型であるか、ポインタ型であるかなど)を持っているかを判断するためのものです。これらのチェックが、元の型を保持するt0
に対して行われるようになりました。これにより、チェックがより正確になります。
- 変更前:
-
デリファレンスされた型の処理:
- 変更前:
t = t->type;
- 変更後:
t = t0->type;
- この行は、
new
に渡された型がポインタ型であった場合に、そのポインタが指す先の型(デリファレンスされた型)をt
に代入します。このt
は、その後のswitch
文で、デリファレンスされた型に基づいて追加の処理を行うために使用されます。t0
からt
への代入に変更されたことで、元の型t0
は保持されつつ、t
はデリファレンス後の型を正しく参照できるようになります。
- 変更前:
-
エラーメッセージの修正:
- 変更前:
yyerror("cannot new(*%T, expr)", t);
およびyyerror("cannot new(*%T)", t);
- 変更後:
yyerror("cannot new(%T, expr)", t0);
およびyyerror("cannot new(%T)", t0);
- これがこのコミットの最も重要な変更点です。エラーメッセージを出力する
yyerror
関数に渡される型引数が、t
からt0
に変更されました。また、エラーメッセージのフォーマット文字列も*
が削除され、cannot new(%T)
となりました。 - これにより、
new(bool)
のような無効な呼び出しがあった場合、以前はcannot new(*bool)
のように表示されていたエラーメッセージが、cannot new(bool)
と、new
に渡された元の型を正確に表示するようになりました。これは、ユーザーがエラーの原因をより直感的に理解するのに役立ちます。
- 変更前:
この変更により、Goコンパイラはnew
組み込み関数の誤用に対するエラーメッセージをより正確かつ分かりやすく表示できるようになり、開発者のデバッグ体験が向上しました。
関連リンク
-
Go言語の
new
とmake
について:- The Go Programming Language Specification - Allocations
- Go by Example: make vs new (これはGoの公式ドキュメントではありませんが、概念を理解するのに役立ちます)
-
Goコンパイラのソースコード(
src/cmd/gc
ディレクトリ):
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/cd40cd243535e1662dda14f238adf59535cc045c
- Go言語の公式ドキュメント (Go Programming Language Specification)
- Go言語のソースコード (GitHubリポジトリ)
- Go言語の
new
とmake
に関する一般的な解説記事 (Go by Exampleなど)