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

[インデックス 17511] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)において、_ = nil のような「型付けされていない nil の使用」に対する診断メッセージを改善することを目的としています。具体的には、ブランク識別子 (_) への nil の代入が、より適切なコンパイルエラーとして報告されるようになります。これにより、開発者はコードの潜在的な問題を早期に発見し、より堅牢なGoプログラムを作成できるようになります。

コミット

commit 903c2fda18825f1f8b276c92325f84f52bc71071
Author: Russ Cox <rsc@golang.org>
Date:   Mon Sep 9 12:49:39 2013 -0400

    cmd/gc: diagnose '_ = nil' better
    
    Fixes #6004.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/13616044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/903c2fda18825f1f8b276c92325f84f52bc71071

元コミット内容

cmd/gc: diagnose '_ = nil' better

Fixes #6004.

R=ken2
CC=golang-dev
https://golang.org/cl/13616044

変更の背景

このコミットは、Go言語のIssue 6004を修正するために行われました。Go言語では、nil は特定の型を持たない値であり、ポインタ、スライス、マップ、チャネル、インターフェース、関数などのゼロ値として使用されます。しかし、nil はそれ自体では型を持たないため、「型付けされていない nil (untyped nil)」と呼ばれます。

Goのコンパイラは、型付けされていない nil が使用される文脈で、その nil がどの型として扱われるべきかを推論しようとします。しかし、ブランク識別子 (_) は値を破棄するために使用され、特定の型を必要としません。そのため、_ = nil のようなコードは、コンパイラが nil の型を推論できない状況を生み出し、Goの型システムにおける曖昧さや潜在的なバグの原因となる可能性がありました。

以前のコンパイラでは、このようなケースに対して適切なエラーメッセージが出力されず、開発者が意図しない挙動に気づきにくいという問題がありました。このコミットは、_ = nil のようなコードが検出された際に、より明確な「use of untyped nil」というエラーメッセージを生成することで、コンパイラの診断能力を向上させ、開発者がより安全なコードを書けるようにすることを目的としています。

前提知識の解説

Go言語のブランク識別子 (_)

Go言語におけるブランク識別子 (_) は、変数を宣言したがその値を使用しない場合や、関数の戻り値の一部を破棄したい場合など、特定の値を無視するために使用される特別な識別子です。例えば、_, err := someFunc() のように、エラーだけを処理し、他の戻り値を無視する際に頻繁に利用されます。ブランク識別子に代入された値は破棄され、メモリも割り当てられません。

Go言語の nil

nil はGo言語におけるゼロ値の一つで、ポインタ、スライス、マップ、チャネル、インターフェース、関数といった参照型の「値がない」状態を表します。nil はそれ自体では具体的な型を持たず、文脈によってその型が決定されます。これを「型付けされていない nil (untyped nil)」と呼びます。例えば、var p *int = nil のように、型が明示的に指定されると nil はその型のゼロ値として扱われます。しかし、_ = nil のように型が推論できない文脈では、nil の型が曖昧になります。

Goコンパイラ (cmd/gc) の型チェック

Goコンパイラ (cmd/gc) は、ソースコードを解析し、Go言語の仕様に準拠しているかを確認する型チェックを行います。型チェックのプロセスでは、各変数の型、関数の引数と戻り値の型、そして代入や演算における型の互換性などが検証されます。型付けされていない nil の場合、コンパイラは代入先の型や使用される文脈から nil の具体的な型を推論しようとします。推論が不可能な場合や、型システムに矛盾が生じる場合には、コンパイルエラーを報告します。

技術的詳細

このコミットの技術的な核心は、Goコンパイラの型変換および代入処理を担当する部分に、型付けされていない nil がブランク識別子に代入されるケースを特別に診断するロジックを追加した点にあります。

変更が加えられたのは、src/cmd/gc/subr.c ファイル内の assignconv 関数です。この関数は、代入や型変換が行われる際に呼び出され、左辺と右辺の型の互換性をチェックし、必要に応じて型変換を行います。

追加されたコードは以下の条件をチェックします。

  1. 代入先の型 (t->etype) がブランク識別子 (TBLANK) であること。
  2. 代入元の型 (n->type->etype) が型付けされていない nil (TNIL) であること。

この両方の条件が真である場合、つまり「型付けされていない nil がブランク識別子に代入されようとしている」場合に、yyerror("use of untyped nil"); というエラーメッセージを出力します。yyerror はGoコンパイラがコンパイルエラーを報告するために使用する内部関数です。

この変更により、コンパイラは以前は黙認していた _ = nil のようなコードに対して、明確なエラーメッセージを出すようになり、開発者が意図しない nil の使用を特定しやすくなります。これは、Goの型安全性を高め、より堅牢なプログラム開発を促進する上で重要な改善です。

コアとなるコードの変更箇所

diff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index 2f617ac9d0..079ca305d5 100644
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -1411,6 +1411,9 @@ assignconv(Node *n, Type *t, char *context)\n 	if(n == N || n->type == T || n->type->broke)\n 		return n;\n \n+	if(t->etype == TBLANK && n->type->etype == TNIL)\n+		yyerror("use of untyped nil");\n+\n 	old = n;\n 	old->diag++;  // silence errors about n; we\'ll issue one below\n 	defaultlit(&n, t);\
diff --git a/test/fixedbugs/issue6004.go b/test/fixedbugs/issue6004.go
new file mode 100644
index 0000000000..45aaffd2c9
--- /dev/null
+++ b/test/fixedbugs/issue6004.go
@@ -0,0 +1,15 @@
+// errorcheck
+\n+// Copyright 2013 The Go Authors.  All rights reserved.\n+// Use of this source code is governed by a BSD-style\n+// license that can be found in the LICENSE file.\n+\n+package main\n+\n+func main() {\n+\t_ = nil // ERROR "use of untyped nil"\n+\t_, _ = nil, 1 // ERROR "use of untyped nil"\n+\t_, _ = 1, nil // ERROR "use of untyped nil"\n+\t_ = append(nil, 1, 2, 3) // ERROR "untyped nil"\n+}\n+\n

コアとなるコードの解説

src/cmd/gc/subr.c の変更

src/cmd/gc/subr.c は、Goコンパイラのバックエンド部分である gc におけるサブルーチンやユーティリティ関数を定義しているファイルです。このファイル内の assignconv 関数は、代入操作や型変換の際に呼び出され、左辺と右辺の型の整合性を確認し、必要に応じて型変換を行います。

追加された以下の3行がこのコミットの核心です。

	if(t->etype == TBLANK && n->type->etype == TNIL)
		yyerror("use of untyped nil");
  • t->etype == TBLANK: これは、代入の左辺(ターゲット)の型がブランク識別子 (_) であることをチェックしています。TBLANK はコンパイラ内部でブランク識別子を表す型定数です。
  • n->type->etype == TNIL: これは、代入の右辺(ソース)の型が型付けされていない nil であることをチェックしています。TNIL はコンパイラ内部で型付けされていない nil を表す型定数です。
  • yyerror("use of untyped nil");: 上記の2つの条件が同時に真である場合、つまり「型付けされていない nil がブランク識別子に代入されようとしている」場合に、"use of untyped nil" というエラーメッセージをコンパイラの標準エラー出力に報告します。yyerror はGoコンパイラが構文解析や型チェック中にエラーを検出した際に使用する関数です。

この変更により、コンパイラは _ = nil のようなコードパターンを明示的に検出し、開発者にその問題点を通知できるようになりました。

test/fixedbugs/issue6004.go の追加

このファイルは、Issue 6004で報告されたバグが修正されたことを検証するための回帰テストです。// errorcheck ディレクティブは、このファイルがコンパイル時に特定のエラーメッセージを生成することを期待していることをコンパイラに伝えます。

ファイル内の各行は、_ = nil の様々なバリエーションをテストしています。

  • _ = nil // ERROR "use of untyped nil": 最も基本的なケース。
  • _, _ = nil, 1 // ERROR "use of untyped nil": 複数の代入で nil が含まれるケース。
  • _, _ = 1, nil // ERROR "use of untyped nil": 複数の代入で nil が含まれるケース(順序違い)。
  • _ = append(nil, 1, 2, 3) // ERROR "untyped nil": append 関数の第一引数に型付けされていない nil が渡されるケース。append はスライスを操作する関数であり、第一引数にはスライス型が期待されます。型付けされていない nil はスライス型に変換可能ですが、ブランク識別子への代入という文脈では型が確定できないため、エラーとなります。

これらのテストケースは、assignconv 関数に追加されたロジックが正しく機能し、期待されるエラーメッセージが生成されることを保証します。

関連リンク

参考にした情報源リンク