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

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

このコミットは、Go言語のパーサーにおいて、型の曖昧さを解消するために括弧 () を使用できるようにする変更を導入しています。これにより、複雑な型表現、特にチャネル型や関数型がネストされた場合に、パーサーが型を正しく解釈できるようになります。

コミット

commit ebc10db3e14facac2cb843e12486f1f2176727d9
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 18 10:07:46 2009 -0800

    allow parens to disambiguate types.
    examples:
    
            chan <- (chan int)
            chan (<- chan int)
            (map[string]func())("a": main)
    
    R=ken
    OCL=25151
    CL=25151

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

https://github.com/golang/go/commit/ebc10db3e14facac2cb843e12486f1f1f2176727d9

元コミット内容

allow parens to disambiguate types.
examples:

        chan <- (chan int)
        chan (<- chan int)
        (map[string]func())("a": main)

R=ken
OCL=25151
CL=25151

変更の背景

Go言語の初期段階では、型の構文解析において特定の状況で曖昧さが発生することがありました。特に、チャネル型や関数型が複雑に組み合わされた場合、パーサーが意図しない解釈をしてしまう可能性がありました。

例えば、chan <- chan int のような構文は、「int 型の値を送信するチャネルのチャネル」と解釈されるべきですが、パーサーによっては異なる解釈をする可能性がありました。このような曖昧さは、言語の表現力を制限し、開発者が意図した型を正確に記述することを困難にしていました。

このコミットは、C言語など他の言語で一般的な、括弧 () を使用して式の評価順序やグループ化を明示するのと同様に、Go言語の型構文においても括弧を導入することで、この曖昧さを解消し、より複雑で明確な型表現を可能にすることを目的としています。これにより、開発者は型の意図をより明確にパーサーに伝えることができるようになり、言語の柔軟性と表現力が向上しました。

前提知識の解説

Go言語の型システム

Go言語は静的型付け言語であり、変数は使用前に型を宣言する必要があります。Goの型システムはシンプルでありながら強力で、プリミティブ型(int, string, boolなど)、複合型(array, slice, map, struct)、ポインタ型、関数型、インターフェース型、チャネル型などがあります。

チャネル型 (Channel Types)

チャネルはGoの並行処理の根幹をなす要素であり、ゴルーチン間の通信に使用されます。チャネル型は chan ElementType の形式で宣言され、ElementType はチャネルが送受信するデータの型です。チャネルには送信専用 (chan<- ElementType)、受信専用 (<-chan ElementType)、双方向 (chan ElementType) の3種類があります。

構文解析 (Parsing) と Yacc/Bison

構文解析は、プログラミング言語のソースコードを解析し、その構造を理解するプロセスです。コンパイラやインタプリタの重要なフェーズの一つです。

go.y ファイルは、Goコンパイラのフロントエンドの一部である gc (Go Compiler) が使用するYacc (Yet Another Compiler Compiler) または Bison (GNU Parser Generator) の文法定義ファイルです。Yacc/Bisonは、BNF (Backus-Naur Form) に似た形式で文法規則を記述することで、プログラミング言語のパーサーを自動生成するためのツールです。

go.y のようなファイルでは、言語の構文要素(トークン、非終端記号)とそれらの組み合わせ方(規則)が定義されています。パーサーはこれらの規則に従って入力(ソースコード)を解析し、抽象構文木 (AST) を構築します。

曖昧な文法 (Ambiguous Grammar)

文法が曖昧であるとは、ある入力文字列に対して複数の有効な構文解析木が存在する場合を指します。プログラミング言語のパーサーは通常、曖昧さがないように設計されるか、曖昧さを解決するための優先順位規則や結合規則が定義されます。しかし、複雑な構文では、予期せぬ曖昧さが発生することがあります。

このコミットのケースでは、Goの型構文において、特にチャネル型や関数型がネストされた場合に、パーサーが型の境界を正しく識別できない曖昧さがあったと考えられます。括弧は、このような曖昧さを解消し、開発者の意図を明確にパーサーに伝えるための一般的なメカニズムです。

技術的詳細

このコミットの技術的詳細は、Goコンパイラの構文解析器 (src/cmd/gc/go.y) に、型定義内で括弧 () を使用して型をグループ化する新しい規則を追加することにあります。

具体的には、以下の3つの非終端記号(文法規則)に括弧で囲まれた型を許容する規則が追加されました。

  1. convtype:

    • この規則は、型変換の文脈で使用される型を定義します。例えば、T(expr) のような型変換の T の部分です。
    • 追加された規則: | '(' type ')' { $$ = $2; }
    • これは、「左括弧、type、右括弧」というシーケンスが convtype として有効であることを意味します。アクション $2 は、括弧内の type の値をそのまま convtype の値として使用することを示します。
  2. Btype:

    • この規則は、基本的な型(Basic Type)を定義します。これは、チャネル型、関数型、構造体型など、より複雑な型を構成する際の基盤となる型です。
    • 追加された規則: | '(' type ')' { $$ = $2; }
    • convtype と同様に、括弧で囲まれた typeBtype として認識します。
  3. Bnon_chan_type:

    • この規則は、チャネル型ではない基本的な型を定義します。これは、チャネルの送受信方向を決定する際に、チャネルではない型を区別するために使用されます。
    • 追加された規則: | '(' Btype ')' { $$ = $2; }
    • ここでも、括弧で囲まれた BtypeBnon_chan_type として認識します。

これらの変更により、パーサーは (chan int)(<- chan int) のような括弧で囲まれた型表現を単一の型として正しく解釈できるようになりました。これにより、例えば chan <- (chan int) のような構文において、chan int が独立した型として認識され、そのチャネルが別のチャネル型を送信するチャネルとして正しく解析されるようになります。

また、test/parentype.go という新しいテストファイルが追加され、これらの新しい構文が正しく機能することを確認しています。このテストファイルには、f((map[string]string)("a":"b","c":"d"))f(make(chan(<-chan int))) のような、括弧で型を明示的にグループ化した例が含まれています。

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

src/cmd/gc/go.y ファイルへの変更:

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1004,6 +1004,10 @@ convtype:
 		$$->type = $5;
 	}
 |	structtype
+|\t'(' type ')'
+\t{
+\t\t$$ = $2;
+\t}
 
 /*
  * to avoid parsing conflicts, type is split into
@@ -1031,6 +1035,10 @@ Btype:
 |	Bchantype
 |	Bfntype
 |	Bothertype
+|\t'(' type ')'
+\t{
+\t\t$$ = $2;
+\t}
 
 non_name_type:
 	chantype
@@ -1052,6 +1060,10 @@ Bnon_chan_type:
 	nametype
 |	Bfntype
 |	Bothertype
+|\t'(' Btype ')'
+\t{
+\t\t$$ = $2;
+\t}
 
 Anon_fn_type:
 	Achantype
@@ -1062,7 +1074,6 @@ Bnon_fn_type:
 |	Bchantype
 |	Bothertype
 
-
 nametype:
 	LATYPE
 	{

test/parentype.go ファイルの追加:

--- /dev/null
+++ b/test/parentype.go
@@ -0,0 +1,17 @@
+// $G $D/$F.go
+
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func f(interface{})
+func g() {}
+func main() {
+	f(map[string]string("a":"b","c":"d"));
+	f((map[string]string)("a":"b","c":"d"));
+	f((map[string]func())("a":g,"c":g));
+	f(make(chan(<-chan int)));
+	f(make(chan<-(chan int)));
+}

コアとなるコードの解説

src/cmd/gc/go.y の変更

このファイルはGoコンパイラの字句解析器/構文解析器の定義です。変更の核心は、convtypeBtypeBnon_chan_type という3つの文法規則に、括弧で囲まれた型を許容する新しいプロダクションルールを追加した点です。

  • convtype 規則への追加:

    |	'(' type ')'
    	{
    		$$ = $2;
    	}
    

    これは、convtype() で囲まれた type としても認識されることを意味します。$$ = $2; は、括弧内の type の構文木ノードを、この convtype 規則の最終的な結果として採用することを示しています。これにより、例えば (int) のような表現が型変換の文脈で有効な型として扱えるようになります。

  • Btype 規則への追加:

    |	'(' type ')'
    	{
    		$$ = $2;
    	}
    

    Btype はGoの基本的な型(プリミティブ型、複合型、チャネル型、関数型など)を定義する際に使用されます。この変更により、Btype も括弧で囲まれた type として認識されるようになります。これは、より複雑な型定義の中で、特定の型部分を明示的にグループ化する際に重要です。

  • Bnon_chan_type 規則への追加:

    |	'(' Btype ')'
    	{
    		$$ = $2;
    	}
    

    Bnon_chan_type はチャネル型ではない型を定義するために使用されます。この規則にも括弧で囲まれた Btype が追加されました。これにより、チャネルの方向性(送信専用、受信専用)を決定する際に、チャネルではない型が括弧でグループ化されていても正しく識別されるようになります。

これらの変更は、Go言語のパーサーが、開発者が意図する型の構造をより柔軟に、かつ曖昧さなく解釈できるようにするためのものです。特に、chan <- (chan int) のような、チャネル型の中に別のチャネル型がネストされるような複雑なケースで、括弧が型の境界を明確にする役割を果たします。

test/parentype.go の追加

この新しいテストファイルは、go.y の変更によって可能になった新しい型構文の有効性を検証するために作成されました。

  • f(map[string]string("a":"b","c":"d")); これは既存のマップリテラルの構文です。

  • f((map[string]string)("a":"b","c":"d")); 新しい構文の例です。マップ型 map[string]string が括弧で囲まれています。これにより、パーサーが map[string]string を単一の型として正しく認識し、その後に続くマップリテラルがその型の値として解釈されることを確認します。

  • f((map[string]func())("a":g,"c":g)); これも新しい構文の例で、マップの値が関数型である場合です。map[string]func() 型全体が括弧で囲まれています。

  • f(make(chan(<-chan int))); この例は、チャネルのチャネルを作成する際に、内側のチャネル型 <-chan int が括弧で囲まれているケースです。これにより、make 関数の引数として渡される型が chan (<-chan int) であることが明確になり、パーサーが正しく解釈できることを確認します。

  • f(make(chan<-(chan int))); これは、括弧がない場合の既存のチャネルのチャネルの構文です。新しい構文が既存の構文と競合しないことを確認するためにも重要です。

これらのテストケースは、括弧による型の曖昧さ解消が、マップリテラルやチャネル型を含む様々な複雑な型表現で正しく機能することを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントと仕様
  • Yacc/Bison の一般的な構文解析に関する情報
  • Go言語の初期の設計に関する議論(公開されているものがあれば)
  • コミットメッセージと変更されたコードの直接的な分析