[インデックス 18970] ファイルの概要
コミット
このコミットは、Goコンパイラ(cmd/gc
)における「untyped nilの使用」に関する誤ったエラー報告(spurious error)を修正するものです。具体的には、nil
が型付けされていない状態で使用された際に、コンパイラが同じ問題に対して複数回エラーを報告してしまうバグを解決します。この修正により、コンパイラは一度報告したエラーについては再報告しないようになり、より正確で簡潔なエラーメッセージを提供できるようになります。
GitHub上でのコミットページへのリンク
https://github.com.com/golang/go/commit/21b2e168424ee28bd20f7886ab53f68513857fa2
元コミット内容
cmd/gc: fix spurious 'use of untyped nil' error
Fixes #6402
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/81340044
変更の背景
Go言語では、nil
は特定の型を持たない「untyped nil」として扱われることがあります。これは、インターフェース、マップ、スライス、チャネル、関数、ポインタなど、様々な参照型のゼロ値として使用されます。しかし、nil
を型が明確でないコンテキストで使用しようとすると、コンパイラは「use of untyped nil」エラーを報告します。
このコミットが修正する問題は、コンパイラがこの「use of untyped nil」エラーを、同じコード上の同じ問題に対して複数回報告してしまうというものでした。これは、コンパイラの内部処理において、エラーが報告されたノード(ASTノードなど)に対して、既にエラーが報告済みであることを示すフラグが適切に設定されていなかったためと考えられます。結果として、開発者は同じエラーメッセージが繰り返し表示されることで混乱し、問題の特定や修正が困難になる可能性がありました。
このバグはGoのIssue 6402として報告されており、このコミットはその問題を解決するために作成されました。
前提知識の解説
Goコンパイラ (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラです。Goのソースコードを解析し、抽象構文木(AST)を構築し、型チェック、最適化、そして最終的に実行可能なバイナリコードを生成する役割を担っています。エラー報告もコンパイラの重要な機能の一つです。
nil
と「untyped nil」
Go言語におけるnil
は、他の言語のnull
やnullptr
に相当しますが、Goのnil
は特定の型を持たないという特徴があります。これを「untyped nil」と呼びます。nil
は、以下のような参照型のゼロ値として使用されます。
- インターフェース (interface): インターフェース型の変数がどの具象値も保持していない状態。
- マップ (map): 初期化されていないマップ。
- スライス (slice): 初期化されていないスライス。
- チャネル (channel): 初期化されていないチャネル。
- 関数 (func): 関数リテラルが
nil
である状態。 - ポインタ (pointer): ポインタが何も指していない状態。
nil
は、そのコンテキストから型が推論できる場合にのみ使用できます。例えば、var p *int = nil
のように明示的に型が指定されている場合や、if x == nil
のように比較演算子で型が推論できる場合です。しかし、return nil
のように、nil
がどのような型として扱われるべきかコンパイラが判断できない場合、コンパイラは「use of untyped nil」エラーを報告します。
yyerror
関数
yyerror
は、コンパイラやパーサーの文脈でよく見られるエラー報告関数です。通常、構文解析中やセマンティック解析中にエラーが検出された際に呼び出され、エラーメッセージを標準エラー出力などに表示します。この関数は、エラーが発生した行番号などの情報も合わせて報告することが一般的です。
抽象構文木 (AST) とノード (Node
)
コンパイラは、ソースコードを解析する際に、その構造を抽象構文木(Abstract Syntax Tree, AST)として内部的に表現します。ASTは、プログラムの構造を木構造で表現したもので、各ノードはプログラムの要素(変数、関数呼び出し、演算子など)に対応します。コンパイラはASTを走査しながら型チェックやコード生成を行います。このコミットの文脈では、n
はおそらくASTのノードを指しており、そのノードに関連するエラーが報告される際にn->diag
というフラグが使用されています。
技術的詳細
このコミットの技術的な核心は、コンパイラが同じエラーを複数回報告するのを防ぐためのシンプルなフラグ管理にあります。
Goコンパイラのsrc/cmd/gc/const.c
ファイルは、定数やリテラル、特にnil
の型チェックと処理に関連するロジックを含んでいます。問題の箇所はdefaultlit
関数内にあり、この関数はリテラル値のデフォルト型を決定する役割を担っています。
以前のコードでは、nil
が型付けされていない状態で使用された場合(n->val.ctype == CTNIL
の条件が真の場合)、yyerror("use of untyped nil");
が直接呼び出されていました。このyyerror
の呼び出しは、特定のASTノードn
に対してエラーを報告します。しかし、コンパイラの処理フローによっては、同じnil
の使用箇所(つまり同じASTノードn
)に対して、defaultlit
関数が複数回呼び出される可能性がありました。その結果、同じエラーメッセージが複数回出力されてしまうという「spurious error」が発生していました。
この修正では、yyerror
の呼び出しをif(!n->diag)
という条件文で囲み、エラーが報告された直後にn->diag = 1;
を設定しています。
n->diag
: これは、ASTノードn
に関連付けられた診断フラグ(diagnostic flag)であると推測されます。0
(または未設定)はまだ診断メッセージが報告されていないことを意味し、1
は既に報告済みであることを意味します。if(!n->diag)
: この条件は、「もしこのノードn
に対してまだ診断メッセージが報告されていなければ」という意味になります。n->diag = 1;
: エラーが報告された後、このフラグを1
に設定することで、以降同じノードに対してdefaultlit
関数が呼び出されても、二度と同じエラーが報告されないようにします。
この変更は、コンパイラのエラー報告メカニズムを改善し、開発者にとってよりクリーンで理解しやすいエラー出力を提供することを目的としています。
コアとなるコードの変更箇所
変更は主にsrc/cmd/gc/const.c
ファイル内のdefaultlit
関数にあります。
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -1144,7 +1144,10 @@ defaultlit(Node **np, Type *t)
}
if(n->val.ctype == CTNIL) {
lineno = lno;
-\t\t\tyyerror(\"use of untyped nil\");
+\t\t\tif(!n->diag) {
+\t\t\t\tyyerror(\"use of untyped nil\");
+\t\t\t\tn->diag = 1;
+\t\t\t}
\t\t\tn->type = T;
\t\t\tbreak;
\t\t}
また、この修正を検証するための新しいテストケースがtest/fixedbugs/issue6402.go
として追加されています。
// 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.
// Issue 6402: spurious 'use of untyped nil' error
package p
func f() uintptr {
return nil // ERROR "cannot use nil as type uintptr in return argument"
}
このテストは、uintptr
型を返す関数でnil
を返そうとする不正なコードを含んでいます。このコードは「cannot use nil as type uintptr in return argument」というエラーを発生させるべきであり、このコミットの修正前には、これに加えて「use of untyped nil」という誤ったエラーが複数回報告されていたと考えられます。修正後は、期待されるエラーのみが一度だけ報告されるようになります。
コアとなるコードの解説
src/cmd/gc/const.c
の変更は、defaultlit
関数内でnil
リテラル(n->val.ctype == CTNIL
)が処理される部分にあります。
元のコードでは、nil
が型付けされていない状態で使用された場合、即座にyyerror("use of untyped nil");
が呼び出されていました。これは、コンパイラがnil
の型を推論できない状況で、その使用が不正であることを示すためのエラーです。
修正後のコードでは、このyyerror
の呼び出しがif(!n->diag)
ブロック内に移動されました。
n
は、現在処理中の抽象構文木(AST)のノードを指します。このノードは、問題のnil
リテラルに対応しています。n->diag
は、このノードに対して既に診断メッセージ(エラーや警告)が報告されたかどうかを示すフラグです。初期状態では0
(または未設定)であり、まだ報告されていないことを意味します。if(!n->diag)
は、n->diag
が0
の場合に真となり、エラーメッセージがまだ報告されていないことを確認します。- エラーメッセージが報告された後、
n->diag = 1;
が実行されます。これにより、このノードに対してエラーが報告済みであることがマークされ、defaultlit
関数が再度このノードを処理しようとしても、同じ「use of untyped nil」エラーが重複して報告されることはなくなります。
この変更は、コンパイラが同じ論理的なエラーに対して複数のエラーメッセージを出力するのを防ぐための、一般的なエラー報告のベストプラクティスを適用したものです。これにより、コンパイラの出力がより簡潔になり、開発者が本当に重要なエラーに集中できるようになります。
関連リンク
- Go Issue 6402: https://github.com/golang/go/issues/6402 (このコミットが修正したバグの報告)
- Go Code Review CL 81340044: https://golang.org/cl/81340044 (このコミットのコードレビューページ)
参考にした情報源リンク
- Go言語の公式ドキュメント (nilについて): https://go.dev/doc/effective_go#nil
- Goコンパイラのソースコード (src/cmd/gc): https://github.com/golang/go/tree/master/src/cmd/gc
- Go言語のASTパッケージ: https://pkg.go.dev/go/ast (ASTノードの概念理解のため)