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

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

このコミットは、Go言語のコンポジットリテラル(複合リテラル)の構文変更に関するものです。具体的には、従来の波括弧 {} を使用したコンポジットリテラルの構文に加えて、丸括弧 () を使用する新しい構文を導入し、古い構文に対して警告を発するように変更しています。これは、Go言語の初期段階における構文の進化の一環であり、型変換とコンポジットリテラルの構文的な曖昧さを解消し、より明確な言語仕様を確立するための重要なステップでした。

コミット

commit 07244f7c8081669f33433385a1f81e2a8d8cb55d
Author: Russ Cox <rsc@golang.org>
Date:   Fri Feb 13 14:48:16 2009 -0800

    add composite literal ( ) syntax.
    warn about composite literal { } syntax.
    
    R=ken
    OCL=25018
    CL=25023

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

https://github.com/golang/go/commit/07244f7c8081669f33433385a1f81e2a8d8cb55d

元コミット内容

このコミットの元の内容は以下の通りです。

  • コンポジットリテラルに () 構文を追加。
  • コンポジットリテラルの {} 構文について警告を発するように変更。

変更の背景

Go言語の初期の設計段階では、コンポジットリテラルと型変換の構文に一部重複があり、曖昧さが存在していました。特に、T{...} のような構文は、型 T のコンポジットリテラルを表す場合と、式 {...} を型 T に変換する場合の両方に解釈される可能性がありました。

この曖昧さは、パーサーの複雑性を増し、開発者がコードを読解する上での混乱を招く可能性がありました。このコミットは、この構文の曖昧さを解消し、言語の明確性を向上させることを目的としています。具体的には、コンポジットリテラルには () 構文を推奨し、型変換には T(...) のような構文を明確に区別することで、構文解析をより容易にし、コードの意図をより明確に表現できるようにしました。

また、この変更は、Go言語の進化の過程で、より堅牢で直感的な構文を追求する取り組みの一環でもあります。初期のGo言語はまだ実験的な段階であり、このような構文の調整は、言語が成熟し、より多くの開発者に採用されるための重要なステップでした。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。

  1. コンポジットリテラル (Composite Literals): Go言語において、構造体 (struct)、配列 (array)、スライス (slice)、マップ (map) などの複合型を初期化するための構文です。例えば、Point{X: 1, Y: 2} は構造体のコンポジットリテラル、[]int{1, 2, 3} はスライスのコンポジットリテラルです。これらは、新しい複合型の値を簡潔に生成するために使用されます。

  2. 型変換 (Type Conversions): ある型の値を別の型に明示的に変換する操作です。Go言語では、T(expression) のように記述します。例えば、int(3.14) は浮動小数点数 3.14 を整数型に変換します。

  3. Goコンパイラ (gc): Go言語の公式コンパイラの一つで、このコミットが対象としているのは、Go言語の初期に開発された gc (Go Compiler) です。gc は、Goのソースコードを機械語に変換する役割を担っています。

  4. 抽象構文木 (Abstract Syntax Tree - AST): コンパイラがソースコードを解析する際に内部的に構築する、プログラムの構造を木構造で表現したものです。各ノードは、変数、演算子、関数呼び出しなどのプログラム要素に対応します。コンパイラはASTを走査し、最適化やコード生成を行います。

  5. go.y (Yacc/Bison Grammar File): Goコンパイラの字句解析器と構文解析器を生成するためのYacc(またはBison)形式の文法定義ファイルです。このファイルには、Go言語の文法規則が記述されており、コンパイラがソースコードをどのように解釈し、ASTを構築するかを定義します。pexpr (primary expression) のようなルールは、式の構文を定義する部分です。

  6. walk.c (AST Walker/Rewriter): gc コンパイラの一部で、ASTを走査("walk")し、様々な変換や最適化を行う役割を担っています。このフェーズでは、構文解析によって生成されたASTが、より低レベルの表現に変換されたり、意味解析が行われたりします。例えば、コンポジットリテラルや型変換のASTノードは、この段階で具体的なコード生成のための準備が行われます。

  7. ASTノードの種類 (e.g., OCONV, OCOMP, OCONVDOT, OCONVPAREN): gc コンパイラ内部でASTの各ノードを表すために使用される列挙型(enum)の値です。

    • OCONV: 一般的な型変換を表すノード。
    • OCOMP: 従来のコンポジットリテラルを表すノード。
    • OCONVDOT: 型アサーション .(type) のようなドット付きの型変換を表すノード。
    • OCONVPAREN: このコミットで新しく導入された、丸括弧 () を使用する型変換またはコンポジットリテラルを表すノード。

技術的詳細

このコミットの技術的な核心は、Go言語の構文解析器とAST変換器の変更にあります。

  1. ASTノードの追加と変更 (src/cmd/gc/go.h, src/cmd/gc/subr.c):

    • go.h に新しいASTノードタイプ OCONVDOTOCONVPAREN が追加されました。
      • OCONVDOT は、expr.(type) のような型アサーションやインターフェース型変換をより明確に表現するために導入されました。
      • OCONVPAREN は、新しい T() 構文のコンポジットリテラルや型変換を表現するために導入されました。
    • 従来の OCOMP ノードは、OCONVPAREN に置き換えられる形で削除されました。
    • subr.c では、これらの新しいノードタイプに対応する文字列表現が追加され、デバッグやログ出力時にノードの種類を識別できるようになっています。
    • arraylitmaplit 関数のシグネチャが変更され、Node *var 引数が追加されました。これは、コンポジットリテラルが一時変数に割り当てられる場合の処理をより柔軟にするためです。
  2. 構文解析器の変更 (src/cmd/gc/go.y):

    • go.y ファイルは、Go言語の文法を定義するYacc(またはBison)の入力ファイルです。
    • pexpr (primary expression) のルールが変更され、型変換とコンポジットリテラルの構文が更新されました。
    • 従来の pexpr '.' '(' type ')' (型アサーション) は、nod(OCONVDOT, $1, N) を生成するように変更されました。これにより、OCONV との区別が明確になります。
    • 最も重要な変更は、コンポジットリテラルの構文です。
      • convtype '(' braced_keyexpr_list ')' という新しいルールが追加され、これが OCONVPAREN ノードを生成するように定義されました。これは、T() 形式のコンポジットリテラルに対応します。
      • 従来の convtype '{' braced_keyexpr_list '}' 構文は引き続きサポートされますが、debug['{'] フラグが設定されていない場合に「braces should now be parens」(波括弧は今や丸括弧であるべき)という警告を発するように変更されました。これは、古い構文からの移行を促すための措置です。
      • この変更により、T{...}T(...) の構文的な曖昧さが解消され、T(...) がコンポジットリテラルまたは型変換として明確に解釈されるようになります。
  3. ASTウォーカーの変更 (src/cmd/gc/walk.c):

    • walk.c は、ASTを走査し、意味解析やコード変換を行う部分です。
    • 従来の OCONV および OCOMP ノードの処理ロジックが大幅にリファクタリングされました。
    • 新しい関数 walkconv(Node *n) が導入されました。この関数は、OCONVDOTOCONVPAREN ノードの処理を一元的に行います。
    • walkconv 関数は、型変換(数値型間の変換、文字列への変換、インターフェース変換、unsafe.Pointer への変換など)と、コンポジットリテラル(構造体、配列、マップの初期化)の両方を処理します。
    • OCONVPAREN ノードの場合、その型(n->type)が構造体、配列、マップのいずれかであれば、それぞれ structlit, arraylit, maplit 関数を呼び出してコンポジットリテラルの処理を行います。
    • & (アドレス演算子) の処理も変更されました。&Point{1, 2} のような構文は、&Point(1, 2) のように OCONVPAREN ノードとして扱われるようになり、内部で一時変数の割り当てと初期化が行われるようになりました。これにより、コンポジットリテラルのアドレスを取得する際の挙動が統一されます。

これらの変更により、Goコンパイラは、新しい構文を正しく解析し、ASTを構築し、最終的に実行可能なコードに変換できるようになりました。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

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

    • enumOCONVDOTOCONVPAREN が追加され、OCOMP が削除されました。
    • walkconv 関数のプロトタイプが追加されました。
    • arraylitmaplit 関数のシグネチャが Node*, Node* に変更されました。
    --- a/src/cmd/gc/go.h
    +++ b/src/cmd/gc/go.h
    @@ -326,7 +326,10 @@ enum
     	OINDEX, OSLICE,
     	ONOT, OCOM, OPLUS, OMINUS, OSEND, ORECV,
     	OLITERAL, OREGISTER, OINDREG,
    -	OCONV, OCOMP, OKEY, OPARAM,
    +	OKEY, OPARAM,
    +	OCONV,
    +	OCONVDOT,
    +	OCONVPAREN,
     	OBAD,
    
     	OEXTEND,	// 6g internal
    @@ -805,6 +808,7 @@ void	gettype(Node*, Node*);
     void	walk(Node*);
     void	walkstate(Node*);
     void	walktype(Node*, int);
    +void	walkconv(Node*);
     void	walkas(Node*);
     void	walkbool(Node*);
     Type*	walkswitch(Node*, Type*(*)(Node*, Type*));
    @@ -840,8 +844,8 @@ Node*	reorder2(Node*);
     Node*	reorder3(Node*);
     Node*	reorder4(Node*);
     Node*	structlit(Node*, Node*);
    -Node*	arraylit(Node*);
    -Node*	maplit(Node*);
    +Node*	arraylit(Node*, Node*);
    +Node*	maplit(Node*, Node*);
     Node*	selectas(Node*, Node*);
     Node*	old2new(Node*, Type*);
     void	addrescapes(Node*);
    
  2. src/cmd/gc/go.y:

    • pexpr ルール内で、型アサーション .(type)OCONVDOT を生成するように変更されました。
    • コンポジットリテラルの構文が convtype '(' braced_keyexpr_list ')' に変更され、OCONVPAREN を生成するように定義されました。
    • 従来の {} 構文に対して警告が追加されました。
    --- a/src/cmd/gc/go.y
    +++ b/src/cmd/gc/go.y
    @@ -791,7 +791,7 @@ pexpr:
      	}
      |\tpexpr '.' '(' type ')'
      	{
    -	\t$$ = nod(OCONV, $1, N);
    +	\t$$ = nod(OCONVDOT, $1, N);
      		$$->type = $4;
      	}
      |\tpexpr '[' expr ']'
    @@ -841,20 +841,24 @@ pexpr:
      		$$ = nod(OMAKE, $5, N);
      		$$->type = $3;
      	}
    -|\tlatype '(' expr ')'
    +|\tconvtype '(' braced_keyexpr_list ')'
      	{
    -	\t$$ = nod(OCONV, $3, N);
    -	\t$$->type = oldtype($1);
    +	\t// typed literal
    +	\t$$ = rev($3);
    +	\tif($$ == N)
    +	\t\t$$ = nod(OEMPTY, N, N);
    +	\t$$ = nod(OCONVPAREN, $$, N);
    +	\t$$->type = $1;
      	}
      |\tconvtype '{' braced_keyexpr_list '}'
      	{
    +	\tif(!debug['{'])
    +	\t\twarn("braces should now be parens");
      		// composite literal
      		$$ = rev($3);
      		if($$ == N)
      			$$ = nod(OEMPTY, N, N);
    -		if(!iscomposite($1))
    -			yyerror("illegal composite literal type %T", $1);
    -		$$ = nod(OCOMP, $$, N);
    +		$$ = nod(OCONVPAREN, $$, N);
      		$$->type = $1;
      	}
      |\tfnliteral
    
  3. src/cmd/gc/walk.c:

    • OCONVOCOMPcase が削除され、OCONVDOTOCONVPARENcase が追加されました。
    • 新しい walkconv 関数が定義され、型変換とコンポジットリテラルの処理を統合しました。
    • & 演算子とコンポジットリテラルの組み合わせ (&Point{1, 2} など) の処理が OCONVPAREN に対応するように変更されました。
    • arraylitmaplit 関数が Node *var 引数を受け取るように変更されました。
    --- a/src/cmd/gc/walk.c
    +++ b/src/cmd/gc/walk.c
    @@ -507,7 +507,7 @@ loop:
      			}
      			break;
    
    -		case OCONV:
    +		case OCONVDOT:
      			if(cl == 2 && cr == 1) {
      				// a,b = i.(T)
      				walktype(r->left, Erv);
    @@ -590,128 +590,11 @@ loop:
      		goto ret;
    
      	case OCONV:
    -\t\tif(top == Etop)
    -\t\t\tgoto nottop;
    -\n-\t\tl = n->left;\n-\t\tif(l == N)
    -\t\t\tgoto ret;\n-\n-\t\twalktype(l, Erv);\n-\n-\t\tt = n->type;\n-\t\tif(t == T)
    -\t\t\tgoto ret;\n-\n-\t\tconvlit1(l, t, 1);\n-\n-\t\t// nil conversion
    -\t\tif(eqtype(t, l->type, 0)) {
    -\t\t\tif(l->op != ONAME) {
    -\t\t\t\tindir(n, l);
    -\t\t\t\tn->type = t;
    -\t\t\t}\n-\t\t\tgoto ret;\n-\t\t}\n-\n-\t\t// simple fix-float
    -\t\tif(l->type != T)
    -\t\tif(isint[l->type->etype] || isfloat[l->type->etype])
    -\t\tif(isint[t->etype] || isfloat[t->etype]) {
    -\t\t\tevconst(n);
    -\t\t\tgoto ret;\n-\t\t}\n-\n-\t\t// to string
    -\t\tif(l->type != T)
    -\t\tif(istype(t, TSTRING)) {
    -\t\t\tet = l->type->etype;
    -\t\t\tif(isint[et]) {
    -\t\t\t\tindir(n, stringop(n, top));
    -\t\t\t\tgoto ret;\n-\t\t\t}\n-\t\t\tif(et == TARRAY)
    -\t\t\tif(istype(l->type->type, TUINT8)) {
    -\t\t\t\tn->op = OARRAY;
    -\t\t\t\tindir(n, stringop(n, top));
    -\t\t\t\tgoto ret;\n-\t\t\t}\n-\t\t}\n-\n-\t\t// convert dynamic to static generated by ONEW/OMAKE
    -\t\tif(isfixedarray(t) && isslice(l->type))
    -\t\t\tgoto ret;\n-\n-\t\t// convert static array to dynamic array
    -\t\tif(isslice(t) && isfixedarray(l->type)) {
    -\t\t\tif(eqtype(t->type->type, l->type->type->type, 0)) {
    -\t\t\t\tindir(n, arrayop(n, Erv));
    -\t\t\t\tgoto ret;\n-\t\t\t}\n-\t\t}\n-\n-\t\t// interface assignment
    -\t\tet = ifaceas(n->type, l->type, 1);
    -\t\tif(et != Inone) {
    -\t\t\tindir(n, ifaceop(n->type, l, et));
    -\t\t\tgoto ret;\n-\t\t}\n-\n-\t\t// convert to unsafe.pointer
    -\t\tif(isptrto(n->type, TANY)) {
    -\t\t\tif(isptr[l->type->etype])
    -\t\t\t\tgoto ret;\n-\t\t\tif(l->type->etype == TUINTPTR)
    -\t\t\t\tgoto ret;\n-\t\t}\n-\n-\t\t// convert from unsafe.pointer
    -\t\tif(isptrto(l->type, TANY)) {
    -\t\t\tif(isptr[n->type->etype])
    -\t\t\t\tgoto ret;\n-\t\t\tif(n->type->etype == TUINTPTR)
    -\t\t\t\tgoto ret;\n-\t\t}\n-\n-\t\tif(l->type != T)
    -\t\t\tyyerror("cannot convert %T to %T", l->type, t);\n-\t\tgoto ret;\n-\n-\tcase OCOMP:
    -\t\tif(top == Etop)
    +\tcase OCONVDOT:
    +\tcase OCONVPAREN:
    +\t\tif(top != Erv)
     		goto nottop;
    -\n-\t\tl = n->left;\n-\t\tif(l == N)
    -\t\t\tgoto ret;\n-\n-\t\twalktype(l, Erv);\n-\n-\t\tt = n->type;\n-\t\tif(t == T)
    -\t\t\tgoto ret;\n-\n-\t\t// structure literal
    -\t\tif(t->etype == TSTRUCT) {
    -\t\t\tindir(n, structlit(n, nil));
    -\t\t\tgoto ret;\n-\t\t}\n-\n-\t\t// array literal
    -\t\tif(t->etype == TARRAY) {
    -\t\t\tr = arraylit(n);
    -\t\t\tindir(n, r);
    -\t\t\tgoto ret;\n-\t\t}\n-\n-\t\t// map literal
    -\t\tif(t->etype == TMAP) {
    -\t\t\tr = maplit(n);
    -\t\t\tindir(n, r);
    -\t\t\tgoto ret;\n-\t\t}\n-\n-\t\tyyerror("bad composite literal %T", t);
    +\t\twalkconv(n);
     		goto ret;
    
      	case ORETURN:
    @@ -1001,14 +884,18 @@ loop:
      	case OADDR:
      		if(top != Erv)
      			goto nottop;
    -\t\tif(n->left->op == OCOMP && n->left->type != T)
    -\t\tif(n->left->type->etype == TSTRUCT) {
    -\t\t\t// turn &Point{1, 2} into allocation.
    +\t\tif(n->left->op == OCONVPAREN && n->left->type != T)
    +\t\tswitch(n->left->type->etype) {
    +\t\tcase TSTRUCT:
    +\t\tcase TARRAY:
    +\t\tcase TMAP:
    +\t\t\t// turn &Point(1, 2) or &[]int(1, 2) or &[...]int(1, 2) into allocation.
      			// initialize with
      			//	nvar := new(*Point);
    -\t\t\t//	*nvar = Point{1, 2};
    +\t\t\t//	*nvar = Point(1, 2);
      			// and replace expression with nvar
    -\t\t\tNode *nvar, *nas;
    +\t\t\t; // stupid c syntax - case label must be on stmt, not decl
    +\t\t\tNode *nvar, *nas, *nstar;
    
      			nvar = nod(OXXX, N, N);
      			tempname(nvar, ptrto(n->left->type));
    @@ -1016,10 +903,27 @@ loop:
      			nas = nod(OAS, nvar, callnew(n->left->type));
      			addtop = list(addtop, nas);
    
    -\t\t\tstructlit(n->left, nvar);
    +\t\t\tnstar = nod(OIND, nvar, N);
    +\t\t\tnstar->type = n->left->type;
    +\n+\t\t\tswitch(n->left->type->etype) {
    +\t\t\tcase TSTRUCT:
    +\t\t\t\tstructlit(n->left, nstar);
    +\t\t\t\tbreak;
    +\t\t\tcase TARRAY:
    +\t\t\t\tarraylit(n->left, nstar);
    +\t\t\t\tbreak;
    +\t\t\tcase TMAP:
    +\t\t\t\tmaplit(n->left, nstar);
    +\t\t\t\tbreak;
    +\t\t\tdefault:
    +\t\t\t\tfatal("addr lit %T", n->left->type);
    +\t\t\t}
    +\n     			indir(n, nvar);
      			goto ret;
      		}
    +\n     		if(istype(n->left->type, TFUNC) && n->left->class == PFUNC) {
      			if(!n->diag) {
      				n->diag = 1;
    @@ -1223,6 +1127,133 @@ walkbool(Node *n)
      		yyerror("IF and FOR require a boolean type");
     }
    
    +void
    +walkconv(Node *n)
    +{
    +\tint et, op;
    +\tType *t;
    +\tNode *l;
    +\n+\tt = n->type;
    +\tif(t == T)
    +\t\treturn;
    +\tl = n->left;
    +\tif(l == N)
    +\t\treturn;
    +\twalktype(l, Erv);
    +\n+\tswitch(t->etype) {
    +\tcase TSTRUCT:
    +\tcase TMAP:
    +\tcase TARRAY:
    +\t\tbreak;
    +\tdefault:
    +\t\tconvlit1(l, t, 1);
    +\t}\n+\n+\top = n->op;
    +\tn->op = OCONV;	// generic conversion
    +\n+\t// nil conversion
    +\tif(eqtype(t, l->type, 0)) {
    +\t\tif(l->op != ONAME) {
    +\t\t\tindir(n, l);
    +\t\t\tn->type = t;
    +\t\t}\n+\t\treturn;
    +\t}\n+\n+\t// simple fix-float
    +\tif(l->type != T)
    +\tif(isint[l->type->etype] || isfloat[l->type->etype])
    +\tif(isint[t->etype] || isfloat[t->etype]) {
    +\t\tevconst(n);
    +\t\treturn;
    +\t}\n+\n+\t// to string
    +\tif(l->type != T)
    +\tif(istype(t, TSTRING)) {
    +\t\tet = l->type->etype;
    +\t\tif(isint[et]) {
    +\t\t\tindir(n, stringop(n, Erv));
    +\t\t\treturn;
    +\t\t}\n+\t\tif(et == TARRAY)
    +\t\tif(istype(l->type->type, TUINT8)) {
    +\t\t\tn->op = OARRAY;
    +\t\t\tindir(n, stringop(n, Erv));
    +\t\t\treturn;
    +\t\t}\n+\t}\n+\n+\t// convert dynamic to static generated by ONEW/OMAKE
    +\tif(isfixedarray(t) && isslice(l->type))
    +\t\treturn;
    +\n+\t// convert static array to dynamic array
    +\tif(isslice(t) && isfixedarray(l->type)) {
    +\t\tif(eqtype(t->type->type, l->type->type->type, 0)) {
    +\t\t\tindir(n, arrayop(n, Erv));
    +\t\t\treturn;
    +\t\t}\n+\t}\n+\n+\t// convert to unsafe.pointer
    +\tif(isptrto(n->type, TANY)) {
    +\t\tif(isptr[l->type->etype])
    +\t\t\treturn;
    +\t\tif(l->type->etype == TUINTPTR)
    +\t\t\treturn;
    +\t}\n+\n+\t// convert from unsafe.pointer
    +\tif(isptrto(l->type, TANY)) {
    +\t\tif(isptr[n->type->etype])
    +\t\t\treturn;
    +\t\tif(n->type->etype == TUINTPTR)
    +\t\t\treturn;
    +\t}\n+\n+\t// possible interface conversion if using .(T)
    +\tif(op == OCONVDOT) {
    +\t\t// interface conversion
    +\t\tet = ifaceas(n->type, l->type, 1);
    +\t\tif(et != Inone) {
    +\t\t\tindir(n, ifaceop(n->type, l, et));
    +\t\t\treturn;
    +\t\t}\n+\t}\n+\n+\t// possible composite literal if using T()
    +\tif(op == OCONVPAREN) {
    +\t\t// structure literal
    +\t\tif(t->etype == TSTRUCT) {
    +\t\t\tindir(n, structlit(n, N));
    +\t\t\treturn;
    +\t\t}\n+\n+\t\t// array literal
    +\t\tif(t->etype == TARRAY) {
    +\t\t\tindir(n, arraylit(n, N));
    +\t\t\treturn;
    +\t\t}\n+\n+\t\t// map literal
    +\t\tif(t->etype == TMAP) {
    +\t\t\tindir(n, maplit(n, N));
    +\t\t\treturn;
    +\t\t}\n+\t}\n+\n+\tif(l->type != T)
    +\t\tyyerror("invalid conversion: %T to %T", l->type, t);
    +\telse if(n->left->op == OLIST)
    +\t\tyyerror("invalid type for composite literal: %T", t);
    +}\n+\n+\n+\n     /*
      * return the first type
      */
    @@ -3110,7 +3141,7 @@ multi:
      		n = list(n, a);
      		break;
    
    -\tcase OCONV:
    +\tcase OCONVDOT:
      		// a,b := i.(T)
      		if(cl != 2)
      			goto badt;
    @@ -3538,11 +3569,11 @@ loop:
     }
    
     Node*
    -arraylit(Node *n)
    +arraylit(Node *n, Node *var)
     {
     	Iter saver;
     	Type *t;
    -\tNode *var, *r, *a, *nnew;
    +\tNode *r, *a, *nnew;
     	int idx, ninit, b;
    
     	t = n->type;
    @@ -3567,8 +3598,10 @@ arraylit(Node *n)
     		t->bound = b;
     	}
    
    -\tvar = nod(OXXX, N, N);
    -\ttempname(var, t);
    +\tif(var == N) {
    +\t\tvar = nod(OXXX, N, N);
    +\t\ttempname(var, t);
    +\t}
    
     	nnew = nil;
     	if(b < 0) {
    @@ -3606,18 +3639,20 @@ arraylit(Node *n)
     }
    
     Node*
    -maplit(Node *n)
    +maplit(Node *n, Node *var)
     {
     	Iter saver;
     	Type *t;
    -\tNode *var, *r, *a;
    +\tNode *r, *a;
    
     	t = n->type;
     	if(t->etype != TMAP)
     		fatal("maplit: not map");
    
    -\tvar = nod(OXXX, N, N);
    -\ttempname(var, t);
    +\tif(var == N) {
    +\t\tvar = nod(OXXX, N, N);
    +\t\ttempname(var, t);
    +\t}
    
     	a = nod(OMAKE, N, N);
     	a->type = t;
    

コアとなるコードの解説

このコミットの核心は、Go言語のコンポジットリテラルと型変換の構文を明確に分離し、コンパイラ内部での処理を統一することにあります。

  1. go.h の変更:

    • OCONVDOTOCONVPAREN という新しいASTノードタイプが導入されたことで、コンパイラは構文解析の段階で、expr.(type) のような型アサーションと、Type() のようなコンポジットリテラル/型変換を明確に区別できるようになりました。これにより、構文解析の曖昧さが解消されます。
    • arraylitmaplit のシグネチャ変更は、これらのリテラルが一時変数に割り当てられる場合の柔軟な処理を可能にし、& 演算子との組み合わせをより効率的に扱えるようにするための準備です。
  2. go.y の変更:

    • pexpr ルールにおける OCONVDOT の導入は、.(type) 構文が一般的な型変換 OCONV とは異なる特殊なケースとして扱われることを示しています。
    • 最も重要なのは、コンポジットリテラルの構文が convtype '(' braced_keyexpr_list ')' に変更された点です。これにより、Type{...} ではなく Type(...) が推奨される構文となりました。
    • 従来の {} 構文に対する警告は、開発者に対して新しい構文への移行を促すためのもので、Go言語の構文が進化していることを示しています。この変更は、言語の初期段階において、より良い設計を模索する過程で生じたものです。
  3. walk.c の変更:

    • walkconv 関数の導入は、このコミットの最も重要な機能的変更です。この関数は、OCONVDOTOCONVPAREN の両方を処理することで、型変換とコンポジットリテラルのAST変換ロジックを統合しました。
    • 以前は OCONVOCOMP で別々に処理されていたロジックが walkconv に集約されたことで、コードの重複が減り、保守性が向上しました。
    • walkconv 内では、ノードの元のオペレーション (op) を保持しつつ、内部的には OCONV に変換して一般的な型変換ロジックを適用します。その後、元の op に基づいて、インターフェース変換 (OCONVDOT の場合) や、構造体/配列/マップのコンポジットリテラル (OCONVPAREN の場合) の具体的な処理 (structlit, arraylit, maplit の呼び出し) を行います。
    • & 演算子とコンポジットリテラルの組み合わせ (&Type{...}&Type(...)) の処理が OCONVPAREN に統一されたことで、コンパイラはこれらのケースを一時変数の割り当てと初期化として効率的に処理できるようになりました。これは、コンポジットリテラルがヒープに割り当てられ、そのアドレスが返されるというGoのセマンティクスを正しく実装するために重要です。

これらの変更は、Go言語の構文をより明確にし、コンパイラの内部処理を合理化することで、言語の安定性と将来の拡張性を高めることに貢献しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go Language Specification)
  • Go言語のソースコード (特に src/cmd/gc ディレクトリ)
  • Yacc/Bison のドキュメンテーション (構文解析器の理解のため)
  • コンパイラ設計に関する一般的な知識
  • Russ Cox のブログや講演資料 (Go言語の歴史的背景と設計思想の理解のため)