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

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

このコミットは、Goコンパイラ(cmd/gc)におけるレシーバ型に対する括弧の使用制限を緩和するものです。具体的には、メソッドのレシーバ型を括弧で囲むことが許可されるようになります。この変更は、Go言語の仕様との整合性を高めることを目的としています。

変更されたファイルは以下の通りです。

  • src/cmd/gc/fmt.c: コンパイラのフォーマット関連のコード。OTPARENノードの処理が削除されています。
  • src/cmd/gc/go.h: コンパイラの内部で使用される定義(列挙型など)を含むヘッダファイル。OTPARENノードの定義が削除されています。
  • src/cmd/gc/go.y: Go言語の文法定義(Yacc/Bison形式)。レシーバ型における括弧の扱いが変更され、OTPARENノードが生成されなくなります。
  • src/cmd/gc/typecheck.c: コンパイラの型チェック関連のコード。OTPARENノードの型チェックロジックが削除されています。
  • src/cmd/gc/y.tab.c: go.yから生成されるYaccの出力ファイル。go.yの変更に伴い、関連するコードが更新されています。
  • test/fixedbugs/bug299.go: 修正されたバグのテストケース。以前はコンパイルエラーとなっていたレシーバ型の括弧使用が、この変更により合法となることを示しています。

コミット

commit 2565b5c06086488b2b23d48929803c8c3cec4400
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jun 25 09:57:48 2014 -0400

    cmd/gc: drop parenthesization restriction for receiver types
    
    Matches CL 101500044.
    
    LGTM=gri
    R=gri
    CC=golang-codereviews
    https://golang.org/cl/110160044

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

https://github.com/golang/go/commit/2565b5c06086488b2b23d48929803c8c3cec4400

元コミット内容

cmd/gc: drop parenthesization restriction for receiver types

Matches CL 101500044.

LGTM=gri
R=gri
CC=golang-codereviews
https://golang.org/cl/110160044

変更の背景

Go言語のメソッド宣言において、レシーバの型を括弧で囲む構文は、以前のGoコンパイラ(gc)では許可されていませんでした。しかし、Go言語の仕様上は、型定義において括弧を使用することは一般的に許容されており、レシーバ型もその例外ではありませんでした。この不一致は、コンパイラが仕様に完全に準拠していないことを意味していました。

このコミットの背景には、Go言語の仕様とコンパイラの実装との間の乖離を解消し、より柔軟な構文を許可するという目的があります。特に、test/fixedbugs/bug299.goの変更が示すように、以前はコンパイルエラーとなっていた(T)*(T)のようなレシーバ型が、この変更によって合法と見なされるようになります。これにより、開発者はより表現豊かなコードを書くことができ、コンパイラの挙動が仕様に厳密に合致するようになります。

コミットメッセージにあるMatches CL 101500044は、この変更が別のチェンジリスト(おそらく内部的なもの)と同期していることを示唆しています。これは、Goコンパイラの開発が複数の変更を並行して進める中で、一貫性を保つための一般的なプラクティスです。

前提知識の解説

このコミットの変更内容を理解するためには、以下の前提知識が必要です。

Go言語のメソッドとレシーバ

Go言語では、構造体などの型に「メソッド」を関連付けることができます。メソッドは、特定の型の値(またはポインタ)に対して操作を行う関数です。メソッドを定義する際には、関数名の前に「レシーバ」を指定します。レシーバは、そのメソッドがどの型の値に対して呼び出されるかを示します。

例:

type MyStruct struct {
    Value int
}

// 値レシーバのメソッド
func (s MyStruct) GetValue() int {
    return s.Value
}

// ポインタレシーバのメソッド
func (p *MyStruct) SetValue(newValue int) {
    p.Value = newValue
}

このコミットが扱うのは、このレシーバの型宣言部分、例えば(s MyStruct)(p *MyStruct)におけるMyStruct*MyStructの部分に括弧が使用された場合の挙動です。

Goコンパイラ(gc)の構造

Goコンパイラ(gc)は、Goのソースコードを機械語に変換するツールチェーンの一部です。その処理は、大まかに以下のフェーズに分けられます。

  1. 字句解析(Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。
  2. 構文解析(Syntactic Analysis): トークンのストリームを解析し、Go言語の文法規則に従っているかを確認します。このフェーズで抽象構文木(AST: Abstract Syntax Tree)が構築されます。ASTは、ソースコードの構造を木構造で表現したものです。
  3. 型チェック(Type Checking): ASTを走査し、各ノードの型が正しいか、型の一貫性が保たれているかなどを検証します。
  4. 最適化(Optimization): 生成されたコードを最適化します。
  5. コード生成(Code Generation): 最終的な機械語コードを生成します。

このコミットは主に構文解析と型チェックのフェーズに影響を与えます。

Yacc/Bisonとgo.y

go.yファイルは、Go言語の文法をYacc(Yet Another Compiler Compiler)またはBisonというツールが理解できる形式で記述したものです。Yacc/Bisonは、文法定義ファイル(.yファイル)を読み込み、C言語のパーサ(構文解析器)を生成します。生成されたパーサは、入力されたトークンストリームが文法に合致するかどうかを判断し、ASTを構築します。

go.yの中では、Go言語の様々な構文要素が「ルール」として定義されており、それぞれのルールには、その構文要素が認識されたときに実行されるC言語のコード(「アクション」と呼ばれます)が関連付けられています。このアクションの中で、ASTのノードが作成されたり、型情報が処理されたりします。

抽象構文木(AST)とノードの種類

ASTは、プログラムの構造を抽象的に表現した木構造のデータ構造です。各ノードは、プログラムの特定の構文要素(変数宣言、関数呼び出し、型など)を表します。Goコンパイラでは、これらのノードは内部的な列挙型(enum)で識別されます。

このコミットで特に重要なのはOTPARENというノードタイプです。これは、以前のコンパイラが「括弧で囲まれた型」を表現するために使用していた内部的なノードタイプでした。例えば、(T)という型がソースコードに現れた場合、コンパイラはこれをOTPARENノードとしてASTに表現していました。

技術的詳細

このコミットの技術的な核心は、Goコンパイラがレシーバ型における括弧を特別な構文要素として扱わないように変更することです。以前は、レシーバ型に括弧が出現すると、コンパイラはそれをOTPAREN(Operator Type Parenthesis)という特殊なASTノードとして表現し、そのノードに対して特定の制限(特にレシーバ型での使用禁止)を課していました。

変更前は、go.yの構文解析ルールにおいて、( ntype )という形式の型が検出されると、nod(OTPAREN, $2, N)というコードによってOTPARENノードが生成されていました。このOTPARENノードは、typecheck.cの型チェックフェーズで処理され、最終的には内部の型に変換されていました。しかし、fndcl(関数宣言、特にメソッド宣言)のルールでは、レシーバ型がOTPARENノードである場合、またはポインタ型でそのポインタの指す型がOTPARENノードである場合にエラーを発生させる明示的なチェックがありました。

このコミットでは、以下の変更が行われました。

  1. OTPARENノードの廃止:

    • src/cmd/gc/go.hからOTPARENの列挙型定義が削除されました。これは、このノードタイプがコンパイラの内部表現から完全に削除されることを意味します。
    • src/cmd/gc/fmt.copprec配列からOTPARENのエントリが削除され、exprfmt関数からもOTPARENケースの処理が削除されました。これにより、コンパイラがASTを文字列に変換する際にOTPARENノードを特別に扱う必要がなくなりました。
    • src/cmd/gc/typecheck.cからOTPARENケースの型チェックロジックが削除されました。これは、もはやOTPARENノードが存在しないため、その型チェックも不要になったことを意味します。
  2. 構文解析の変更:

    • src/cmd/gc/go.yにおいて、ntypeおよびnon_recvchantypeのルールで( ntype )が解析された際のアクションが変更されました。以前はnod(OTPAREN, $2, N)としてOTPARENノードを生成していましたが、変更後は単に$$ = $2となり、括弧の中の型ノードを直接使用するようになりました。これは、括弧が単なるグループ化の記号として扱われ、AST上では特別なノードを生成しないことを意味します。
    • src/cmd/gc/go.yfndclルールから、レシーバ型がOTPARENである場合にエラーを発生させる明示的なチェック(if(rcvr->right->op == OTPAREN || ...))が削除されました。これにより、レシーバ型に括弧を使用してもコンパイルエラーにならなくなります。

これらの変更により、コンパイラはレシーバ型における括弧を、他の場所での型定義と同様に、単なるグループ化の構文として透過的に扱うようになります。これにより、Go言語の仕様との整合性が向上し、より柔軟なコード記述が可能になります。

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

src/cmd/gc/fmt.c

--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -974,7 +974,6 @@ static int opprec[] = {
 	[OTFUNC] = 8,
 	[OTINTER] = 8,
 	[OTMAP] = 8,
-	[OTPAREN] = 8,
 	[OTSTRUCT] = 8,
 
 	[OINDEXMAP] = 8,
@@ -1140,9 +1139,6 @@ exprfmt(Fmt *f, Node *n, int prec)
 			return fmtprint(f, "[]%N", n->left);
 		return fmtprint(f, "[]%N", n->right);  // happens before typecheck
 
-	case OTPAREN:
-		return fmtprint(f, "(%N)", n->left);
-
 	case OTMAP:
 		return fmtprint(f, "map[%N]%N", n->left, n->right);

OTPARENに関するエントリとケースが削除されています。

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -574,7 +574,6 @@ enum
 	OTINTER,	// interface{}
 	OTFUNC,	// func()
 	OTARRAY,	// []int, [8]int, [N]int or [...]int
-	OTPAREN,	// (T)
 
 	// misc
 	ODDD,	// func f(args ...int) or f(l...) or var a = [...]int{0, 1, 2}.

OTPARENの列挙型定義が削除されています。

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1180,7 +1180,7 @@ ntype:
 |	dotname
 |	'(' ntype ')'
 	{
-		$$ = nod(OTPAREN, $2, N);
+		$$ = $2;
 	}
 
 non_expr_type:
@@ -1199,7 +1199,7 @@ non_recvchantype:
 |	dotname
 |	'(' ntype ')'
 	{
-		$$ = nod(OTPAREN, $2, N);
+		$$ = $2;
 	}
 
 convtype:
@@ -1366,8 +1366,6 @@ fndcl:
 			yyerror("bad receiver in method");
 			break;
 		}
-		if(rcvr->right->op == OTPAREN || (rcvr->right->op == OIND && rcvr->right->left->op == OTPAREN))
-			yyerror("cannot parenthesize receiver type");
 
 		t = nod(OTFUNC, rcvr, N);
 		t->list = $6;

ntypenon_recvchantypeのルールでOTPARENノードの生成が削除され、fndclルールからレシーバ型の括弧に関するエラーチェックが削除されています。

src/cmd/gc/typecheck.c

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -382,16 +382,6 @@ reswitch:
 		if(n->type == T)
 			goto error;
 		break;
-
-	case OTPAREN:
-		ok |= Etype;
-		l = typecheck(&n->left, Etype);
-		if(l->type == T)
-			goto error;
-		n->op = OTYPE;
-		n->type = l->type;
-		n->left = N;
-		break;
 	
 	case OTARRAY:
 		ok |= Etype;

OTPARENケースの型チェックロジックが削除されています。

src/cmd/gc/y.tab.c

go.yの変更に伴い、生成されたパーサコードの行番号参照などが更新されています。これはgo.yの変更の直接的な結果です。

test/fixedbugs/bug299.go

--- a/test/fixedbugs/bug299.go
+++ b/test/fixedbugs/bug299.go
@@ -21,7 +21,9 @@ type T struct {
 // legal according to spec
 func (p T) m() {}
 
-// not legal according to spec
-func (p (T)) f() {}   // ERROR "parenthesize|expected"
-func (p *(T)) g() {}  // ERROR "parenthesize|expected"
-func (p (*T)) h() {}  // ERROR "parenthesize|expected"
+// now legal according to spec
+func (p (T)) f() {}
+func (p *(T)) g() {}
+func (p (*T)) h() {}
+func (p (*(T))) i() {}
+func ((T),) j() {}

以前はエラーとなっていたレシーバ型の括弧使用が、コメントと共に合法とマークされ、さらに新しいテストケースが追加されています。

コアとなるコードの解説

このコミットの主要な変更は、Goコンパイラがレシーバ型における括弧を、特別な意味を持つ構文要素としてではなく、単なるグループ化の記号として扱うようにすることです。

  • OTPARENの削除: go.hからOTPARENというASTノードタイプが削除されたことで、コンパイラは括弧で囲まれた型を内部的に特別なノードとして表現する必要がなくなりました。これは、コンパイラのASTがよりシンプルになり、括弧の有無に関わらず同じ型として扱われることを意味します。
  • go.yでのAST生成の変更: go.yntypeおよびnon_recvchantypeルールにおいて、( ntype )が解析された際に、以前はOTPARENノードを生成していましたが、変更後は括弧の中の型ノード($2)を直接返すようになりました。これにより、構文解析の段階で括弧が「透過的」に扱われるようになります。
  • レシーバ型のエラーチェックの削除: go.yfndclルール(関数宣言、特にメソッド宣言を扱う部分)から、レシーバ型がOTPARENである場合にエラーを発生させる明示的なチェックが削除されました。これは、コンパイラがレシーバ型に括弧が使用されていても、それを不正な構文として扱わないことを保証します。
  • fmt.ctypecheck.cのクリーンアップ: OTPARENノードが廃止されたため、そのノードをフォーマットするロジック(fmt.c)や型チェックするロジック(typecheck.c)も不要となり、削除されました。これは、コンパイラのコードベースを整理し、不要な複雑さを取り除くための変更です。
  • test/fixedbugs/bug299.goの更新: このテストファイルは、以前はコンパイルエラーとなるべきコードとしてマークされていたレシーバ型の括弧使用例が、この変更によって合法となることを示しています。これは、コンパイラの挙動がGo言語の仕様に合致したことを検証する重要なテストです。

これらの変更により、Goコンパイラはレシーバ型における括弧の利用を、他の型表現と同様に、文法的に正しいものとして受け入れるようになります。

関連リンク

  • このコミットのGo Gerrit上のチェンジリスト: https://golang.org/cl/110160044
  • コミットメッセージに記載されているCL 101500044については、公開されているGoのチェンジリスト検索では見つかりませんでした。これは、Google内部のチェンジリストIDである可能性が高いです。

参考にした情報源リンク

  • Go言語仕様 (The Go Programming Language Specification): Go言語の文法とセマンティクスに関する公式ドキュメント。レシーバ型の構文に関する理解の基礎となります。
  • Goコンパイラのソースコード: cmd/gcディレクトリ内のファイルは、Goコンパイラの内部動作を理解するための主要な情報源です。
  • Yacc/Bisonのドキュメンテーション: go.yファイルの理解には、YaccまたはBisonの文法定義とアクションに関する知識が役立ちます。
  • 抽象構文木(AST)に関する一般的な情報: コンパイラの設計におけるASTの役割と構造に関する知識は、OTPARENノードの削除の意味を理解する上で重要です。
  • Go言語のメソッドに関するチュートリアルやドキュメント: Goのメソッドとレシーバの基本的な概念を再確認するために参照しました。
  • Goのバグトラッカーやメーリングリスト: 過去の議論やバグ報告が、特定の変更の背景を理解する手がかりとなることがあります。
  • Goのコミット履歴: 関連するコミットや以前の変更を追跡することで、このコミットの文脈をより深く理解できます。