[インデックス 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/ディレクトリ内のファイル構造と関数)