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

[インデックス 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コンパイラが構造体リテラルを文字列として表現する際に、そのリテラルが暗黙的な型を持つ場合に、型名を省略して出力するように変更することです。

変更は主に以下のファイルにわたります。

  1. src/cmd/gc/go.h:

    • Node構造体のimplicitフィールドの型がucharから、より意味のある列挙型(enum)に変更されました。
    • 新しい列挙型enum { Explicit, Implicit, ImplPtr }が導入されました。
      • Explicit: 明示的に記述された要素。
      • Implicit: 暗黙的に型が推論された要素(出力時には型名を省略すべき)。
      • ImplPtr: &T{...}のような構文で、内部的にポインタ型が暗黙的に追加された場合。
    • これにより、implicitフラグが単なる真偽値ではなく、より詳細な状態を持つことができるようになり、コンパイラが異なる種類の「暗黙性」を区別できるようになりました。
  2. 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フィールドの値をより詳細に表示できるように変更されています。
  3. src/cmd/gc/go.y および src/cmd/gc/y.tab.c:

    • &T{...}のような構文(複合リテラルのアドレス演算子)の処理において、生成される内部ノードのimplicitフィールドが1からImplPtrに設定されるように変更されました。これは、ImplPtrという新しい状態を導入したことによる整合性のための変更です。
  4. 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) が実行され、ポインタリテラルの型情報(&TTの部分)が省略されて出力されます。これにより、&{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言語のコンパイラに関するドキュメントやブログ記事 (一般的なGoコンパイラの構造理解のため)
  • Go言語の仕様書 (構造体リテラルや型の推論に関する理解のため)
  • Gitのコミットログと差分 (このコミットの具体的な変更内容を把握するため)
  • Go言語のIssueトラッカー (Issue 2678に関する議論の可能性を調査するため)
  • Go言語のコードレビューシステム (CL 5534077に関する詳細な議論を調査するため)

(注: Issue 2678およびCL 5534077の具体的な内容は、公開されている情報からは特定できませんでしたが、コミットメッセージとコードの変更内容からその意図を推測し解説しました。)