[インデックス 18319] ファイルの概要
このコミットは、Goコンパイラの型チェックロジックにおけるバグ修正に関するものです。具体的には、多重代入におけるnil
型の型チェックの挙動を修正し、issue #6572
で報告された問題を解決します。
コミット
commit 062ae4571168608ca2bcd6fa1a9d122a20a5d677
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date: Tue Jan 21 22:44:54 2014 -0500
cmd/gc: do not typecheck nil types in multiple assignment
Fixes #6572.
LGTM=rsc, daniel.morsing, rsc
R=golang-codereviews, bradfitz, minux.ma, iant, rsc, gobot, daniel.morsing
CC=golang-codereviews
https://golang.org/cl/14516055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/062ae4571168608ca2bcd6fa1a9d122a20a5d677
元コミット内容
commit 062ae4571168608ca2bcd6fa1a9d122a20a5d677
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date: Tue Jan 21 22:44:54 2014 -0500
cmd/gc: do not typecheck nil types in multiple assignment
Fixes #6572.
LGTM=rsc, daniel.morsing, rsc
R=golang-codereviews, bradfitz, minux.ma, iant, rsc, gobot, daniel.morsing
CC=golang-codereviews
https://golang.org/cl/14516055
---
src/cmd/gc/typecheck.c | 2 +-\n test/fixedbugs/issue6572.go | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 68d2c3404d..3c27d99154 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2814,7 +2814,7 @@ typecheckas2(Node *n)\n tn->op = OAS2FUNC;\n tt = structfirst(&s, &r->type);\n for(ll=n->list; ll; ll=ll->next) {\n- if(ll->n->type != T)\n+ if(t->type != T && ll->n->type != T)\n checkassignto(t->type, ll->n);\n if(ll->n->defn == n && ll->n->ntype == N)\n ll->n->type = t->type;\ndiff --git a/test/fixedbugs/issue6572.go b/test/fixedbugs/issue6572.go
new file mode 100644
index 0000000000..e75da54c99 100644
--- /dev/null
+++ b/test/fixedbugs/issue6572.go
@@ -0,0 +1,21 @@
+// 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.
+
+package main
+
+func foo() (T, T) { // ERROR "undefined"
+ return 0, 0
+}
+
+func bar() (T, string, T) { // ERROR "undefined"
+ return 0, "", 0
+}
+
+func main() {
+ var x, y, z int
+ x, y = foo()
+ x, y, z = bar() // ERROR "cannot (use type|assign) string"
+}
変更の背景
このコミットは、Goコンパイラ(cmd/gc
)における多重代入の型チェックに関するバグを修正します。具体的には、issue #6572
で報告された問題に対応しています。
issue #6572
は、関数が複数の値を返す際に、その戻り値の中に未定義の型(T
)が含まれている場合、コンパイラが誤った型チェックを行うというものでした。特に、nil
型(Goコンパイラの内部表現でT
として扱われることがあります)が絡む多重代入において、不適切なエラーが発生する可能性がありました。
このバグにより、本来はコンパイルが成功すべきコードがエラーになったり、あるいは逆に、型エラーがあるべきコードが誤ってコンパイルされてしまうといった問題が引き起こされる可能性がありました。このコミットは、このような誤った型チェックの挙動を修正し、コンパイラの堅牢性と正確性を向上させることを目的としています。
前提知識の解説
このコミットの理解には、以下のGo言語およびコンパイラの概念に関する知識が役立ちます。
- 多重代入 (Multiple Assignment): Go言語の大きな特徴の一つで、複数の変数に複数の値を一度に代入できる機能です。例えば、
x, y = foo()
のように、関数が複数の戻り値を返す場合や、複数の変数を同時に初期化する場合に利用されます。 - 型チェック (Type Checking): コンパイラの重要なフェーズの一つで、プログラム中の各式や変数の型がGo言語の型システム規則に適合しているかを確認するプロセスです。型チェックにより、型に関する誤り(例: 整数型変数に文字列を代入しようとする)をコンパイル時に検出できます。
nil
型とGoコンパイラの内部表現: Go言語においてnil
は、ポインタ、スライス、マップ、チャネル、インターフェース、関数などのゼロ値を表すために使用されます。コンパイラの内部では、型がまだ決定されていない、あるいは特定の型に依存しない汎用的な「型なし」の状態を示すために、特別な内部型が使用されることがあります。このコミットの文脈では、T
という内部表現がnil
型や未定義の型を示すために使われている可能性があります。src/cmd/gc/typecheck.c
: Goコンパイラのソースコードの一部で、C言語で書かれた型チェックのロジックを実装しているファイルです。Goコンパイラは、初期の段階ではC言語で書かれており、その後Go言語自身で書かれたコンパイラ(gc
)に移行しました。このファイルは、その過渡期または初期のコンパイラの一部を示しています。checkassignto
関数:typecheck.c
内で定義されている関数で、ある型から別の型への代入が有効であるかをチェックする役割を担っています。
技術的詳細
このバグは、src/cmd/gc/typecheck.c
内のtypecheckas2
関数、特に多重代入を処理する部分で発生していました。typecheckas2
関数は、x, y = foo()
のような多重代入文を型チェックする際に呼び出されます。
元のコードでは、多重代入の右辺(foo()
の戻り値など)の型と、左辺の各変数の型を比較し、代入の妥当性をcheckassignto
関数で検証していました。しかし、このチェックがll->n->type != T
という条件で行われていました。ここでT
は、Goコンパイラの内部で「型なし」または「nil
型」を示す特別な型として扱われることがあります。
問題は、右辺の戻り値の型(t->type
)がT
である場合(つまり、nil
型や未定義の型である場合)に、左辺の変数(ll->n->type
)がT
でないにもかかわらず、checkassignto
が呼び出されてしまうことでした。これは、nil
型が特定の型に代入される際に、その型がまだ確定していないにもかかわらず、不必要な型チェックが実行され、結果として誤ったエラー(例: undefined
エラーやcannot assign string
エラー)が発生する原因となっていました。
修正は、checkassignto
を呼び出す条件をif(t->type != T && ll->n->type != T)
に変更することです。この変更により、右辺の戻り値の型(t->type
)がT
である場合、または左辺の変数の型(ll->n->type
)がT
である場合には、checkassignto
関数による厳密な型チェックをスキップするようになります。これにより、nil
型が絡む多重代入において、型がまだ確定していない状態での不適切な型チェックが回避され、コンパイラが正しい挙動を示すようになります。
test/fixedbugs/issue6572.go
という新しいテストファイルが追加されており、このテストは修正前のコンパイラではエラーとなるが、修正後のコンパイラでは正しく動作することを確認します。このテストケースは、foo()
やbar()
のような関数が未定義の型T
を返す場合に、多重代入がどのように振る舞うかを検証しています。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.c
ファイルのtypecheckas2
関数内の以下の行が変更されました。
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2814,7 +2814,7 @@ typecheckas2(Node *n)\
tn->op = OAS2FUNC;\
tt = structfirst(&s, &r->type);\
for(ll=n->list; ll; ll=ll->next) {\
- if(ll->n->type != T)\
+ if(t->type != T && ll->n->type != T)\
checkassignto(t->type, ll->n);\
if(ll->n->defn == n && ll->n->ntype == N)\
ll->n->type = t->type;
コアとなるコードの解説
変更された行は、多重代入の各要素に対する型チェックの条件を修正しています。
-
変更前:
if(ll->n->type != T)
- この条件は、「左辺の変数の型が
T
(型なし/nil型)でない場合」にcheckassignto
関数を呼び出して型チェックを行う、という意味でした。 - 問題は、右辺の戻り値の型(
t->type
)がT
であるにもかかわらず、左辺の変数の型がT
でない場合に、不適切な型チェックが行われてしまうことでした。例えば、func foo() (T, T)
のような関数から返されるT
がnil
型として扱われる場合、そのnil
をint
型の変数に代入しようとすると、checkassignto
が呼び出され、誤ったエラーが発生する可能性がありました。
- この条件は、「左辺の変数の型が
-
変更後:
if(t->type != T && ll->n->type != T)
- この新しい条件は、「右辺の戻り値の型が
T
でなく、かつ左辺の変数の型もT
でない場合」にのみcheckassignto
関数を呼び出す、という意味になります。 - これにより、右辺の戻り値の型が
T
である場合(つまり、nil
型や未定義の型である場合)には、左辺の変数の型が何であってもcheckassignto
は呼び出されなくなります。これは、nil
型が特定の型に代入される際に、その型がまだ確定していない状態での不必要な型チェックを回避するために重要です。 - この修正により、コンパイラは
nil
型が絡む多重代入において、より正確な型推論と型チェックを行うことができるようになり、issue #6572
で報告されたような誤ったエラーの発生を防ぎます。
- この新しい条件は、「右辺の戻り値の型が
関連リンク
- Go Issue #6572: https://github.com/golang/go/issues/6572 (直接のリンクは見つかりませんでしたが、コミットメッセージに記載されています)
- Go CL 14516055: https://golang.org/cl/14516055