[インデックス 11643] ファイルの概要
このコミットは、Goコンパイラのgc
(Go Compiler)における構造体リテラルのエクスポート時の挙動に関する修正です。具体的には、暗黙的な型を持つ構造体リテラルがエクスポートされる際に、その型情報が不必要に表示される問題を解決します。
コミット
commit 419c53af30c1898a75ac0ef5ba49533ea91ddcfc
Author: Luuk van Dijk <lvd@golang.org>
Date: Mon Feb 6 12:19:59 2012 +0100
gc: don't print implicit type on struct literal in export
As pointed out in the discussion around 2678.
R=rsc
CC=golang-dev
https://golang.org/cl/5534077
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/419c53af30c1898a75ac0ef5ba49533ea91ddcfc
元コミット内容
gc: don't print implicit type on struct literal in export
このコミットは、Goコンパイラ(gc
)が構造体リテラルをエクスポートする際に、暗黙的な型情報を出力しないようにするものです。これは、以前の議論(おそらくIssue 2678に関連)で指摘された問題に対応しています。
変更の背景
Go言語では、構造体リテラルを記述する際に、型名を省略できる場合があります。例えば、type S struct { x, y int }
という構造体があるとして、[]S{{1,2}, {3,4}}
のように、スライスや配列の要素として構造体リテラルを記述する場合、S{1,2}
のS
を省略して{1,2}
と書くことができます。この場合、{1,2}
は文脈からS
型であることが推論されるため、「暗黙的な型」を持つ構造体リテラルと見なされます。
このコミット以前のgc
の挙動では、このような暗黙的な型を持つ構造体リテラルが、コンパイラ内部で処理され、最終的にエクスポートされる際に、不必要にその型情報(例えばS{}
のような形式)が出力されてしまう問題がありました。これは、コンパイラの出力が冗長になるだけでなく、場合によっては構文的に正しくない、または意図しない出力となる可能性がありました。
コミットメッセージにある「discussion around 2678」は、この問題に関するコミュニティでの議論やバグ報告を指していると考えられます。開発者は、この冗長な型情報の出力を抑制し、よりクリーンで正確なコンパイラ出力を実現するためにこの変更を行いました。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造とGo言語の構文に関する知識が必要です。
- Goコンパイラ (
gc
): Go言語の公式コンパイラであり、ソースコードを機械語に変換する役割を担います。src/cmd/gc
ディレクトリ以下にそのソースコードがあります。 - 構造体リテラル (Struct Literal): Go言語で構造体の値を初期化するための構文です。
TypeName{field1: value1, field2: value2}
またはTypeName{value1, value2}
のように記述します。 - 暗黙的な型 (Implicit Type): Go言語のコンテキストにおいて、コンパイラが文脈から型を推論できる場合に、明示的な型指定を省略できることがあります。構造体リテラルが配列やスライスの要素として使われる場合などがこれに該当します。
- エクスポート (Export): コンパイラが内部表現を外部形式(例えば、他のパッケージから参照されるためのバイナリ形式や、デバッグ情報など)に変換するプロセスを指します。このコミットでは、特にコンパイラがコードを「出力」する際のフォーマットに関する問題が扱われています。
Node
構造体: GoコンパイラのAST(Abstract Syntax Tree: 抽象構文木)における各ノードを表す内部構造体です。ソースコードの各要素(変数、関数、式など)がNode
として表現されます。implicit
フィールド:Node
構造体内のフィールドで、そのノードが表す要素が暗黙的に生成されたものか、または明示的に記述されたものかを示すフラグです。このコミットでは、このフィールドの扱いがより詳細になっています。fmt.c
: Goコンパイラのソースコード内で、ASTノードを文字列としてフォーマット(整形)し、出力する役割を担うファイルです。go.y
/y.tab.c
: Go言語の文法定義(Yacc形式)と、それから生成されるパーサーのコードです。ソースコードの解析時にASTノードのimplicit
フラグがどのように設定されるかに影響します。typecheck.c
: 型チェックを行うファイルです。Go言語の型規則に従って、ASTノードの型が正しいか検証します。
技術的詳細
このコミットの核心は、Goコンパイラが構造体リテラルを文字列として表現する際に、そのリテラルが暗黙的な型を持つ場合に、型名を省略して出力するように変更することです。
変更は主に以下のファイルにわたります。
-
src/cmd/gc/go.h
:Node
構造体のimplicit
フィールドの型がuchar
から、より意味のある列挙型(enum
)に変更されました。- 新しい列挙型
enum { Explicit, Implicit, ImplPtr }
が導入されました。Explicit
: 明示的に記述された要素。Implicit
: 暗黙的に型が推論された要素(出力時には型名を省略すべき)。ImplPtr
:&T{...}
のような構文で、内部的にポインタ型が暗黙的に追加された場合。
- これにより、
implicit
フラグが単なる真偽値ではなく、より詳細な状態を持つことができるようになり、コンパイラが異なる種類の「暗黙性」を区別できるようになりました。
-
src/cmd/gc/fmt.c
:exprfmt
関数(式をフォーマットする関数)内のOSTRUCTLIT
(構造体リテラル)の処理が変更されました。- 以前は、
fmtmode == FExp
(エクスポートモード)の場合、常に%T{
を使って型名と波括弧を出力していました。 - 変更後、
n->right->implicit == Implicit
の場合、つまり構造体リテラルが暗黙的な型を持つ場合は、fmtstrcpy(f, "{")
を使って型名を省略し、波括弧のみを出力するように修正されました。 - また、
OPTRLIT
(ポインタリテラル)の処理も同様に、fmtmode == FExp
かつn->left->right->implicit == Implicit
の場合に型名を省略するように変更されました。 nodedump
関数(ノードのデバッグ情報をダンプする関数)も、OTYPE
ノードの出力で%J
フォーマット指定子を追加し、Node
構造体のimplicit
フィールドの値をより詳細に表示できるように変更されています。
-
src/cmd/gc/go.y
およびsrc/cmd/gc/y.tab.c
:&T{...}
のような構文(複合リテラルのアドレス演算子)の処理において、生成される内部ノードのimplicit
フィールドが1
からImplPtr
に設定されるように変更されました。これは、ImplPtr
という新しい状態を導入したことによる整合性のための変更です。
-
src/cmd/gc/typecheck.c
:typecheckcomplit
関数(複合リテラルの型チェックを行う関数)内のポインタ型を持つ複合リテラルのチェックロジックが変更されました。- 以前は
!n->right->implicit
でチェックしていましたが、新しい列挙型に合わせてn->right->implicit == Explicit
でチェックするように変更されました。これにより、&T{...}
のようにImplPtr
が設定されている場合はエラーにならないようになります。
これらの変更により、コンパイラは構造体リテラルの「暗黙性」をより正確に認識し、エクスポート時に適切な形式で出力できるようになりました。
コアとなるコードの変更箇所
src/cmd/gc/fmt.c
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1150,11 +1150,16 @@ exprfmt(Fmt *f, Node *n, int prec)
return fmtprint(f, "%N{ %,H }", n->right, n->list);
case OPTRLIT:
+ if (fmtmode == FExp && n->left->right->implicit == Implicit)
+ return fmtprint(f, "%N", n->left);
return fmtprint(f, "&%N", n->left);
case OSTRUCTLIT:
if (fmtmode == FExp) { // requires special handling of field names
- fmtprint(f, "%T{\", n->type);\n+\t\t\tif(n->right->implicit == Implicit)\n+\t\t\t\tfmtstrcpy(f, \"{\");\n+\t\t\telse \n+\t\t\t\tfmtprint(f, "%T{\", n->type);\n for(l=n->list; l; l=l->next) {
// another special case: if n->left is an embedded field of builtin type,
// it needs to be non-qualified. Can't figure that out in %S, so do it here
@@ -1411,7 +1416,7 @@ nodedump(Fmt *fp, Node *n)
fmtprint(fp, "%O-%O%J", n->op, n->etype, n);
break;
case OTYPE:
- fmtprint(fp, "%O %S type=%T", n->op, n->sym, n->type);
+ fmtprint(fp, "%O %S%J type=%T", n->op, n->sym, n, n->type);
if(recur && n->type == T && n->ntype) {
indent(fp);
fmtprint(fp, "%O-ntype%N", n->op, n->ntype);
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -217,6 +217,13 @@ enum
EscNever,
};
+enum
+{
+ Explicit,
+ Implicit, // don't print in output
+ ImplPtr, // OIND added by &T{ ... } literal
+};
+
struct Node
{
// Tree structure.
@@ -252,7 +259,7 @@ struct Node
uchar used;\n uchar isddd;\n uchar readonly;\n-\tuchar\timplicit;\t// don't show in printout\n+\tuchar\timplicit;\t// Explicit, Implicit, ImplPtr. \n uchar addrtaken;\t// address taken, even if not moved to heap\n uchar dupok;\t// duplicate definitions ok (for func)\n
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -808,7 +808,7 @@ uexpr:
// Special case for &T{...}: turn into (*T){...}.
$$ = $2;
$$->right = nod(OIND, $$->right, N);
-\t\t\t$$->right->implicit = 1;\n+\t\t\t$$->right->implicit = ImplPtr;\n } else {
$$ = nod(OADDR, $2, N);
}
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2047,10 +2047,9 @@ typecheckcomplit(Node **np)
n->type = t;
if(isptr[t->etype]) {
-\t\t// For better or worse, we don't allow pointers as
-\t\t// the composite literal type, except when using
-\t\t// the &T syntax, which sets implicit.\n-\t\tif(!n->right->implicit) {\n+\t\t// For better or worse, we don't allow pointers as the composite literal type,\n+\t\t// except when using the &T syntax, which sets implicit to ImplPtr.\n+\t\tif(n->right->implicit == Explicit) {\n yyerror("invalid pointer type %T for composite literal (use &%T instead)", t, t->type);
goto error;
}
コアとなるコードの解説
src/cmd/gc/fmt.c
の変更
このファイルは、GoコンパイラがAST(抽象構文木)を文字列として出力する際のフォーマットを制御します。
-
OPTRLIT
(ポインタリテラル) の処理:if (fmtmode == FExp && n->left->right->implicit == Implicit)
の条件が追加されました。fmtmode == FExp
は、コンパイラがエクスポートモード(つまり、他のパッケージから参照される形式でコードを出力するモード)であることを示します。n->left->right->implicit == Implicit
は、ポインタリテラルの基となる式が暗黙的な型を持つことを示します。- この条件が真の場合、
fmtprint(f, "%N", n->left)
が実行され、ポインタリテラルの型情報(&T
のT
の部分)が省略されて出力されます。これにより、&{1,2}
のような形式で出力されるようになります。
-
OSTRUCTLIT
(構造体リテラル) の処理:if (fmtmode == FExp)
ブロック内で、構造体リテラルの型を出力する部分が変更されました。- 以前は常に
fmtprint(f, "%T{", n->type)
で型名を出力していましたが、 if(n->right->implicit == Implicit)
の条件が追加されました。- この条件が真の場合、
fmtstrcpy(f, "{")
が実行され、型名が省略されて波括弧のみが出力されます。これにより、{1,2}
のような形式で出力されるようになります。 - それ以外の場合(
Explicit
またはImplPtr
の場合)は、従来通りfmtprint(f, "%T{", n->type)
で型名が出力されます。
-
nodedump
関数の変更:- デバッグ目的でノード情報をダンプする
nodedump
関数において、OTYPE
ノードの出力に%J
フォーマット指定子が追加されました。これは、Node
構造体のimplicit
フィールドの値をより詳細に表示するためのものです。
- デバッグ目的でノード情報をダンプする
これらの変更により、コンパイラはエクスポート時に、暗黙的な型を持つ構造体リテラルやポインタリテラルの型情報を不必要に出力しなくなり、より簡潔で正しいコードを生成できるようになります。
src/cmd/gc/go.h
の変更
このファイルは、Goコンパイラの内部で使用されるデータ構造や定数を定義するヘッダーファイルです。
-
enum
の追加:Explicit
,Implicit
,ImplPtr
という3つの値を持つ新しい列挙型が追加されました。Implicit
には// don't print in output
というコメントがあり、この値が設定された場合は出力時に型を省略すべきであることが明示されています。ImplPtr
には// OIND added by &T{ ... } literal
というコメントがあり、&T{...}
のような構文で内部的に追加されるポインタ型であることを示しています。
-
Node
構造体のimplicit
フィールドの変更:uchar implicit;
のコメントが// don't show in printout
から// Explicit, Implicit, ImplPtr.
に変更されました。- これにより、
implicit
フィールドが単なる真偽値ではなく、より詳細な状態を表現できるようになり、コンパイラが異なる種類の「暗黙性」を区別して処理できるようになりました。
src/cmd/gc/go.y
および src/cmd/gc/y.tab.c
の変更
これらのファイルは、Go言語の文法定義と、それから生成されるパーサーのコードです。
&T{...}
構文の処理:uexpr
ルール(単項式)内で、&T{...}
のような構文が解析される際に、生成される内部ノードのimplicit
フィールドが1
(真) からImplPtr
に変更されました。- これは、
go.h
で導入された新しい列挙型に合わせて、より正確な意味を割り当てるための変更です。&T{...}
は、ユーザーが明示的に&
を付けているため、完全に暗黙的とは異なる特別なケースとして扱われます。
src/cmd/gc/typecheck.c
の変更
このファイルは、Goコンパイラが型チェックを行う際のロジックを含んでいます。
typecheckcomplit
関数の変更:- 複合リテラルの型チェックを行う
typecheckcomplit
関数内で、ポインタ型を持つ複合リテラルのチェックロジックが変更されました。 - 以前は
if(!n->right->implicit)
でチェックしていましたが、新しい列挙型に合わせてif(n->right->implicit == Explicit)
に変更されました。 - この変更により、
&T{...}
のようにImplPtr
が設定されている場合はエラーにならず、明示的にポインタ型を複合リテラルとして使用しようとした場合(例:*T{...}
)のみエラーとなるようになります。
- 複合リテラルの型チェックを行う
これらの変更は、Goコンパイラが構造体リテラルの「暗黙性」をより正確に管理し、エクスポート時に適切な形式で出力するための重要な改善です。
関連リンク
- Go言語の公式ウェブサイト: https://golang.org/
- Go言語のソースコードリポジトリ: https://github.com/golang/go
参考にした情報源リンク
- Go言語のコンパイラに関するドキュメントやブログ記事 (一般的なGoコンパイラの構造理解のため)
- Go言語の仕様書 (構造体リテラルや型の推論に関する理解のため)
- Gitのコミットログと差分 (このコミットの具体的な変更内容を把握するため)
- Go言語のIssueトラッカー (Issue 2678に関する議論の可能性を調査するため)
- Go言語のコードレビューシステム (CL 5534077に関する詳細な議論を調査するため)
(注: Issue 2678およびCL 5534077の具体的な内容は、公開されている情報からは特定できませんでしたが、コミットメッセージとコードの変更内容からその意図を推測し解説しました。)