[インデックス 1424] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)における配列リテラルの扱い、特に[...]
構文を用いた配列の長さの自動推論機能の導入に関するものです。Go言語の初期段階において、コンパイラが配列リテラルの要素数から配列の長さを自動的に決定する機能が追加されました。
コミット
commit b0f627a6e1b1df1b47309f7fd59281a3809fb4d0
Author: Ken Thompson <ken@golang.org>
Date: Tue Jan 6 17:31:24 2009 -0800
closed arrays including [...]
R=r
OCL=22182
CL=22182
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b0f627a6e1b1df1b47309f7fd59281a3809fb4d0
元コミット内容
このコミットは、Go言語のコンパイラに、配列リテラルで[...]
構文を使用した場合に、初期化子リストの要素数に基づいて配列の長さを自動的に決定する機能を追加します。これにより、開発者は配列の長さを明示的に指定することなく、初期値から配列を定義できるようになります。
変更されたファイルは以下の通りです。
src/cmd/gc/go.y
: Goコンパイラの文法定義ファイル。[...]
構文の新しい型定義が追加されました。src/cmd/gc/walk.c
: Goコンパイラの抽象構文木(AST)を走査し、コード生成を行う部分。配列リテラルの処理ロジックが更新され、[...]
配列の長さ推論と、固定長配列の初期化時の境界チェックが追加されました。src/run.bash
: テスト実行スクリプト。実行ファイルのパス指定が修正されました。
変更の背景
Go言語の初期開発段階において、配列の宣言と初期化の方法は進化していました。このコミット以前は、配列リテラルを定義する際に、その長さを明示的に指定する必要がありました(例: [5]int{1, 2, 3, 4, 5}
)。しかし、初期化する要素の数が多くなる場合や、要素数が頻繁に変わるような場合には、長さを手動で数えて指定するのは手間であり、エラーの原因にもなり得ました。
この問題を解決するため、コンパイラが配列リテラルの初期化子リストから配列の長さを自動的に推論する機能が求められました。C言語など他の言語にも同様の機能(例: int arr[] = {1, 2, 3};
)が存在し、Go言語でも同様の利便性を提供することが目的でした。[...]
構文は、この「コンパイラによる長さ推論」を明示的に示すためのシンタックスシュガーとして導入されました。これにより、コードの可読性と保守性が向上し、開発者は配列の長さを気にすることなく、初期化するデータに集中できるようになりました。
前提知識の解説
このコミットの理解には、以下の技術的知識が役立ちます。
-
Go言語の配列とスライス:
- 配列 (Array): Go言語の配列は、固定長で同じ型の要素のシーケンスです。宣言時に長さが決定され、実行中に変更することはできません。例:
var a [5]int
。 - スライス (Slice): スライスは配列の上に構築された動的なビューです。長さは可変で、実行時に要素を追加したり削除したりできます。スライスは内部的に配列を参照し、長さと容量を持ちます。例:
var s []int
。 - 配列リテラル:
[length]Type{elem1, elem2, ...}
の形式で配列を初期化する方法です。
- 配列 (Array): Go言語の配列は、固定長で同じ型の要素のシーケンスです。宣言時に長さが決定され、実行中に変更することはできません。例:
-
コンパイラの基本:
- 字句解析 (Lexical Analysis): ソースコードをトークン(単語)のストリームに変換するプロセス。
- 構文解析 (Parsing): トークンのストリームを解析し、言語の文法規則に従って抽象構文木(AST)を構築するプロセス。
go.y
ファイルはこの構文解析器の定義に使用されるYacc/Bison形式の文法ファイルです。 - 抽象構文木 (Abstract Syntax Tree - AST): ソースコードの構造を木構造で表現したもの。コンパイラの後の段階(型チェック、コード生成など)で利用されます。
- セマンティック分析 (Semantic Analysis): ASTを走査し、型チェックや意味的な検証を行うプロセス。
walk.c
ファイルはこの段階の一部を担います。 - コード生成 (Code Generation): ASTから実行可能なコード(または中間コード)を生成するプロセス。
-
Yacc/Bison:
- Yacc (Yet Another Compiler Compiler) や Bison は、文法定義から構文解析器(パーサー)を自動生成するためのツールです。
.y
拡張子のファイルは、これらのツールが読み込む文法規則を記述したものです。Goコンパイラの初期バージョンでは、go.y
がGo言語の構文解析に使用されていました。
- Yacc (Yet Another Compiler Compiler) や Bison は、文法定義から構文解析器(パーサー)を自動生成するためのツールです。
技術的詳細
このコミットの技術的詳細は、Goコンパイラのフロントエンドにおける構文解析とセマンティック分析の変更に集約されます。
-
src/cmd/gc/go.y
における構文解析の拡張:- Go言語の文法定義ファイルである
go.y
に、新しいconvtype
(型変換または型定義)ルールが追加されました。 \'[\' LDDD \']\' type
という新しいプロダクションルールが追加されています。ここでLDDD
はGo言語の...
(エリプシス)トークンに対応します。これは、[...]T
という形式(例:[...]int
)が有効な型として認識されるようになったことを意味します。- このルールがマッチした場合、アクションとして
$$ = aindex(N, $4);
と$$->bound = -100;
が実行されます。aindex(N, $4)
: 配列の型ノードを作成する関数です。$4
はtype
に対応し、配列の要素型を示します。N
はここでは配列の長さがまだ不明であることを示唆しています。$$->bound = -100;
: ここで-100
という特殊な値がbound
フィールドに設定されます。これは、この配列が「長さがコンパイラによって推論されるべき配列」であることを示す内部的なフラグとして機能します。Goコンパイラでは、配列の長さはType
構造体のbound
フィールドに格納されますが、負の値は通常、スライス(長さが不定)や、このケースのように特別な意味を持つ場合に使用されます。
- Go言語の文法定義ファイルである
-
src/cmd/gc/walk.c
におけるセマンティック分析とコード生成の変更:arraylit
関数は、配列リテラルを処理するGoコンパイラの重要な部分です。この関数は、ソースコード中の[...]Type{...}
のような配列初期化を、内部的なAST表現に変換し、最終的なコード生成のための準備を行います。- 長さ推論ロジックの追加:
b = t->bound;
で配列の型情報からbound
(長さ)を取得します。if(b < 0 && b != -100)
: この条件は、スライス(bound < 0
)であり、かつ[...]
配列ではない場合(bound != -100
)に、スライスとして処理するためのOMAKE
ノード(スライスを作成する操作)を生成します。if(b == -100)
: これが[...]
配列の長さ推論の核心部分です。bound
が-100
の場合、コンパイラは初期化子リストを走査して実際に提供された要素の数(idx
)を数え上げます。そして、t->bound = idx;
によって、このidx
の値を配列の実際の長さとしてType
構造体に設定します。これにより、コンパイル時に配列の長さが確定します。
- 固定長配列の境界チェック:
if(b >= 0 && idx >= b)
: この新しいチェックは、明示的に長さが指定された配列(b >= 0
)に対して、初期化子リストの要素数(idx
)が宣言された長さ(b
)を超えていないかを検証します。もし超えている場合、yyerror("literal array initializer out of bounds");
というエラーを報告し、コンパイルエラーとします。これは、[3]int{1, 2, 3, 4}
のような不正な初期化を検出するために重要です。
- 一時変数の利用: 配列リテラルは、直接その場で生成されるのではなく、一時変数(
var
)に割り当てられ、その一時変数に対して要素が順次代入される形式に変換されます。これは、コンパイラが複雑な初期化を効率的に処理するための一般的な手法です。
-
src/run.bash
の変更:time run
がtime ./run
に変更されました。これは、run
という実行ファイルが現在のディレクトリにあることを明示的に指定するための変更です。PATH
環境変数に依存せず、ローカルの実行ファイルを確実に呼び出すための、ビルドスクリプトにおける一般的なプラクティスです。この変更自体は配列リテラルの機能とは直接関係ありませんが、コンパイラのテスト環境の堅牢性を高めるためのものです。
これらの変更により、Goコンパイラは[...]
構文を正しく解釈し、配列の長さを自動的に推論できるようになり、同時に固定長配列の初期化における安全性を高めました。
コアとなるコードの変更箇所
src/cmd/gc/go.y
(文法定義)
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1021,6 +1021,12 @@ convtype:
// array literal
$$ = aindex($2, $4);
}
+|\t'[' LDDD ']' type
+\t{
+\t\t// array literal of nelem
+\t\t$$ = aindex(N, $4);
+\t\t$$->bound = -100;
+\t}
|\tLMAP '[' type ']' type
{
// map literal
src/cmd/gc/walk.c
(セマンティック分析/コード生成)
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -3556,23 +3556,24 @@ arraylit(Node *n)\
Iter saver;\
Type *t;\
Node *var, *r, *a, *nas, *nnew;\
-\tint idx;\
+\tint idx, b;\
\
t = n->type;\
if(t->etype != TARRAY)\
fatal("arraylit: not array");
\
-\tif(t->bound >= 0)\
-\t\tfatal("arraylit: literal fixed arrays not implemented");
-\
var = nod(OXXX, N, N);\
tempname(var, t);\
\
-\tnnew = nod(OMAKE, N, N);\
-\tnnew->type = t;\
+\tb = t->bound;\
+\tif(b < 0 && b != -100) {\
+\t\t// slice
+\t\tnnew = nod(OMAKE, N, N);\
+\t\tnnew->type = t;\
\
-\tnas = nod(OAS, var, nnew);\
-\taddtop = list(addtop, nas);\
+\t\tnas = nod(OAS, var, nnew);\
+\t\taddtop = list(addtop, nas);\
+\t}\
\
idx = 0;\
r = listfirst(&saver, &n->left);\
@@ -3580,6 +3581,10 @@ arraylit(Node *n)\
\tr = N;\
while(r != N) {\
\t// build list of var[c] = expr\
+\t\tif(b >= 0 && idx >= b) {\
+\t\t\tyyerror("literal array initializer out of bounds");
+\t\t\tbreak;\
+\t\t}\
\ta = nodintconst(idx);\
\ta = nod(OINDEX, var, a);\
\ta = nod(OAS, a, r);\
@@ -3587,7 +3592,13 @@ arraylit(Node *n)\
\tidx++;\
\tr = listnext(&saver);\
}\
-\tnnew->left = nodintconst(idx);\
+\tif(b == -100) {\
+\t\t// compiler counted closed array
+\t\tb = idx;\
+\t\tt->bound = b;\
+\t}\
+\tif(b < 0)\
+\t\tnnew->left = nodintconst(idx);\
return var;\
}
\
コアとなるコードの解説
src/cmd/gc/go.y
convtype
ルールに'[' LDDD ']' type
という新しいプロダクションが追加されました。LDDD
はGo言語の...
トークンに対応します。これにより、[...]int
のような構文がGoの型として認識されるようになります。- このルールがマッチすると、
aindex(N, $4)
が呼び出され、配列の型を表すノードが作成されます。N
は現時点では長さが不明であることを示し、$4
は要素の型(例:int
)です。 $$->bound = -100;
は、この配列の長さがコンパイラによって推論されるべきであることを示す内部的なマーカーとして機能します。-100
は、Goコンパイラ内で特別な意味を持つ負の値として使用されます。
src/cmd/gc/walk.c
arraylit
関数は配列リテラルの処理を担当します。int idx, b;
:idx
は初期化子リストの要素数をカウントするために使用され、b
は配列のbound
(長さ)を保持します。b = t->bound;
:配列の型情報から現在のbound
を取得します。if(b < 0 && b != -100)
:この条件は、型がスライス(bound < 0
)であり、かつ[...]
配列ではない場合に真となります。このブロックでは、スライスを作成するためのOMAKE
ノードが生成され、一時変数に割り当てられます。if(b >= 0 && idx >= b)
:これは新しい境界チェックです。もし配列が固定長(b >= 0
)で、かつ初期化子リストの要素数(idx
)が宣言された長さ(b
)以上である場合、yyerror
を呼び出して「literal array initializer out of bounds」(リテラル配列の初期化子が境界外)というエラーを報告します。これにより、[3]int{1,2,3,4}
のような不正なコードがコンパイル時に検出されます。if(b == -100)
:これが[...]
配列の長さ推論の核心です。もしbound
が-100
(つまり[...]
配列)であれば、b = idx;
によって、実際に初期化子リストに存在する要素の数(idx
)が配列の確定した長さとしてb
に設定されます。そして、t->bound = b;
によって、この確定した長さが配列の型情報に書き込まれます。これにより、コンパイル時に配列の長さが決定されます。if(b < 0) nnew->left = nodintconst(idx);
:これはスライスの場合の処理で、スライスの初期長を設定します。
これらの変更により、Goコンパイラは[...]
構文を正しく解釈し、初期化子リストから配列の長さを自動的に推論できるようになりました。また、固定長配列の初期化における安全性が向上し、不正な初期化をコンパイル時に検出できるようになりました。
関連リンク
- Go言語の配列とスライスに関する公式ドキュメント: https://go.dev/blog/go-slices-usage-and-internals (スライスに関するブログ記事ですが、配列との関連も説明されています)
- Go言語の仕様: https://go.dev/ref/spec (配列リテラルと
...
構文に関する記述があります) - Yacc/Bisonの概念: https://ja.wikipedia.org/wiki/Yacc
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/cmd/gc
ディレクトリ) - コンパイラ設計に関する一般的な知識
- Yacc/Bisonに関する一般的な知識
- Go言語の初期のコミット履歴と関連する議論(GoのIssueトラッカーやメーリングリストなど)
- Go言語の
[...]
配列に関するブログ記事やチュートリアル
[インデックス 1424] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)における配列リテラルの扱い、特に[...]
構文を用いた配列の長さの自動推論機能の導入に関するものです。Go言語の初期段階において、コンパイラが配列リテラルの要素数から配列の長さを自動的に決定する機能が追加されました。
コミット
commit b0f627a6e1b1df1b47309f7fd59281a3809fb4d0
Author: Ken Thompson <ken@golang.org>
Date: Tue Jan 6 17:31:24 2009 -0800
closed arrays including [...]
R=r
OCL=22182
CL=22182
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b0f627a6e1b1df1b47309f7fd59281a3809fb4d0
元コミット内容
このコミットは、Go言語のコンパイラに、配列リテラルで[...]
構文を使用した場合に、初期化子リストの要素数に基づいて配列の長さを自動的に決定する機能を追加します。これにより、開発者は配列の長さを明示的に指定することなく、初期値から配列を定義できるようになります。
変更されたファイルは以下の通りです。
src/cmd/gc/go.y
: Goコンパイラの文法定義ファイル。[...]
構文の新しい型定義が追加されました。src/cmd/gc/walk.c
: Goコンパイラの抽象構文木(AST)を走査し、コード生成を行う部分。配列リテラルの処理ロジックが更新され、[...]
配列の長さ推論と、固定長配列の初期化時の境界チェックが追加されました。src/run.bash
: テスト実行スクリプト。実行ファイルのパス指定が修正されました。
変更の背景
Go言語の初期開発段階において、配列の宣言と初期化の方法は進化していました。このコミット以前は、配列リテラルを定義する際に、その長さを明示的に指定する必要がありました(例: [5]int{1, 2, 3, 4, 5}
)。しかし、初期化する要素の数が多くなる場合や、要素数が頻繁に変わるような場合には、長さを手動で数えて指定するのは手間であり、エラーの原因にもなり得ました。
この問題を解決するため、コンパイラが配列リテラルの初期化子リストから配列の長さを自動的に推論する機能が求められました。C言語など他の言語にも同様の機能(例: int arr[] = {1, 2, 3};
)が存在し、Go言語でも同様の利便性を提供することが目的でした。[...]
構文は、この「コンパイラによる長さ推論」を明示的に示すためのシンタックスシュガーとして導入されました。これにより、コードの可読性と保守性が向上し、開発者は配列の長さを気にすることなく、初期化するデータに集中できるようになりました。
前提知識の解説
このコミットの理解には、以下の技術的知識が役立ちます。
-
Go言語の配列とスライス:
- 配列 (Array): Go言語の配列は、固定長で同じ型の要素のシーケンスです。宣言時に長さが決定され、実行中に変更することはできません。例:
var a [5]int
。 - スライス (Slice): スライスは配列の上に構築された動的なビューです。長さは可変で、実行時に要素を追加したり削除したりできます。スライスは内部的に配列を参照し、長さと容量を持ちます。例:
var s []int
。 - 配列リテラル:
[length]Type{elem1, elem2, ...}
の形式で配列を初期化する方法です。
- 配列 (Array): Go言語の配列は、固定長で同じ型の要素のシーケンスです。宣言時に長さが決定され、実行中に変更することはできません。例:
-
コンパイラの基本:
- 字句解析 (Lexical Analysis): ソースコードをトークン(単語)のストリームに変換するプロセス。
- 構文解析 (Parsing): トークンのストリームを解析し、言語の文法規則に従って抽象構文木(AST)を構築するプロセス。
go.y
ファイルはこの構文解析器の定義に使用されるYacc/Bison形式の文法ファイルです。 - 抽象構文木 (Abstract Syntax Tree - AST): ソースコードの構造を木構造で表現したもの。コンパイラの後の段階(型チェック、コード生成など)で利用されます。
- セマンティック分析 (Semantic Analysis): ASTを走査し、型チェックや意味的な検証を行うプロセス。
walk.c
ファイルはこの段階の一部を担います。 - コード生成 (Code Generation): ASTから実行可能なコード(または中間コード)を生成するプロセス。
-
Yacc/Bison:
- Yacc (Yet Another Compiler Compiler) や Bison は、文法定義から構文解析器(パーサー)を自動生成するためのツールです。
.y
拡張子のファイルは、これらのツールが読み込む文法規則を記述したものです。Goコンパイラの初期バージョンでは、go.y
がGo言語の構文解析に使用されていました。
- Yacc (Yet Another Compiler Compiler) や Bison は、文法定義から構文解析器(パーサー)を自動生成するためのツールです。
技術的詳細
このコミットの技術的詳細は、Goコンパイラのフロントエンドにおける構文解析とセマンティック分析の変更に集約されます。
-
src/cmd/gc/go.y
における構文解析の拡張:- Go言語の文法定義ファイルである
go.y
に、新しいconvtype
(型変換または型定義)ルールが追加されました。 \'[\' LDDD \']\' type
という新しいプロダクションルールが追加されています。ここでLDDD
はGo言語の...
(エリプシス)トークンに対応します。これは、[...]T
という形式(例:[...]int
)が有効な型として認識されるようになったことを意味します。- このルールがマッチした場合、アクションとして
$$ = aindex(N, $4);
と$$->bound = -100;
が実行されます。aindex(N, $4)
: 配列の型ノードを作成する関数です。$4
はtype
に対応し、配列の要素型を示します。N
はここでは配列の長さがまだ不明であることを示唆しています。$$->bound = -100;
: ここで-100
という特殊な値がbound
フィールドに設定されます。これは、この配列が「長さがコンパイラによって推論されるべき配列」であることを示す内部的なフラグとして機能します。Goコンパイラでは、配列の長さはType
構造体のbound
フィールドに格納されますが、負の値は通常、スライス(長さが不定)や、このケースのように特別な意味を持つ場合に使用されます。
- Go言語の文法定義ファイルである
-
src/cmd/gc/walk.c
におけるセマンティック分析とコード生成の変更:arraylit
関数は、配列リテラルを処理するGoコンパイラの重要な部分です。この関数は、ソースコード中の[...]Type{...}
のような配列初期化を、内部的なAST表現に変換し、最終的なコード生成のための準備を行います。- 長さ推論ロジックの追加:
b = t->bound;
で配列の型情報からbound
(長さ)を取得します。if(b < 0 && b != -100)
: この条件は、スライス(bound < 0
)であり、かつ[...]
配列ではない場合(bound != -100
)に、スライスとして処理するためのOMAKE
ノード(スライスを作成する操作)を生成します。if(b == -100)
: これが[...]
配列の長さ推論の核心部分です。bound
が-100
の場合、コンパイラは初期化子リストを走査して実際に提供された要素の数(idx
)を数え上げます。そして、t->bound = idx;
によって、このidx
の値を配列の実際の長さとしてType
構造体に設定します。これにより、コンパイル時に配列の長さが確定します。
- 固定長配列の境界チェック:
if(b >= 0 && idx >= b)
: この新しいチェックは、明示的に長さが指定された配列(b >= 0
)に対して、初期化子リストの要素数(idx
)が宣言された長さ(b
)を超えていないかを検証します。もし超えている場合、yyerror("literal array initializer out of bounds");
というエラーを報告し、コンパイルエラーとします。これは、[3]int{1, 2, 3, 4}
のような不正な初期化を検出するために重要です。
- 一時変数の利用: 配列リテラルは、直接その場で生成されるのではなく、一時変数(
var
)に割り当てられ、その一時変数に対して要素が順次代入される形式に変換されます。これは、コンパイラが複雑な初期化を効率的に処理するための一般的な手法です。
-
src/run.bash
の変更:time run
がtime ./run
に変更されました。これは、run
という実行ファイルが現在のディレクトリにあることを明示的に指定するための変更です。PATH
環境変数に依存せず、ローカルの実行ファイルを確実に呼び出すための、ビルドスクリプトにおける一般的なプラクティスです。この変更自体は配列リテラルの機能とは直接関係ありませんが、コンパイラのテスト環境の堅牢性を高めるためのものです。
これらの変更により、Goコンパイラは[...]
構文を正しく解釈し、配列の長さを自動的に推論できるようになり、同時に固定長配列の初期化における安全性を高めました。
コアとなるコードの変更箇所
src/cmd/gc/go.y
(文法定義)
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1021,6 +1021,12 @@ convtype:
// array literal
$$ = aindex($2, $4);
}
+|\t'[' LDDD ']' type
+\t{
+\t\t// array literal of nelem
+\t\t$$ = aindex(N, $4);
+\t\t$$->bound = -100;
+\t}
|\tLMAP '[' type ']' type
{
// map literal
src/cmd/gc/walk.c
(セマンティック分析/コード生成)
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -3556,23 +3556,24 @@ arraylit(Node *n)\
Iter saver;\
Type *t;\
Node *var, *r, *a, *nas, *nnew;\
-\tint idx;\
+\tint idx, b;\
\
t = n->type;\
if(t->etype != TARRAY)\
fatal("arraylit: not array");
\
-\tif(t->bound >= 0)\
-\t\tfatal("arraylit: literal fixed arrays not implemented");
-\
var = nod(OXXX, N, N);\
tempname(var, t);\
\
-\tnnew = nod(OMAKE, N, N);\
-\tnnew->type = t;\
+\tb = t->bound;\
+\tif(b < 0 && b != -100) {\
+\t\t// slice
+\t\tnnew = nod(OMAKE, N, N);\
+\t\tnnew->type = t;\
\
-\tnas = nod(OAS, var, nnew);\
-\taddtop = list(addtop, nas);\
+\t\tnas = nod(OAS, var, nnew);\
+\t\taddtop = list(addtop, nas);\
+\t}\
\
idx = 0;\
r = listfirst(&saver, &n->left);\
@@ -3580,6 +3581,10 @@ arraylit(Node *n)\
\tr = N;\
while(r != N) {\
\t// build list of var[c] = expr\
+\t\tif(b >= 0 && idx >= b) {\
+\t\t\tyyerror("literal array initializer out of bounds");
+\t\t\tbreak;\
+\t\t}\
\ta = nodintconst(idx);\
\ta = nod(OINDEX, var, a);\
\ta = nod(OAS, a, r);\
@@ -3587,7 +3592,13 @@ arraylit(Node *n)\
\tidx++;\
\tr = listnext(&saver);\
}\
-\tnnew->left = nodintconst(idx);\
+\tif(b == -100) {\
+\t\t// compiler counted closed array
+\t\tb = idx;\
+\t\tt->bound = b;\
+\t}\
+\tif(b < 0)\
+\t\tnnew->left = nodintconst(idx);\
return var;\
}
\
コアとなるコードの解説
src/cmd/gc/go.y
convtype
ルールに'[' LDDD ']' type
という新しいプロダクションが追加されました。LDDD
はGo言語の...
トークンに対応します。これにより、[...]int
のような構文がGoの型として認識されるようになります。- このルールがマッチすると、
aindex(N, $4)
が呼び出され、配列の型を表すノードが作成されます。N
は現時点では長さが不明であることを示し、$4
は要素の型(例:int
)です。 $$->bound = -100;
は、この配列の長さがコンパイラによって推論されるべきであることを示す内部的なマーカーとして機能します。-100
は、Goコンパイラ内で特別な意味を持つ負の値として使用されます。
src/cmd/gc/walk.c
arraylit
関数は配列リテラルの処理を担当します。int idx, b;
:idx
は初期化子リストの要素数をカウントするために使用され、b
は配列のbound
(長さ)を保持します。b = t->bound;
:配列の型情報から現在のbound
を取得します。if(b < 0 && b != -100)
:この条件は、型がスライス(bound < 0
)であり、かつ[...]
配列ではない場合に真となります。このブロックでは、スライスを作成するためのOMAKE
ノードが生成され、一時変数に割り当てられます。if(b >= 0 && idx >= b)
:これは新しい境界チェックです。もし配列が固定長(b >= 0
)で、かつ初期化子リストの要素数(idx
)が宣言された長さ(b
)以上である場合、yyerror
を呼び出して「literal array initializer out of bounds」(リテラル配列の初期化子が境界外)というエラーを報告します。これにより、[3]int{1,2,3,4}
のような不正なコードがコンパイル時に検出されます。if(b == -100)
:これが[...]
配列の長さ推論の核心です。もしbound
が-100
(つまり[...]
配列)であれば、b = idx;
によって、実際に初期化子リストに存在する要素の数(idx
)が配列の確定した長さとしてb
に設定されます。そして、t->bound = b;
によって、この確定した長さが配列の型情報に書き込まれます。これにより、コンパイル時に配列の長さが決定されます。if(b < 0) nnew->left = nodintconst(idx);
:これはスライスの場合の処理で、スライスの初期長を設定します。
これらの変更により、Goコンパイラは[...]
構文を正しく解釈し、初期化子リストから配列の長さを自動的に推論できるようになりました。また、固定長配列の初期化における安全性が向上し、不正な初期化をコンパイル時に検出できるようになりました。
関連リンク
- Go言語の配列とスライスに関する公式ドキュメント: https://go.dev/blog/go-slices-usage-and-internals (スライスに関するブログ記事ですが、配列との関連も説明されています)
- Go言語の仕様: https://go.dev/ref/spec (配列リテラルと
...
構文に関する記述があります) - Yacc/Bisonの概念: https://ja.wikipedia.org/wiki/Yacc
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/cmd/gc
ディレクトリ) - コンパイラ設計に関する一般的な知識
- Yacc/Bisonに関する一般的な知識
- Go言語の初期のコミット履歴と関連する議論(GoのIssueトラッカーやメーリングリストなど)
- Go言語の
[...]
配列に関するブログ記事やチュートリアル