Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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は、他の言語のnullnullptrに相当しますが、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->diag0の場合に真となり、エラーメッセージがまだ報告されていないことを確認します。
  • エラーメッセージが報告された後、n->diag = 1;が実行されます。これにより、このノードに対してエラーが報告済みであることがマークされ、defaultlit関数が再度このノードを処理しようとしても、同じ「use of untyped nil」エラーが重複して報告されることはなくなります。

この変更は、コンパイラが同じ論理的なエラーに対して複数のエラーメッセージを出力するのを防ぐための、一般的なエラー報告のベストプラクティスを適用したものです。これにより、コンパイラの出力がより簡潔になり、開発者が本当に重要なエラーに集中できるようになります。

関連リンク

参考にした情報源リンク