[インデックス 1007] ファイルの概要
このコミットは、Go言語の初期のコンパイラ (gc
) における構造体タグ(struct tag)の扱いに関する改善を導入しています。具体的には、インポートされた構造体におけるアノテーションのサポートと、複数のフィールド名にわたってタグを適切に分配する機能を追加しています。これにより、Go言語の構造体定義におけるメタデータの表現力が向上し、より柔軟なコード生成やデータ処理が可能になります。
コミット
- Author: Russ Cox rsc@golang.org
- Date: Thu Oct 30 15:25:26 2008 -0700
- Commit Hash: 1850b29da672c8c1364ce9a2cdfebefead6d40e2
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1850b29da672c8c1364ce9a2cdfebefead6d40e2
元コミット内容
struct annotations in imports.
distribute tag across multiple names.
R=ken
OCL=18178
CL=18178
変更の背景
Go言語の初期段階において、構造体フィールドに付与される「タグ」(現在のstruct tag)は、主にencoding/json
やencoding/xml
のようなパッケージで、フィールド名と異なるキー名を指定したり、特定の振る舞いを制御したりするために使用されていました。このコミットが行われた2008年10月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。
このコミットの背景には、以下の2つの主要な課題があったと考えられます。
- インポートされた構造体のアノテーション(タグ)のサポート: Go言語では、他のパッケージで定義された構造体をインポートして使用することが一般的です。しかし、初期のコンパイラでは、インポートされた構造体のフィールドに付与されたアノテーション(タグ)が適切に処理されていなかった可能性があります。これにより、インポートされた構造体に対して、タグを利用した特定の処理(例: JSONエンコーディング/デコーディング)が期待通りに機能しない問題が発生していたと考えられます。
- 複数名にわたるタグの分配: Go言語では、
Field1, Field2 string
のように、複数のフィールドを一度に宣言し、その後にタグを付与する構文が可能です。この場合、付与されたタグが宣言されたすべてのフィールドに正しく適用される必要がありました。しかし、初期の実装では、この「タグの分配」が正しく行われていなかった、あるいは考慮されていなかった可能性があります。このコミットは、この問題を解決し、タグが宣言されたすべてのフィールドに適切に紐付けられるようにするためのものです。
これらの改善は、Go言語の構造体タグ機能の堅牢性を高め、より実用的なデータ構造の定義と利用を可能にするために不可欠でした。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。
-
Go言語の構造体 (Struct): Go言語における構造体は、異なる型のフィールドをまとめた複合データ型です。
type Person struct { Name string Age int }
-
Go言語の構造体タグ (Struct Tag): 構造体フィールドの宣言に付与される文字列リテラルで、フィールドに関するメタデータを提供します。主にリフレクションと組み合わせて、JSONエンコーディング/デコーディング、データベースマッピングなどで利用されます。
type Person struct { Name string `json:"person_name"` Age int `json:"person_age,omitempty"` }
このタグは、コンパイル時には無視されますが、実行時に
reflect
パッケージを通じてアクセスできます。 -
Goコンパイラ (
gc
): Go言語の公式コンパイラの一つで、Goソースコードを機械語に変換します。このコミットが対象としているのは、Go言語の初期のコンパイラ実装です。 -
go.y
(Yacc/Bison 文法ファイル): Goコンパイラのソースコードに含まれるgo.y
ファイルは、Go言語の構文解析(パース)ルールを定義するYacc(またはBison)形式の文法ファイルです。Yaccは、文法定義からC言語のパーサーコードを生成するツールです。このファイルは、Go言語のソースコードがどのようにトークン化され、抽象構文木(AST)に変換されるかを規定しています。変更点はこのファイルのhidden_structdcl
ルールにあり、構造体宣言の解析方法に影響を与えます。 -
subr.c
(サブルーチンファイル): Goコンパイラのソースコードに含まれるsubr.c
ファイルは、コンパイラの様々なサブルーチン(補助関数)を実装しています。このコミットでは、cleanidlist
関数が変更されており、これは識別子リスト(この場合は構造体フィールドのリスト)の処理に関連するものです。 -
抽象構文木 (Abstract Syntax Tree - AST): コンパイラがソースコードを解析する際に生成する、プログラムの構造を木構造で表現したものです。コンパイラはASTを元に、型チェック、最適化、コード生成などを行います。Goコンパイラでは、
Node
構造体がASTのノードを表します。 -
nod
関数: Goコンパイラ内部でASTノードを生成するための関数です。nod(ODCLFIELD, ...)
は、フィールド宣言を表すノードを作成します。 -
newname
関数: 新しい名前(識別子)を作成するための関数です。 -
OLIST
: Goコンパイラ内部で、リスト構造を表すASTノードのオペレーションコード(op
フィールドの値)です。複数の要素がリストとして扱われる場合に用いられます。 -
val
フィールド: ASTノードのval
フィールドは、リテラル値や、このコミットのように構造体タグのようなメタデータを保持するために使用されます。
技術的詳細
このコミットは、Goコンパイラのパーサーとセマンティック分析部分に影響を与える変更を含んでいます。
src/cmd/gc/go.y
の変更
go.y
ファイルでは、hidden_structdcl
という文法ルールが変更されています。このルールは、構造体フィールドの宣言を処理する部分です。
変更前:
hidden_structdcl:
sym1 hidden_type
{
$$ = nod(ODCLFIELD, newname($1), N);
$$->type = $2;
}
変更後:
hidden_structdcl:
sym1 hidden_type oliteral
{
$$ = nod(ODCLFIELD, newname($1), N);
$$->type = $2;
$$->val = $3;
}
この変更のポイントは、oliteral
という新しい要素がhidden_structdcl
ルールに追加されたことです。
sym1
: フィールド名(識別子)を表します。hidden_type
: フィールドの型を表します。oliteral
: オプショナルなリテラル(optional literal)を意味し、この文脈では構造体タグの文字列リテラルを指します。
変更により、パーサーは構造体フィールドの型だけでなく、それに続くタグの文字列リテラルも$3
として取得し、それを新しく生成されるASTノード($$
)のval
フィールドに格納するようになりました。これにより、コンパイラのASTが構造体タグの情報を保持できるようになり、後続の処理でこの情報が利用可能になります。
src/cmd/gc/subr.c
の変更
subr.c
ファイルでは、cleanidlist
関数が変更されています。この関数は、複数の識別子(この場合は構造体フィールド名)がカンマ区切りで宣言されている場合に、それらの識別子を処理し、共通の型やその他の属性を割り当てる役割を担っています。
変更前:
for(n=na; n->op == OLIST; n=n->right)
n->left->type = last->type;
変更後:
for(n=na; n->op == OLIST; n=n->right) {
n->left->type = last->type;
n->left->val = last->val;
}
この変更のポイントは、ループ内でn->left->val = last->val;
という行が追加されたことです。
na
: 識別子リストの先頭ノード。n
: 現在処理中のリストノード。n->left
: リスト内の個々の識別子(フィールド名)を表すノード。last
: リストの最後の要素(この場合は、タグ情報を持つフィールド宣言ノード)を表します。
cleanidlist
関数は、Field1, Field2 string
のような宣言において、Field1
とField2
の両方にstring
型を割り当てるために使用されます。この変更により、last
ノード(つまり、タグ情報を持つ最後のフィールド宣言)から取得したval
(タグの文字列)が、リスト内のすべてのフィールドノード(n->left
)のval
フィールドにコピーされるようになりました。
これにより、「distribute tag across multiple names」(複数の名前にわたってタグを分配する)というコミットメッセージの目的が達成されます。つまり、Field1, Field2 string
json:"foo" のような宣言があった場合、
json:"foo"というタグが
Field1と
Field2`の両方のASTノードに正しく関連付けられるようになります。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index c49c47f21e..cc5a101524 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1892,10 +1892,11 @@ hidden_dcl:
}\n
hidden_structdcl:
-\tsym1 hidden_type
+\tsym1 hidden_type oliteral
{\n \t\t$$ = nod(ODCLFIELD, newname($1), N);\n \t\t$$->type = $2;\n+\t\t$$->val = $3;\n \t}\n |\t\'?\' hidden_type
\t{\ndiff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index d774a8d834..24e894d897 100644
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2192,8 +2192,10 @@ cleanidlist(Node *na)\n \tif(last->type == T)\n \t\tfatal(\"cleanidlist: no type\");\n \n-\tfor(n=na; n->op == OLIST; n=n->right)\n+\tfor(n=na; n->op == OLIST; n=n->right) {\n \t\tn->left->type = last->type;\n+\t\tn->left->val = last->val;\n+\t}\n \treturn na;\n }\n \n```
## コアとなるコードの解説
### `src/cmd/gc/go.y` の変更点
* **`hidden_structdcl` ルールの変更**:
* `sym1 hidden_type` から `sym1 hidden_type oliteral` へと変更されました。これは、構造体フィールド宣言の構文解析において、フィールド名 (`sym1`) と型 (`hidden_type`) に加えて、オプションの文字列リテラル (`oliteral`、この場合は構造体タグ) も解析対象に含めることを意味します。
* アクションブロック内に `$$->val = $3;` が追加されました。これは、解析された`oliteral`(`$3`)の値を、新しく作成されるASTノード(`$$`)の`val`フィールドに代入することを意味します。これにより、構造体タグの情報がASTに組み込まれるようになります。
### `src/cmd/gc/subr.c` の変更点
* **`cleanidlist` 関数のループ内の変更**:
* `n->left->val = last->val;` がループ内に追加されました。
* `cleanidlist`関数は、`Field1, Field2 string` のように複数の識別子が一度に宣言された場合に、それらすべての識別子に共通の属性(この場合は型とタグ)を割り当てるために使用されます。
* `last->val` は、宣言の最後に付与されたタグの文字列リテラルを保持しています。この変更により、リスト内の各フィールドノード (`n->left`) に対して、この共通のタグ情報 (`last->val`) がコピーされるようになります。
* これにより、`Field1, Field2 string `json:"foo"` のような宣言があった場合、`Field1`と`Field2`の両方のフィールドが`json:"foo"`というタグを持つように、コンパイラの内部表現が更新されます。
これらの変更は、Go言語の構造体タグが、単一のフィールドだけでなく、複数のフィールド宣言にも正しく適用され、コンパイラのASTに正確に反映されるようにするための重要なステップでした。
## 関連リンク
Go言語の初期の設計に関する情報は、現在の公式ドキュメントからは見つけにくい場合があります。しかし、Goの設計哲学や歴史に関する一般的な情報は以下のリンクで参照できます。
* **Go言語の公式ドキュメント**: [https://go.dev/doc/](https://go.dev/doc/)
* **Go言語のブログ**: [https://go.dev/blog/](https://go.dev/blog/) (特に初期の設計に関する記事があるかもしれません)
* **Go言語の仕様**: [https://go.dev/ref/spec](https://go.dev/ref/spec) (構造体タグの仕様について)
## 参考にした情報源リンク
この解説は、提供されたコミット情報と、Go言語のコンパイラ設計、特にYacc/Bison文法とASTの概念に関する一般的な知識に基づいて作成されています。特定の外部情報源への直接的なリンクはありませんが、以下のキーワードで検索することで、より詳細な情報を得ることができます。
* "Go compiler source code"
* "Go gc go.y"
* "Go struct tags implementation"
* "Yacc grammar example"
* "Abstract Syntax Tree (AST) in compilers"
* "Go language history"
* "Russ Cox Go"
* "Go OCL CL" (Goの変更リスト番号に関する情報)
# [インデックス 1007] ファイルの概要
このコミットは、Go言語の初期のコンパイラ (`gc`) における構造体タグ(struct tag)の扱いに関する改善を導入しています。具体的には、インポートされた構造体におけるアノテーションのサポートと、複数のフィールド名にわたってタグを適切に分配する機能を追加しています。これにより、Go言語の構造体定義におけるメタデータの表現力が向上し、より柔軟なコード生成やデータ処理が可能になります。
## コミット
- **Author**: Russ Cox <rsc@golang.org>
- **Date**: Thu Oct 30 15:25:26 2008 -0700
- **Commit Hash**: 1850b29da672c8c1364ce9a2cdfebefead6d40e2
## GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1850b29da672c8c1364ce9a2cdfebefead6d40e2
## 元コミット内容
struct annotations in imports.
distribute tag across multiple names.
R=ken
OCL=18178
CL=18178
## 変更の背景
Go言語の初期段階において、構造体フィールドに付与される「タグ」(現在のstruct tag)は、主に`encoding/json`や`encoding/xml`のようなパッケージで、フィールド名と異なるキー名を指定したり、特定の振る舞いを制御したりするために使用されていました。このコミットが行われた2008年10月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。
このコミットの背景には、以下の2つの主要な課題があったと考えられます。
1. **インポートされた構造体のアノテーション(タグ)のサポート**: Go言語では、他のパッケージで定義された構造体をインポートして使用することが一般的です。しかし、初期のコンパイラでは、インポートされた構造体のフィールドに付与されたアノテーション(タグ)が適切に処理されていなかった可能性があります。これにより、インポートされた構造体に対して、タグを利用した特定の処理(例: JSONエンコーディング/デコーディング)が期待通りに機能しない問題が発生していたと考えられます。
2. **複数名にわたるタグの分配**: Go言語では、`Field1, Field2 string `のように、複数のフィールドを一度に宣言し、その後にタグを付与する構文が可能です。この場合、付与されたタグが宣言されたすべてのフィールドに正しく適用される必要がありました。しかし、初期の実装では、この「タグの分配」が正しく行われていなかった、あるいは考慮されていなかった可能性があります。このコミットは、この問題を解決し、タグが宣言されたすべてのフィールドに適切に紐付けられるようにするためのものです。
これらの改善は、Go言語の構造体タグ機能の堅牢性を高め、より実用的なデータ構造の定義と利用を可能にするために不可欠でした。
## 前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。
* **Go言語の構造体 (Struct)**: Go言語における構造体は、異なる型のフィールドをまとめた複合データ型です。
```go
type Person struct {
Name string
Age int
}
```
* **Go言語の構造体タグ (Struct Tag)**: 構造体フィールドの宣言に付与される文字列リテラルで、フィールドに関するメタデータを提供します。主にリフレクションと組み合わせて、JSONエンコーディング/デコーディング、データベースマッピングなどで利用されます。
```go
type Person struct {
Name string `json:"person_name"`
Age int `json:"person_age,omitempty"`
}
```
このタグは、コンパイル時には無視されますが、実行時に`reflect`パッケージを通じてアクセスできます。
* **Goコンパイラ (`gc`)**: Go言語の公式コンパイラの一つで、Goソースコードを機械語に変換します。このコミットが対象としているのは、Go言語の初期のコンパイラ実装です。
* **`go.y` (Yacc/Bison 文法ファイル)**: Goコンパイラのソースコードに含まれる`go.y`ファイルは、Go言語の構文解析(パース)ルールを定義するYacc(またはBison)形式の文法ファイルです。Yaccは、文法定義からC言語のパーサーコードを生成するツールです。このファイルは、Go言語のソースコードがどのようにトークン化され、抽象構文木(AST)に変換されるかを規定しています。変更点はこのファイルの`hidden_structdcl`ルールにあり、構造体宣言の解析方法に影響を与えます。
* **`subr.c` (サブルーチンファイル)**: Goコンパイラのソースコードに含まれる`subr.c`ファイルは、コンパイラの様々なサブルーチン(補助関数)を実装しています。このコミットでは、`cleanidlist`関数が変更されており、これは識別子リスト(この場合は構造体フィールドのリスト)の処理に関連するものです。
* **抽象構文木 (Abstract Syntax Tree - AST)**: コンパイラがソースコードを解析する際に生成する、プログラムの構造を木構造で表現したものです。コンパイラはASTを元に、型チェック、最適化、コード生成などを行います。Goコンパイラでは、`Node`構造体がASTのノードを表します。
* **`nod`関数**: Goコンパイラ内部でASTノードを生成するための関数です。`nod(ODCLFIELD, ...)`は、フィールド宣言を表すノードを作成します。
* **`newname`関数**: 新しい名前(識別子)を作成するための関数です。
* **`OLIST`**: Goコンパイラ内部で、リスト構造を表すASTノードのオペレーションコード(`op`フィールドの値)です。複数の要素がリストとして扱われる場合に用いられます。
* **`val`フィールド**: ASTノードの`val`フィールドは、リテラル値や、このコミットのように構造体タグのようなメタデータを保持するために使用されます。
## 技術的詳細
このコミットは、Goコンパイラのパーサーとセマンティック分析部分に影響を与える変更を含んでいます。
### `src/cmd/gc/go.y` の変更
`go.y`ファイルでは、`hidden_structdcl`という文法ルールが変更されています。このルールは、構造体フィールドの宣言を処理する部分です。
変更前:
```yacc
hidden_structdcl:
sym1 hidden_type
{
$$ = nod(ODCLFIELD, newname($1), N);
$$->type = $2;
}
変更後:
hidden_structdcl:
sym1 hidden_type oliteral
{
$$ = nod(ODCLFIELD, newname($1), N);
$$->type = $2;
$$->val = $3;
}
この変更のポイントは、oliteral
という新しい要素がhidden_structdcl
ルールに追加されたことです。
sym1
: フィールド名(識別子)を表します。hidden_type
: フィールドの型を表します。oliteral
: オプショナルなリテラル(optional literal)を意味し、この文脈では構造体タグの文字列リテラルを指します。
変更により、パーサーは構造体フィールドの型だけでなく、それに続くタグの文字列リテラルも$3
として取得し、それを新しく生成されるASTノード($$
)のval
フィールドに格納するようになりました。これにより、コンパイラのASTが構造体タグの情報を保持できるようになり、後続の処理でこの情報が利用可能になります。
src/cmd/gc/subr.c
の変更
subr.c
ファイルでは、cleanidlist
関数が変更されています。この関数は、複数の識別子(この場合は構造体フィールド名)がカンマ区切りで宣言されている場合に、それらの識別子を処理し、共通の型やその他の属性を割り当てる役割を担っています。
変更前:
for(n=na; n->op == OLIST; n=n->right)
n->left->type = last->type;
変更後:
for(n=na; n->op == OLIST; n=n->right) {
n->left->type = last->type;
n->left->val = last->val;
}
この変更のポイントは、ループ内でn->left->val = last->val;
という行が追加されたことです。
na
: 識別子リストの先頭ノード。n
: 現在処理中のリストノード。n->left
: リスト内の個々の識別子(フィールド名)を表すノード。last
: リストの最後の要素(この場合は、タグ情報を持つフィールド宣言ノード)を表します。
cleanidlist
関数は、Field1, Field2 string
のような宣言において、Field1
とField2
の両方にstring
型を割り当てるために使用されます。この変更により、last
ノード(つまり、タグ情報を持つ最後のフィールド宣言)から取得したval
(タグの文字列)が、リスト内のすべてのフィールドノード(n->left
)のval
フィールドにコピーされるようになりました。
これにより、「distribute tag across multiple names」(複数の名前にわたってタグを分配する)というコミットメッセージの目的が達成されます。つまり、Field1, Field2 string
json:"foo" のような宣言があった場合、
json:"foo"というタグが
Field1と
Field2`の両方のASTノードに正しく関連付けられるようになります。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index c49c47f21e..cc5a101524 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1892,10 +1892,11 @@ hidden_dcl:
}\n
hidden_structdcl:
-\tsym1 hidden_type
+\tsym1 hidden_type oliteral
{\n \t\t$$ = nod(ODCLFIELD, newname($1), N);\n \t\t$$->type = $2;\n+\t\t$$->val = $3;\n \t}\n |\t\'?\' hidden_type
\t{\ndiff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index d774a8d834..24e894d897 100644
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2192,8 +2192,10 @@ cleanidlist(Node *na)\n \tif(last->type == T)\n \t\tfatal(\"cleanidlist: no type\");\n \n-\tfor(n=na; n->op == OLIST; n=n->right)\n+\tfor(n=na; n->op == OLIST; n=n->right) {\n \t\tn->left->type = last->type;\n+\t\tn->left->val = last->val;\n+\t}\n \treturn na;\n }\n \n```
## コアとなるコードの解説
### `src/cmd/gc/go.y` の変更点
* **`hidden_structdcl` ルールの変更**:
* `sym1 hidden_type` から `sym1 hidden_type oliteral` へと変更されました。これは、構造体フィールド宣言の構文解析において、フィールド名 (`sym1`) と型 (`hidden_type`) に加えて、オプションの文字列リテラル (`oliteral`、この場合は構造体タグ) も解析対象に含めることを意味します。
* アクションブロック内に `$$->val = $3;` が追加されました。これは、解析された`oliteral`(`$3`)の値を、新しく作成されるASTノード(`$$`)の`val`フィールドに代入することを意味します。これにより、構造体タグの情報がASTに組み込まれるようになります。
### `src/cmd/gc/subr.c` の変更点
* **`cleanidlist` 関数のループ内の変更**:
* `n->left->val = last->val;` がループ内に追加されました。
* `cleanidlist`関数は、`Field1, Field2 string` のように複数の識別子が一度に宣言された場合に、それらすべての識別子に共通の属性(この場合は型とタグ)を割り当てるために使用されます。
* `last->val` は、宣言の最後に付与されたタグの文字列リテラルを保持しています。この変更により、リスト内の各フィールドノード (`n->left`) に対して、この共通のタグ情報 (`last->val`) がコピーされるようになります。
* これにより、`Field1, Field2 string `json:"foo"` のような宣言があった場合、`Field1`と`Field2`の両方のフィールドが`json:"foo"`というタグを持つように、コンパイラの内部表現が更新されます。
これらの変更は、Go言語の構造体タグが、単一のフィールドだけでなく、複数のフィールド宣言にも正しく適用され、コンパイラの内部表現に正確に反映されるようにするための重要なステップでした。
## 関連リンク
Go言語の初期の設計に関する情報は、現在の公式ドキュメントからは見つけにくい場合があります。しかし、Goの設計哲学や歴史に関する一般的な情報は以下のリンクで参照できます。
* **Go言語の公式ドキュメント**: [https://go.dev/doc/](https://go.dev/doc/)
* **Go言語のブログ**: [https://go.dev/blog/](https://go.dev/blog/) (特に初期の設計に関する記事があるかもしれません)
* **Go言語の仕様**: [https://go.dev/ref/spec](https://go.dev/ref/spec) (構造体タグの仕様について)
## 参考にした情報源リンク
この解説は、提供されたコミット情報と、Go言語のコンパイラ設計、特にYacc/Bison文法とASTの概念に関する一般的な知識に基づいて作成されています。また、以下のWeb検索結果も参考にしました。
* Go language struct tags history: [https://boldlygo.tech/posts/2023/07/26/go-struct-tags-history/](https://boldlygo.tech/posts/2023/07/26/go-struct-tags-history/)
* Go compiler gc go.y: [https://caffeinatedwonders.com/2020/03/09/go-compiler-internals-part-1-lexing-and-parsing/](https://caffeinatedwonders.com/2020/03/09/go-compiler-internals-part-1-lexing-and-parsing/)
* Go compiler gc subr.c: [https://go.googlesource.com/go/+/refs/heads/master/src/cmd/gc/subr.c](https://go.googlesource.com/go/+/refs/heads/master/src/cmd/gc/subr.c)
* Go struct annotations early versions: [https://go.dev/blog/json](https://go.dev/blog/json)
* Go OCL CL numbers: [https://go.dev/doc/contribute#change_lists](https://go.dev/doc/contribute#change_lists)