[インデックス 11111] ファイルの概要
gc: export nil literals without inferred type.
コミット
commit feaa9ed10aa369d27dcf5a69863c481f4875bd39
Author: Luuk van Dijk <lvd@golang.org>
Date: Wed Jan 11 21:26:54 2012 +0100
gc: export nil literals without inferred type.
Fixes #2678
R=rsc
CC=golang-dev
https://golang.org/cl/5529066
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/feaa9ed10aa369d27dcf5a69863c481f4875bd39
元コミット内容
gc: export nil literals without inferred type.
Fixes #2678
R=rsc
CC=golang-dev
https://golang.org/cl/5529066
変更の背景
このコミットは、Goコンパイラ(gc
)におけるnil
リテラルの内部表現とフォーマットに関するバグ(Issue 2678)を修正するために行われました。具体的には、nil
リテラルが型推論によって特定の型を持つように「装飾(decorated)」された場合に、コンパイラがそのnil
を正しく扱えず、誤った型チェックエラーやデバッグ情報の表示を引き起こす問題がありました。
提供されたテストケース(test/fixedbugs/bug392.go
)のコメントから、このバグは特にインポート時の型チェックにおいて顕在化し、nil
が非関数型であるにもかかわらず関数として呼び出されようとするような、誤ったコンパイルエラーが発生していたことが示唆されています。この修正は、コンパイラがnil
リテラルをその本来の「型を持たないnil
」として常に正しく認識し、処理できるようにすることを目的としています。
前提知識の解説
-
Go言語における
nil
:nil
はGo言語におけるゼロ値の一つで、ポインタ、インターフェース、マップ、スライス、チャネル、関数などの参照型に割り当てられます。nil
自体は特定の型を持ちませんが、文脈に応じて特定の型のゼロ値として扱われます。例えば、var p *int = nil
のように、ポインタ型にnil
を代入できます。この柔軟性が、コンパイラ内部でのnil
の扱いを複雑にすることがあります。 -
Goコンパイラ (
gc
):gc
はGo言語の公式コンパイラであり、Goのソースコードを機械語に変換する主要なツールです。コンパイルプロセスには、ソースコードの字句解析、構文解析、抽象構文木(AST: Abstract Syntax Tree)の構築、型チェック、中間コード生成、最適化、最終的な機械語コード生成などが含まれます。このコミットで変更されたsrc/cmd/gc/fmt.c
は、コンパイラ内部のデバッグや診断目的で、ASTノードなどの内部データ構造をフォーマット(文字列化)するためのコードが含まれるファイルです。 -
OLITERAL
: Goコンパイラの内部では、ソースコードの各要素がASTノードとして表現されます。OLITERAL
は、数値、文字列、ブール値、そしてnil
などのリテラル(定数値)を表すASTノードの種類の一つです。コンパイラはこれらのリテラルノードを処理し、型チェックやコード生成に利用します。 -
型推論: 型推論は、プログラミング言語が変数の宣言や式の評価時に、明示的な型指定なしにその型を自動的に決定する機能です。Go言語では、
var x = 10
のように、初期値から変数の型を推論できます。nil
も、代入される変数や使用される文脈によって、特定のポインタ型やインターフェース型などが推論されることがあります。この型推論の過程で、nil
リテラルが一時的に特定の型を持つように「装飾」されることがあり、これが今回のバグの原因となっていました。
技術的詳細
このコミットは、Goコンパイラの内部処理におけるnil
リテラルの表現に関する微妙な問題を解決します。問題の核心は、コンパイラがnil
リテラルを内部で処理し、特にデバッグ情報やエラーメッセージのためにフォーマットする際に、型推論によって一時的に特定の型が付与されたnil
を、その「装飾された」状態のまま扱ってしまうことにありました。
具体的には、src/cmd/gc/fmt.c
内のexprfmt
関数が変更されました。この関数は、コンパイラがASTノードを人間が読める形式に変換する際に使用されます。OLITERAL
(リテラルノード)を処理する部分で、以下の新しいロジックが追加されました。
if(n->val.ctype == CTNIL)
n = n->orig; // if this node was a nil decorated with at type, print the original naked nil
このコードは、現在処理しているノードn
がnil
定数(n->val.ctype == CTNIL
)であるかどうかをチェックします。もしそうであれば、n = n->orig
という行が実行されます。ここでn->orig
は、現在のノードが型推論などの過程で変換される前の、元のノードを指します。つまり、もしnil
リテラルが型推論によって一時的に特定の型を持つように「装飾」されていた場合、この修正によってその「装飾」が取り除かれ、フォーマット処理が元の「型を持たないnil
」に対して行われるようになります。
これにより、コンパイラの内部処理やエラー報告において、nil
が誤った型を持つかのように扱われることを防ぎます。例えば、nil
が特定のポインタ型として推論されたとしても、デバッグ出力やエラーメッセージでは、そのポインタ型ではなく、単にnil
として表示されるようになります。これは、Issue 2678
で報告されたような、nil
が特定の型を持つポインタとして扱われた際に、その型が関数ではないにもかかわらず関数呼び出しを試みるような誤った型チェックエラーを防ぐ上で重要です。
この修正は、コンパイラの内部的な整合性を保ち、nil
のセマンティクスを正確に反映させることで、より堅牢な型チェックと正確なエラー報告を実現します。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下のファイルで行われました。
-
src/cmd/gc/fmt.c
:exprfmt
関数内のOLITERAL
ケースに以下のコードが追加されました。
コメントも「this is still a bit of a mess」から「this is a bit of a mess」に微修正されています。--- a/src/cmd/gc/fmt.c +++ b/src/cmd/gc/fmt.c @@ -1072,9 +1072,11 @@ exprfmt(Fmt *f, Node *n, int prec) case OREGISTER: return fmtprint(f, "%R", n->val.u.reg); - case OLITERAL: // this is still a bit of a mess + case OLITERAL: // this is a bit of a mess \tif(fmtmode == FErr && n->sym != S) \t\treturn fmtprint(f, "%S", n->sym); +\t\tif(n->val.ctype == CTNIL) +\t\t\tn = n->orig; // if this node was a nil decorated with at type, print the original naked nil \tif(n->type != types[n->type->etype] && n->type != idealbool && n->type != idealstring) { \t\tif(isptr[n->type->etype]) \t\t\treturn fmtprint(f, "(%T)(%V)", n->type, &n->val);
-
test/fixedbugs/bug392.dir/one.go
(新規追加):package one type file int func (file *file) isnil() bool { return file == nil } func (fil *file) isnil2() bool { return fil == nil }
このファイルは、
file
というカスタム型と、そのポインタレシーバを持つメソッドを定義しています。これらのメソッド内でnil
との比較が行われています。 -
test/fixedbugs/bug392.dir/two.go
(新規追加):package two import _ "./one"
このファイルは、
one.go
パッケージをインポートしています。インポート時に型チェックがトリガーされることで、バグが顕在化するシナリオを構築しています。 -
test/fixedbugs/bug392.go
(新規追加):// $G $D/$F.dir/one.go && $G -ll $D/$F.dir/two.go // Copyright 2011 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 2678 // -ll flag in command above is to force typecheck on import, needed to trigger the bug. // fixedbugs/bug392.dir/two.go:3: cannot call non-function *one.file (type one.file) package ignored
このファイルは、バグを再現するためのテストスクリプトです。
$G -ll
というコンパイルフラグを使用することで、インポート時の型チェックを強制し、Issue 2678
で報告された「*one.file
型が関数ではないのに呼び出そうとする」というエラーを再現するように設計されています。
コアとなるコードの解説
src/cmd/gc/fmt.c
における変更は、Goコンパイラの内部でnil
リテラルがどのように表現され、フォーマットされるかという、非常に低レベルな部分に影響を与えます。
追加されたif(n->val.ctype == CTNIL) n = n->orig;
という行は、exprfmt
関数がOLITERAL
ノード(リテラルを表す抽象構文木のノード)を処理する際に、そのリテラルがnil
である場合に特別な処理を行うことを意味します。
n->val.ctype == CTNIL
: これは、現在処理しているリテラルノードn
がnil
定数であることを確認する条件です。n = n->orig;
: この行がこの修正の核心です。もしnil
リテラルが、型推論などのコンパイラ内部の変換プロセスによって、一時的に特定の型を持つように「装飾」されていた場合、n->orig
はその「装飾」が施される前の、元の(型を持たない)nil
リテラルノードを指します。この行によって、フォーマット処理の対象となるノードが、元の「裸のnil
」に戻されます。
この変更の目的は、コンパイラの内部的なデバッグ出力やエラーメッセージにおいて、nil
リテラルがその本来のセマンティクス(型を持たないゼロ値)で表現されることを保証することです。もしこの修正がなければ、型推論によって特定の型が付与されたnil
が、その型情報を含んだままフォーマットされてしまい、結果として誤解を招くデバッグ情報や、Issue 2678
のような誤った型チェックエラー(例: 非関数型を関数として呼び出そうとするエラー)を引き起こす可能性がありました。
新規追加されたテストケースは、このバグがどのように発生するかを具体的に示しています。one.go
とtwo.go
の組み合わせ、そしてbug392.go
で指定された-ll
フラグは、Goコンパイラがインポート時にnil
の型推論を誤って処理する特定のシナリオを再現します。この修正が適用されることで、これらのテストがパスするようになり、Goコンパイラがnil
リテラルを、その型推論の有無にかかわらず、常に正しく処理できるようになったことを検証しています。
関連リンク
- Go Gerrit Change-ID: https://golang.org/cl/5529066 このコミットの元のコードレビューページです。Goプロジェクトでは、GitHubにプッシュされる前にGerritというコードレビューシステムが使用されていました。
- Issue 2678:
コミットメッセージで参照されているバグトラッカーのイシュー番号です。このイシューは、Goコンパイラにおける
nil
リテラルの型推論とフォーマットに関する問題を報告していました。
参考にした情報源リンク
- https://github.com/golang/go/commit/feaa9ed10aa369d27dcf5a69863c481f4875bd39
- Go言語の公式ドキュメント(
nil
、コンパイラ、型推論に関する一般的な情報) - Goコンパイラのソースコード(
src/cmd/gc/
ディレクトリ内のファイル構造と関数)