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

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

このコミットは、Goコンパイラ(gc)における構造体リテラル(struct literal)のフィールド名表示に関するバグ修正です。具体的には、エクスポートされたインライン化されたコード内で、組み込み型が埋め込まれた構造体リテラルのフィールド名が不適切に修飾されてしまう問題を解決します。これにより、生成されるコードの正確性と可読性が向上します。

コミット

commit 6ff01f01f4e477a931d10c133f33bfe7e0c4ef15
Author: Luuk van Dijk <lvd@golang.org>
Date:   Wed Jan 18 17:51:28 2012 +0100

    gc: fieldnames in structliterals in exported inlines should not be qualified if they're embedded builtin types.
    
    Trust me.
    Fixes #2687.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5545047
---
 src/cmd/gc/fmt.c                 | 18 +++++++++++++++---
 test/fixedbugs/bug396.dir/one.go |  9 +++++++++
 test/fixedbugs/bug396.dir/two.go | 14 ++++++++++++++
 test/fixedbugs/bug396.go         |  7 +++++++
 4 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/src/cmd/gc/fmt.c b/src/cmd/gc/fmt.c
index 15466844be..6f2041c1c5 100644
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1062,6 +1062,7 @@ exprfmt(Fmt *f, Node *n, int prec)
 {
  	int nprec;
  	NodeList *l;
+	Type *t;
  
  	while(n && n->implicit)
  		n = n->left;
@@ -1160,11 +1161,22 @@ exprfmt(Fmt *f, Node *n, int prec)
  	case OSTRUCTLIT:
  		if (fmtmode == FExp) {   // requires special handling of field names
  			fmtprint(f, "%T{\", n->type);\n-\t\t\tfor(l=n->list; l; l=l->next)\n+\t\t\tfor(l=n->list; l; l=l->next) {\n+\t\t\t\t// another special case: if n->left is an embedded field of builtin type,\n+\t\t\t\t// it needs to be non-qualified.  Can't figure that out in %S, so do it here\n+\t\t\t\tif(l->n->left->type->embedded) {\n+\t\t\t\t\tt = l->n->left->type->type;\n+\t\t\t\t\tif(t->sym == S)\n+\t\t\t\t\t\tt = t->type;\n+\t\t\t\t\tfmtprint(f, \" %T:%N\", t, l->n->right);\n+\t\t\t\t} else\n+\t\t\t\t\tfmtprint(f, \" %hhS:%N\", l->n->left->sym, l->n->right);\n+\n  	\t\t\tif(l->next)\n-\t\t\t\t\tfmtprint(f, \" %hhS:%N,\", l->n->left->sym, l->n->right);\n+\t\t\t\t\tfmtstrcpy(f, \",\");\n  	\t\t\telse\n-\t\t\t\t\tfmtprint(f, \" %hhS:%N \", l->n->left->sym, l->n->right);\n+\t\t\t\t\tfmtstrcpy(f, \" \");\n+\t\t\t}\n  	\t\treturn fmtstrcpy(f, \"}\");\n  		}\n  		// fallthrough
diff --git a/test/fixedbugs/bug396.dir/one.go b/test/fixedbugs/bug396.dir/one.go
new file mode 100644
index 0000000000..7902a07d53
--- /dev/null
+++ b/test/fixedbugs/bug396.dir/one.go
@@ -0,0 +1,9 @@
+// Copyright 2012 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 one\n+\n+type T struct { int }\n+\n+func New(i int) T { return T{i} }\ndiff --git a/test/fixedbugs/bug396.dir/two.go b/test/fixedbugs/bug396.dir/two.go
new file mode 100644
index 0000000000..9b32508fd4
--- /dev/null
+++ b/test/fixedbugs/bug396.dir/two.go
@@ -0,0 +1,14 @@
+// Copyright 2012 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+// Use the functions in one.go so that the inlined\n+// forms get type-checked.\n+\n+package two\n+\n+import "./one"\n+\n+func use() {\n+\t_ = one.New(1)\n+}\n\\ No newline at end of file
diff --git a/test/fixedbugs/bug396.go b/test/fixedbugs/bug396.go
new file mode 100644
index 0000000000..50af6006fb
--- /dev/null
+++ b/test/fixedbugs/bug396.go
@@ -0,0 +1,7 @@
+// $G $D/$F.dir/one.go && $G $D/$F.dir/two.go\n+\n+// Copyright 2011 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 ignored\n```

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

[https://github.com/golang/go/commit/6ff01f01f4e477a931d10c133f33bfe7e0c4ef15](https://github.com/golang/go/commit/6ff01f01f4e477a931d10c133f33bfe7e0c4ef15)

## 元コミット内容

gc: fieldnames in structliterals in exported inlines should not be qualified if they're embedded builtin types.

Trust me. Fixes #2687.

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


## 変更の背景

このコミットは、Goコンパイラ(`gc`)が構造体リテラルを処理する際に発生する特定のバグを修正するために導入されました。問題は、エクスポートされた(外部から参照可能な)インライン化された関数内で、組み込み型(`int`, `string`など)が匿名フィールドとして埋め込まれた構造体リテラルを使用した場合に発生していました。

通常、構造体リテラルでフィールド名を指定する際、そのフィールドが別のパッケージからエクスポートされた型である場合、`パッケージ名.フィールド名`のように修飾されることがあります。しかし、組み込み型が匿名で埋め込まれている場合、そのフィールドは修飾されるべきではありません。例えば、`struct { int }`のような構造体で`int`フィールドに値を設定する際に、`int: 10`のように記述するのが正しい形式です。しかし、バグのあるコンパイラは、この`int`フィールドを不適切に修飾してしまい、結果としてコンパイルエラーや不正なコード生成を引き起こしていました。

この問題は、特にコンパイラのコード生成部分、具体的には構造体リテラルのフォーマット処理において、埋め込み型が組み込み型である場合の特殊なケースが考慮されていなかったことに起因します。コミットメッセージにある「Trust me.」は、この問題が非常に微妙で、コンパイラの内部動作に関する深い理解が必要であることを示唆しています。

## 前提知識の解説

このコミットの理解には、以下のGo言語およびコンパイラに関する前提知識が必要です。

*   **Goコンパイラ (`gc`)**: Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担います。`src/cmd/gc`ディレクトリにそのソースコードが存在します。
*   **構造体リテラル (`struct literal`)**: Go言語で構造体の値を初期化するための構文です。例えば、`type Point struct { X, Y int }; p := Point{X: 10, Y: 20}`のように使用します。フィールド名を指定して値を設定する形式(`X: 10`)と、フィールド名を省略して順番に値を設定する形式(`{10, 20}`)があります。
*   **埋め込み型 (`embedded types`)**: Go言語の構造体では、フィールド名なしで型を宣言することで、その型のフィールドやメソッドを現在の構造体に「埋め込む」ことができます。これにより、埋め込まれた型のフィールドやメソッドを、あたかも自身のフィールドやメソッドであるかのように直接アクセスできます。例えば、`type MyStruct struct { int; string }`という構造体では、`int`と`string`が匿名フィールドとして埋め込まれています。
*   **組み込み型 (`builtin types`)**: Go言語に最初から定義されている基本的な型です。`int`, `string`, `bool`, `float64`などがこれに該当します。
*   **エクスポートされた (`exported`)**: Go言語では、識別子(変数名、関数名、型名、フィールド名など)の最初の文字が大文字である場合、その識別子はパッケージ外からアクセス可能(エクスポートされている)になります。
*   **インライン化 (`inlining`)**: コンパイラの最適化手法の一つで、関数呼び出しをその関数の本体のコードで直接置き換えることです。これにより、関数呼び出しのオーバーヘッドを削減し、プログラムの実行速度を向上させることができます。コンパイラは、特定の条件を満たす関数を自動的にインライン化します。
*   **修飾されたフィールド名 (`qualified field names`)**: 別のパッケージで定義された型やフィールドを参照する際に、`パッケージ名.識別子`の形式で記述することです。例えば、`fmt.Println`のように`fmt`パッケージの`Println`関数を呼び出す場合がこれに当たります。構造体リテラルにおいても、埋め込み型が別のパッケージの型である場合、そのフィールド名が修飾されることがあります。

このコミットの文脈では、「エクスポートされたインライン化されたコード」とは、コンパイラが最適化のためにインライン展開した、外部から参照可能なコードブロックを指します。この中で、`struct { int }`のような組み込み型が埋め込まれた構造体リテラルが使用された際に、`int`フィールドが不適切に修飾されてしまう問題が発生していました。

## 技術的詳細

この修正は、Goコンパイラのコード生成部分、特に`src/cmd/gc/fmt.c`ファイル内の`exprfmt`関数に焦点を当てています。`exprfmt`関数は、抽象構文木(AST)のノードをフォーマットして、最終的なGoコードの文字列表現を生成する役割を担っています。

問題の核心は、`OSTRUCTLIT`(構造体リテラル)の処理において、埋め込み型が組み込み型である場合の特殊なケースが適切に処理されていなかった点にあります。以前のコードでは、構造体リテラルの各フィールドをフォーマットする際に、一律に`%hhS:%N`というフォーマット指定子を使用していました。ここで、`%hhS`はシンボル(フィールド名)を修飾付きで出力しようとします。

修正後のコードでは、`OSTRUCTLIT`の処理ループ内で、各フィールドが埋め込み型であるかどうか、そしてその埋め込み型が組み込み型であるかどうかをチェックするロジックが追加されました。

1.  **`Type *t;` の追加**: `exprfmt`関数のローカル変数として`Type *t;`が追加されました。これは、埋め込み型の実際の型を保持するために使用されます。
2.  **埋め込み型のチェック**: `if(l->n->left->type->embedded)`という条件が追加されました。これは、現在のフィールドが埋め込み型であるかどうかをチェックします。`l->n->left`は構造体リテラルのフィールド名を表すノードです。
3.  **組み込み型のシンボル解決**: 埋め込み型である場合、`t = l->n->left->type->type;`で埋め込み元の型を取得します。さらに、`if(t->sym == S) t = t->type;`という行があります。これは、型がシンボル`S`(おそらく匿名型を表す内部シンボル)を持っている場合に、その基底型(`t->type`)をさらに辿ることで、最終的な組み込み型(例: `int`)の情報を正確に取得しようとするものです。
4.  **非修飾フォーマット**: 埋め込み型が組み込み型であると判断された場合、`fmtprint(f, " %T:%N", t, l->n->right);`が使用されます。ここで、`%T`は型名を非修飾で出力するためのフォーマット指定子です。これにより、`int: 10`のように、型名がそのままフィールド名として使用され、不必要な修飾が回避されます。
5.  **通常のフィールドフォーマット**: 埋め込み型でない場合、または埋め込み型が組み込み型でない場合は、以前と同様に`fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);`が使用されます。これは、フィールド名を修飾付きで出力する通常の動作です。
6.  **カンマとスペースの処理の修正**: 以前のコードでは、フィールドの間にカンマを挿入するロジックと、最後のフィールドの後にスペースを挿入するロジックが、`fmtprint`関数内でフィールド名と値の出力と結合されていました。修正後は、`fmtstrcpy(f, ",")`と`fmtstrcpy(f, " ")`を使用して、カンマとスペースの出力がフィールド名と値の出力から分離されました。これにより、コードの可読性が向上し、特殊なケースの処理がより明確になります。

この修正により、コンパイラは構造体リテラルを正確にフォーマットし、特に埋め込み組み込み型の場合に正しいフィールド名(非修飾)を生成できるようになりました。

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

`src/cmd/gc/fmt.c`ファイルの`exprfmt`関数内の`case OSTRUCTLIT:`ブロックが主な変更箇所です。

```diff
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1062,6 +1062,7 @@ exprfmt(Fmt *f, Node *n, int prec)
 {
  	int nprec;
  	NodeList *l;
+	Type *t; // 追加
  
  	while(n && n->implicit)
  		n = n->left;
@@ -1160,11 +1161,22 @@ exprfmt(Fmt *f, Node *n, int prec)
  	case OSTRUCTLIT:
  		if (fmtmode == FExp) {   // requires special handling of field names
  			fmtprint(f, "%T{\", n->type);\n-\t\t\tfor(l=n->list; l; l=l->next)\n+\t\t\tfor(l=n->list; l; l=l->next) { // ループの開始と終了波括弧を追加
+\t\t\t\t// another special case: if n->left is an embedded field of builtin type,
+\t\t\t\t// it needs to be non-qualified.  Can't figure that out in %S, so do it here
+\t\t\t\tif(l->n->left->type->embedded) { // 埋め込み型のチェック
+\t\t\t\t\tt = l->n->left->type->type; // 埋め込み元の型を取得
+\t\t\t\t\tif(t->sym == S) // シンボルSの場合の基底型解決
+\t\t\t\t\t\tt = t->type;
+\t\t\t\t\tfmtprint(f, \" %T:%N\", t, l->n->right); // 非修飾で出力
+\t\t\t\t} else
+\t\t\t\t\tfmtprint(f, \" %hhS:%N\", l->n->left->sym, l->n->right); // 通常の修飾付き出力
+\n  	\t\t\tif(l->next)\n-\t\t\t\t\tfmtprint(f, \" %hhS:%N,\", l->n->left->sym, l->n->right);\n+\t\t\t\t\tfmtstrcpy(f, \",\"); // カンマの出力分離
  	\t\t\telse
-\t\t\t\t\tfmtprint(f, \" %hhS:%N \", l->n->left->sym, l->n->right);\n+\t\t\t\t\tfmtstrcpy(f, \" \"); // スペースの出力分離
+\t\t\t} // ループの終了波括弧を追加
  	\t\treturn fmtstrcpy(f, \"}\");
  		}\n  		// fallthrough

また、この修正を検証するためのテストケースがtest/fixedbugs/bug396.gotest/fixedbugs/bug396.dir/one.gotest/fixedbugs/bug396.dir/two.goとして追加されています。

コアとなるコードの解説

変更されたsrc/cmd/gc/fmt.cexprfmt関数内のOSTRUCTLITケースのコードは、構造体リテラルのフィールドをフォーマットするロジックを改善しています。

  1. Type *t; の導入:

    • Type *t;という変数が追加されました。これは、埋め込み型が組み込み型である場合に、その組み込み型の情報を一時的に保持するために使用されます。
  2. for ループの変更:

    • 以前はfor(l=n->list; l; l=l->next)の後に直接fmtprintが続いていましたが、変更後は波括弧{}が追加され、ループ本体がブロックになりました。これにより、より複雑な条件分岐ロジックを内部に記述できるようになりました。
  3. 埋め込み組み込み型の特殊処理:

    • if(l->n->left->type->embedded): 現在処理している構造体リテラルのフィールド(l->n->left)が埋め込み型であるかをチェックします。
    • t = l->n->left->type->type;: 埋め込み型の場合、その埋め込み元の型(基底型)を取得します。例えば、struct { int }の場合、l->n->left->typeは匿名フィールドの型を表し、そのtypeプロパティがint型を指します。
    • if(t->sym == S) t = t->type;: これは、Goコンパイラの内部的な型表現に関する特殊な処理です。SはGoコンパイラ内部で匿名型や特定のシンボルを表すために使われる定数である可能性があります。もし取得した型tのシンボルがSである場合、それはまだ最終的な組み込み型ではない可能性があり、さらにその基底型(t->type)を辿ることで、真の組み込み型(例: int)に到達しようとします。
    • fmtprint(f, " %T:%N", t, l->n->right);: ここが最も重要な変更点です。埋め込み組み込み型の場合、%Tフォーマット指定子を使用して型tの名前を非修飾で出力します。これにより、例えばint: 10のように、パッケージ名などの修飾なしでフィールド名が生成されます。%Nはフィールドの値(l->n->right)を出力します。
    • else fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);: 埋め込み組み込み型でない場合は、以前と同様に%hhSを使用してフィールドのシンボル(名前)を修飾付きで出力します。
  4. カンマとスペースの出力分離:

    • 以前のコードでは、フィールドの出力とカンマ/スペースの出力がfmtprint呼び出し内で結合されていました。
    • if(l->next) fmtstrcpy(f, ",");: 次のフィールドがある場合は、fmtstrcpyを使ってカンマのみを出力します。
    • else fmtstrcpy(f, " ");: 最後のフィールドの場合は、スペースのみを出力します。
    • この変更により、フィールド名と値のフォーマットロジックと、区切り文字のロジックが明確に分離され、コードの保守性が向上しています。

この一連の変更により、Goコンパイラは、エクスポートされたインライン化されたコード内で使用される、組み込み型が埋め込まれた構造体リテラルのフィールド名を正しく(非修飾で)生成できるようになり、コンパイルエラーや不正なコード生成を防ぎます。

関連リンク

  • Go Gerrit Change-ID: https://golang.org/cl/5545047 (コミットメッセージに記載されているGoのコードレビューシステムへのリンク)

参考にした情報源リンク

  • コミットデータファイル: /home/orange/Project/comemo/commit_data/11226.txt
  • GitHubコミットページ: https://github.com/golang/go/commit/6ff01f01f4e477a931d10c133f33bfe7e0c4ef15