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

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

このコミットは、Goコンパイラ(cmd/gc)において、ブランク識別子(_)に対するセレクタの使用を禁止する変更を導入しています。これにより、Go言語の仕様に沿った、より厳密な型チェックが実現されます。具体的には、構造体のフィールドやメソッドとしてブランク識別子を参照しようとした場合に、コンパイルエラーを発生させるようになります。

コミット

commit b65acaeab24b1d93a765cbd0c53d1b6a0d7bb496
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Mon Mar 4 17:01:42 2013 +0100

    cmd/gc: disallow selectors to the blank identifier
    
    Fixes #4941.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7415051

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

https://github.com/golang/go/commit/b65acaeab24b1d93a765cbd0c53d1b6a0d7bb496

元コミット内容

cmd/gc: disallow selectors to the blank identifier

Fixes #4941.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7415051

変更の背景

この変更は、Go言語のIssue #4941「_ as a field name should not be selectable」を修正するために行われました。Go言語において、ブランク識別子(_)は「値を破棄する」または「未使用の変数を宣言する」といった特殊な意味を持ちます。しかし、Go 1.0のリリース時点では、構造体のフィールド名としてブランク識別子を使用した場合に、そのフィールドにセレクタ(.演算子)を使ってアクセスできてしまうという、言語の意図に反する挙動が存在していました。

例えば、struct { _ int } のような構造体があった場合、t._ のようにアクセスできてしまうと、ブランク識別子の本来の目的(無視されるべきもの)と矛盾が生じます。この挙動は、コンパイラがブランク識別子を通常の識別子と同様に扱ってしまっていたために発生していました。このコミットは、この矛盾を解消し、Go言語の設計思想により忠実なコンパイラの挙動を実現することを目的としています。

前提知識の解説

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

Go言語におけるブランク識別子(_)は、特別な意味を持つ予約済みの識別子です。主に以下の用途で使用されます。

  1. 値の破棄: 関数の戻り値や多値代入において、特定の値を無視したい場合に使用します。
    _, err := someFunction() // errだけが必要で、最初の戻り値は不要な場合
    
  2. 未使用のインポート: パッケージをインポートする際に、そのパッケージの初期化処理(init関数)だけを実行し、パッケージ内の識別子を直接使用しない場合に使用します。
    import _ "net/http/pprof" // pprofの初期化だけが必要な場合
    
  3. 未使用の変数: 変数を宣言したが、その変数をコード内で使用しない場合に、コンパイラのエラー(declared and not used)を回避するために使用します。
    var _ int = 10 // 変数`_`は使用されないが、エラーを回避
    
  4. インターフェースの実装確認: 型が特定のインターフェースを実装していることをコンパイル時に確認するために使用します。
    var _ io.Reader = (*MyReader)(nil)
    
  5. 構造体フィールド名: 構造体のフィールド名として使用されることもありますが、この場合もそのフィールドは「無視されるべき」という意図が込められています。

ブランク識別子は、その性質上、通常の識別子のように参照したり、値を代入したりすることはできません。このコミットは、特に「構造体フィールド名としてのブランク識別子」に対するセレクタアクセスを禁止することで、この原則を徹底しています。

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

Goコンパイラは、ソースコードを解析し、Go言語の仕様に準拠しているかを確認する「型チェック」のフェーズを持っています。cmd/gcはGo言語の公式コンパイラであり、そのソースコードはC言語で書かれています(現在はGo言語に移行済み)。

型チェックのプロセスでは、AST(抽象構文木)を走査し、各ノードの型が正しいか、操作が有効かなどを検証します。セレクタ(.演算子)によるフィールドやメソッドへのアクセスも、この型チェックの段階で検証されます。具体的には、セレクタの左側の式が構造体やポインタ型であるか、右側の識別子がその型に存在するフィールドやメソッドであるかなどが確認されます。

このコミットで変更されたsrc/cmd/gc/typecheck.cは、この型チェックのロジックの一部を担っており、特にセレクタの解決(ODOTODOTPTRノードの処理)に関連する部分です。

技術的詳細

このコミットの技術的な核心は、Goコンパイラの型チェックフェーズにおいて、セレクタの右辺がブランク識別子である場合にエラーを発生させるロジックを追加した点にあります。

変更はsrc/cmd/gc/typecheck.cファイル内のtypecheck関数のODOT(セレクタアクセス)を処理する部分で行われています。

元のコードでは、セレクタの右辺(n->right)が通常の識別子であるかのように、そのシンボルを解決しようとしていました。しかし、ブランク識別子は通常のシンボルテーブルには登録されず、特殊な扱いを受けるべきものです。

追加されたコードは以下の通りです。

		if(isblank(n->right)) {
			yyerror("cannot refer to blank field or method");
			goto error;
		}

このコードは、セレクタの右辺(n->right)がブランク識別子であるかどうかをisblank関数でチェックします。isblank関数は、Goコンパイラ内部でブランク識別子を判定するためのユーティリティ関数です。

もしn->rightがブランク識別子であると判定された場合、yyerror関数が呼び出され、「cannot refer to blank field or method」(ブランクフィールドまたはメソッドを参照できません)というコンパイルエラーメッセージが出力されます。その後、goto error;によってエラー処理フローに移行し、コンパイルが中断されます。

この変更により、t._のようなコードがコンパイル時に捕捉され、Go言語のセマンティクスに合致しない不正なアクセスが防止されるようになりました。

また、test/blank1.goというテストファイルが追加され、この新しい制約が正しく機能することを確認しています。このテストファイルには、_ = t._という行が含まれており、この行がコンパイルエラーとなることを期待しています。

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

src/cmd/gc/typecheck.c

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -761,6 +761,10 @@ reswitch:
 		t->type = n->left->type;
 		if(t->type->etype == Tptr) {
 			n->op = ODOTPTR;
 			checkwidth(t);
 		}
+		if(isblank(n->right)) {
+			yyerror("cannot refer to blank field or method");
+			goto error;
+		}
 		if(!lookdot(n, t, 0)) {
 			if(lookdot(n, t, 1))
 				yyerror("%N undefined (cannot refer to unexported field or method %S)\", n, n->right->sym);

test/blank1.go

--- a/test/blank1.go
+++ b/test/blank1.go
@@ -9,8 +9,13 @@
 
 package _	// ERROR "invalid package name _"
 
+var t struct {
+	_ int
+}
+
 func main() {
 	_()\t// ERROR "cannot use _ as value"\n
 	x := _+1\t// ERROR "cannot use _ as value"\n
 	_ = x
+\t_ = t._ // ERROR "cannot refer to blank field"\n
 }

コアとなるコードの解説

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

typecheck.cの変更は、ODOT(セレクタアクセス)ノードを処理する部分に新しいチェックを追加しています。

		if(isblank(n->right)) {
			yyerror("cannot refer to blank field or method");
			goto error;
		}
  • n->right: これはセレクタの右辺、つまりアクセスしようとしているフィールド名またはメソッド名を表すASTノードです。
  • isblank(n->right): この関数は、n->rightがブランク識別子(_)を表すノードであるかどうかを判定します。Goコンパイラ内部では、ブランク識別子には特定のフラグやシンボルが割り当てられており、この関数はそのチェックを行います。
  • yyerror("cannot refer to blank field or method");: もしisblankが真を返した場合、この行が実行され、指定されたエラーメッセージがコンパイラの標準エラー出力に表示されます。yyerrorは、Goコンパイラがエラーメッセージを出力するために使用する内部関数です。
  • goto error;: エラーが検出されたため、現在の型チェック処理を中断し、エラー処理のラベルにジャンプします。これにより、不正なコードがコンパイルされるのを防ぎます。

この変更により、コンパイラはセレクタの右辺がブランク識別子である場合に、明示的にエラーを報告するようになります。

test/blank1.go の変更

test/blank1.goは、この新しいコンパイラの挙動を検証するためのテストケースです。

+var t struct {
+	_ int
+}
+
 func main() {
 	_()\t// ERROR "cannot use _ as value"\n
 	x := _+1\t// ERROR "cannot use _ as value"\n
 	_ = x
+\t_ = t._ // ERROR "cannot refer to blank field"\n
 }
  • var t struct { _ int }: _という名前のint型フィールドを持つ匿名構造体tを宣言しています。
  • _ = t._ // ERROR "cannot refer to blank field": この行が追加されたテストケースの核心です。tのブランクフィールドにセレクタを使ってアクセスしようとしています。コメントの// ERROR "..."は、この行がコンパイル時に指定されたエラーメッセージを発生させることを期待していることを示しています。このテストが成功するためには、コンパイラが「cannot refer to blank field」というエラーを出力し、コンパイルが失敗する必要があります。

このテストケースは、typecheck.cの変更が意図通りに機能し、ブランク識別子へのセレクタアクセスが正しく禁止されることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語のブランク識別子に関する公式ドキュメントやブログ記事 (一般的なGo言語の知識として参照)
  • Goコンパイラのソースコード(cmd/gc)の構造に関する情報 (一般的なコンパイラの知識として参照)
  • Go言語のIssueトラッカー (Issue #4941の詳細を確認するため)
  • Go Code Review Comments (CL 7415051の詳細を確認するため)

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

このコミットは、Goコンパイラ(cmd/gc)において、ブランク識別子(_)に対するセレクタの使用を禁止する変更を導入しています。これにより、Go言語の仕様に沿った、より厳密な型チェックが実現されます。具体的には、構造体のフィールドやメソッドとしてブランク識別子を参照しようとした場合に、コンパイルエラーを発生させるようになります。

コミット

commit b65acaeab24b1d93a765cbd0c53d1b6a0d7bb496
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Mon Mar 4 17:01:42 2013 +0100

    cmd/gc: disallow selectors to the blank identifier
    
    Fixes #4941.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7415051

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

https://github.com/golang/go/commit/b65acaeab24b1d93a765cbd0c53d1b6a0d7bb496

元コミット内容

cmd/gc: disallow selectors to the blank identifier

Fixes #4941.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7415051

変更の背景

この変更は、Go言語のIssue #4941「_ as a field name should not be selectable」を修正するために行われました。Go言語において、ブランク識別子(_)は「値を破棄する」または「未使用の変数を宣言する」といった特殊な意味を持ちます。しかし、Go 1.0のリリース時点では、構造体のフィールド名としてブランク識別子を使用した場合に、そのフィールドにセレクタ(.演算子)を使ってアクセスできてしまうという、言語の意図に反する挙動が存在していました。

例えば、struct { _ int } のような構造体があった場合、t._ のようにアクセスできてしまうと、ブランク識別子の本来の目的(無視されるべきもの)と矛盾が生じます。この挙動は、コンパイラがブランク識別子を通常の識別子と同様に扱ってしまっていたために発生していました。このコミットは、この矛盾を解消し、Go言語の設計思想により忠実なコンパイラの挙動を実現することを目的としています。

なお、Goの公式GitHubリポジトリではIssue #4941が見つかりませんでしたが、これはコミットが2013年と古いため、当時のIssueトラッカーの番号付けや管理方法が現在と異なっていた可能性、あるいは内部的なIssue番号であった可能性が考えられます。

前提知識の解説

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

Go言語におけるブランク識別子(_)は、特別な意味を持つ予約済みの識別子です。主に以下の用途で使用されます。

  1. 値の破棄: 関数の戻り値や多値代入において、特定の値を無視したい場合に使用します。
    _, err := someFunction() // errだけが必要で、最初の戻り値は不要な場合
    
  2. 未使用のインポート: パッケージをインポートする際に、そのパッケージの初期化処理(init関数)だけを実行し、パッケージ内の識別子を直接使用しない場合に使用します。
    import _ "net/http/pprof" // pprofの初期化だけが必要な場合
    
  3. 未使用の変数: 変数を宣言したが、その変数をコード内で使用しない場合に、コンパイラのエラー(declared and not used)を回避するために使用します。
    var _ int = 10 // 変数`_`は使用されないが、エラーを回避
    
  4. インターフェースの実装確認: 型が特定のインターフェースを実装していることをコンパイル時に確認するために使用します。
    var _ io.Reader = (*MyReader)(nil)
    
  5. 構造体フィールド名: 構造体のフィールド名として使用されることもありますが、この場合もそのフィールドは「無視されるべき」という意図が込められています。

ブランク識別子は、その性質上、通常の識別子のように参照したり、値を代入したりすることはできません。このコミットは、特に「構造体フィールド名としてのブランク識別子」に対するセレクタアクセスを禁止することで、この原則を徹底しています。

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

Goコンパイラは、ソースコードを解析し、Go言語の仕様に準拠しているかを確認する「型チェック」のフェーズを持っています。cmd/gcはGo言語の公式コンパイラであり、そのソースコードはC言語で書かれています(現在はGo言語に移行済み)。

型チェックのプロセスでは、AST(抽象構文木)を走査し、各ノードの型が正しいか、操作が有効かなどを検証します。セレクタ(.演算子)によるフィールドやメソッドへのアクセスも、この型チェックの段階で検証されます。具体的には、セレクタの左側の式が構造体やポインタ型であるか、右側の識別子がその型に存在するフィールドやメソッドであるかなどが確認されます。

このコミットで変更されたsrc/cmd/gc/typecheck.cは、この型チェックのロジックの一部を担っており、特にセレクタの解決(ODOTODOTPTRノードの処理)に関連する部分です。

技術的詳細

このコミットの技術的な核心は、Goコンパイラの型チェックフェーズにおいて、セレクタの右辺がブランク識別子である場合にエラーを発生させるロジックを追加した点にあります。

変更はsrc/cmd/gc/typecheck.cファイル内のtypecheck関数のODOT(セレクタアクセス)を処理する部分で行われています。

元のコードでは、セレクタの右辺(n->right)が通常の識別子であるかのように、そのシンボルを解決しようとしていました。しかし、ブランク識別子は通常のシンボルテーブルには登録されず、特殊な扱いを受けるべきものです。

追加されたコードは以下の通りです。

		if(isblank(n->right)) {
			yyerror("cannot refer to blank field or method");
			goto error;
		}

このコードは、セレクタの右辺(n->right)がブランク識別子であるかどうかをisblank関数でチェックします。isblank関数は、Goコンパイラ内部でブランク識別子を判定するためのユーティリティ関数です。

もしn->rightがブランク識別子であると判定された場合、yyerror関数が呼び出され、「cannot refer to blank field or method」(ブランクフィールドまたはメソッドを参照できません)というコンパイルエラーメッセージが出力されます。その後、goto error;によってエラー処理フローに移行し、コンパイルが中断されます。

この変更により、t._のようなコードがコンパイル時に捕捉され、Go言語のセマンティクスに合致しない不正なアクセスが防止されるようになりました。

また、test/blank1.goというテストファイルが追加され、この新しい制約が正しく機能することを確認しています。このテストファイルには、_ = t._という行が含まれており、この行がコンパイルエラーとなることを期待しています。

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

src/cmd/gc/typecheck.c

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -761,6 +761,10 @@ reswitch:
 		t->type = n->left->type;
 		if(t->type->etype == Tptr) {
 			n->op = ODOTPTR;
 			checkwidth(t);
 		}
+		if(isblank(n->right)) {
+			yyerror("cannot refer to blank field or method");
+			goto error;
+		}
 		if(!lookdot(n, t, 0)) {
 			if(lookdot(n, t, 1))
 				yyerror("%N undefined (cannot refer to unexported field or method %S)\", n, n->right->sym);

test/blank1.go

--- a/test/blank1.go
+++ b/test/blank1.go
@@ -9,8 +9,13 @@
 
 package _	// ERROR "invalid package name _"
 
+var t struct {
+	_ int
+}
+
 func main() {
 	_()\t// ERROR "cannot use _ as value"\n
 	x := _+1\t// ERROR "cannot use _ as value"\n
 	_ = x
+\t_ = t._ // ERROR "cannot refer to blank field"\n
 }

コアとなるコードの解説

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

typecheck.cの変更は、ODOT(セレクタアクセス)ノードを処理する部分に新しいチェックを追加しています。

		if(isblank(n->right)) {
			yyerror("cannot refer to blank field or method");
			goto error;
		}
  • n->right: これはセレクタの右辺、つまりアクセスしようとしているフィールド名またはメソッド名を表すASTノードです。
  • isblank(n->right): この関数は、n->rightがブランク識別子(_)を表すノードであるかどうかを判定します。Goコンパイラ内部では、ブランク識別子には特定のフラグやシンボルが割り当てられており、この関数はそのチェックを行います。
  • yyerror("cannot refer to blank field or method");: もしisblankが真を返した場合、この行が実行され、指定されたエラーメッセージがコンパイラの標準エラー出力に表示されます。yyerrorは、Goコンパイラがエラーメッセージを出力するために使用する内部関数です。
  • goto error;: エラーが検出されたため、現在の型チェック処理を中断し、エラー処理のラベルにジャンプします。これにより、不正なコードがコンパイルされるのを防ぎます。

この変更により、コンパイラはセレクタの右辺がブランク識別子である場合に、明示的にエラーを報告するようになります。

test/blank1.go の変更

test/blank1.goは、この新しいコンパイラの挙動を検証するためのテストケースです。

+var t struct {
+	_ int
+}
+
 func main() {
 	_()\t// ERROR "cannot use _ as value"\n
 	x := _+1\t// ERROR "cannot use _ as value"\n
 	_ = x
+\t_ = t._ // ERROR "cannot refer to blank field"\n
 }
  • var t struct { _ int }: _という名前のint型フィールドを持つ匿名構造体tを宣言しています。
  • _ = t._ // ERROR "cannot refer to blank field": この行が追加されたテストケースの核心です。tのブランクフィールドにセレクタを使ってアクセスしようとしています。コメントの// ERROR "..."は、この行がコンパイル時に指定されたエラーメッセージを発生させることを期待していることを示しています。このテストが成功するためには、コンパイラが「cannot refer to blank field」というエラーを出力し、コンパイルが失敗する必要があります。

このテストケースは、typecheck.cの変更が意図通りに機能し、ブランク識別子へのセレクタアクセスが正しく禁止されることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語のブランク識別子に関する公式ドキュメントやブログ記事 (一般的なGo言語の知識として参照)
  • Goコンパイラのソースコード(cmd/gc)の構造に関する情報 (一般的なコンパイラの知識として参照)
  • Go Code Review Comments (CL 7415051の詳細を確認するため)