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

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

このコミットは、Go言語のコンパイラ(cmd/gc)における型変換の構文解析に関する変更です。具体的には、関数呼び出しのように見えない型変換(例: []int(x))においても、末尾にオプションのカンマ(trailing comma)を許容するように文法が拡張されました。これにより、int(x,) のような関数呼び出し形式の型変換と、[]int(x,) のような非関数呼び出し形式の型変換の間で、末尾カンマの扱いに関する一貫性が確保されます。

コミット

commit ffc742b65803ac3ceee522b145380ad731317eed
Author: Russ Cox <rsc@golang.org>
Date:   Sun Feb 3 00:03:10 2013 -0500

    cmd/gc: allow new conversion syntax
    
    For consistency with conversions that look like function calls,
    conversions that don't look like function calls now allow an
    optional trailing comma.
    
    That is, int(x,) has always been syntactically valid.
    Now []int(x,) is valid too.
    
    Fixes #4162.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7288045

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

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

元コミット内容

このコミットの目的は、Go言語の型変換構文において、関数呼び出し形式ではない型変換(例: []byte("foo")chan int(nil))にも末尾カンマを許容することです。これまでは int(x,) のように関数呼び出しのように見える型変換では末尾カンマが許容されていましたが、それ以外の型変換では構文エラーとなっていました。この変更により、構文の一貫性が向上し、開発者がより柔軟にコードを記述できるようになります。

変更の背景

Go言語では、複合リテラル(composite literal)や関数呼び出しの引数リストなど、一部の構文要素で末尾カンマが許容されています。これは、要素の追加や削除、コードの自動生成などを行う際に、差分(diff)を最小限に抑え、Gitなどのバージョン管理システムでの変更履歴を読みやすくする利点があります。

しかし、型変換の構文においては、int(x,) のような形式(これは実質的に int 型への変換関数呼び出しと解釈できる)では末尾カンマが許容されていた一方で、[]byte("foo")chan int(nil) のような形式では許容されていませんでした。この不一致は、Go言語の構文の一貫性を損ねるものであり、Issue 4162 として報告されていました。

このコミットは、このIssueを解決し、型変換構文全体で末尾カンマの扱いを統一することを目的としています。これにより、Go言語の構文がより予測可能で、開発者にとって使いやすくなります。

前提知識の解説

このコミットを理解するためには、以下の知識が役立ちます。

  1. Go言語の型変換 (Type Conversion): Go言語における型変換は、ある型の値を別の型に変換する操作です。構文は Type(Expression) の形式を取ります。例えば、int(3.14) は浮動小数点数 3.14 を整数型 int に変換します。型変換は、基底型が同じである場合や、特定のルールに従って互換性がある場合にのみ可能です。

  2. 末尾カンマ (Trailing Comma): プログラミング言語において、リスト、配列、引数リストなどの最後の要素の後に置かれるオプションのカンマを指します。Go言語では、以下のような場所で末尾カンマが許容されます。

    • 複合リテラル(例: []int{1, 2, 3,}
    • 関数呼び出しの引数リスト(例: fmt.Println("hello", "world",)
    • 関数のパラメータリストや戻り値リスト
    • import 文や var 宣言のグループ化

    末尾カンマの利点は、主に以下の通りです。

    • 差分の最小化: 新しい要素を追加する際に、既存の行を変更する必要がなく、新しい行を追加するだけで済むため、バージョン管理システムでの差分が小さくなります。
    • コードの自動生成: コードジェネレータが要素リストを生成する際に、最後の要素に特別な処理を施す必要がなくなります。
    • 可読性: 特に複数行にわたるリストの場合、各行が同じ形式で終わるため、可読性が向上します。
  3. Yacc/Bison (Yet Another Compiler Compiler): Yaccは、LALR(1)パーサジェネレータの一種で、文法定義ファイル(通常 .y 拡張子)からC言語の構文解析器(パーサ)のソースコード(通常 y.tab.c)を生成します。Go言語のコンパイラ cmd/gc は、初期の段階でYaccを使用してGo言語の構文解析を行っていました。

    • .y ファイル: 文法規則と、各規則が認識されたときに実行されるアクション(C言語のコード)を記述します。
    • y.tab.c ファイル: .y ファイルからYaccによって生成されるC言語のソースコードで、実際の構文解析ロジックが含まれます。このファイルは通常、手動で編集されることはありません。
    • yerr.h ファイル: Yaccによって生成されるエラーメッセージ定義などが含まれるヘッダファイルです。
  4. Goコンパイラの構造 (cmd/gc): cmd/gc は、Go言語の公式コンパイラの一部であり、Goソースコードを中間表現に変換する役割を担っています。このコンパイラは、字句解析、構文解析、型チェック、最適化、コード生成などの段階を経て、最終的に実行可能なバイナリを生成します。このコミットで変更されているファイルは、主に構文解析の段階に関わるものです。

技術的詳細

このコミットの技術的な核心は、Go言語の構文解析器が、特定の型変換構文における末尾カンマを正しく認識できるように変更された点にあります。

Go言語の構文解析は、Yaccによって生成されたパーサ (y.tab.c) と、その元となる文法定義ファイル (go.y) に基づいています。

変更前は、go.yconvtype ルールにおいて、型変換は convtype '(' expr ')' の形式で定義されていました。これは、型変換の括弧内に式が1つだけ存在することを意味し、末尾カンマは許容されていませんでした。

このコミットでは、go.yconvtype ルールが convtype '(' expr ocomma ')' に変更されました。ここで重要なのは ocomma という新しい非終端記号です。

ocomma は、Yaccの文法定義において、オプションのカンマを表現するための一般的なパターンです。通常、ocomma は以下のように定義されます。

ocomma: /* empty */
      | ','
      ;

これは、「ocomma は何も存在しないか、またはカンマ1つが存在する」ということを意味します。この定義を convtype ルールに組み込むことで、型変換の括弧内の式に続くカンマがオプションとして扱われるようになります。

この go.y の変更に伴い、Yaccは y.tab.c を再生成します。y.tab.c の変更は、主にYaccが生成する内部テーブル(yyprhs, yyrhs, yydefact, yydefgoto, yypact, yytable, yycheck, yystos など)の更新によるものです。これらのテーブルは、パーサが入力トークンを読み込み、文法規則に基づいて構文木を構築するために使用されます。文法規則が変更されると、これらのテーブルの内容も必然的に変更され、その結果として y.tab.c の行数が増減したり、内容が大きく変わったりします。

また、src/cmd/gc/yerr.h の変更は、Yaccによって生成されるエラーメッセージの定義に関連しています。文法規則の変更により、エラーが発生する可能性のあるコンテキストや、エラーコードの割り当てが変更されたため、このファイルも更新されています。

test/fixedbugs/issue4162.go は、この変更が正しく機能することを検証するための新しいテストファイルです。このテストファイルには、これまで構文エラーとなっていた []byte("foo",)chan int(nil,)(func())(nil,) といった形式の型変換が含まれており、これらがコンパイルエラーなく処理されることを確認します。

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

このコミットにおけるコアとなるコードの変更は、主に src/cmd/gc/go.y ファイルに集中しています。

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

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -947,7 +947,7 @@ pexpr_no_paren:
  		$$ = nod(OSLICE, $1, nod(OKEY, $3, $5));
  	}
  |\tpseudocall
-|\tconvtype '(' expr ')'
+|\tconvtype '(' expr ocomma ')'
  	{
  		// conversion
  		$$ = nod(OCALL, $1, N);\

この差分は、pexpr_no_paren という非終端記号の定義の一部を示しています。pexpr_no_paren は、括弧で囲まれていない式を扱うためのルールです。

変更前は、型変換のルールが convtype '(' expr ')' と定義されていました。これは、convtype(変換先の型)、開く括弧、expr(変換される式)、閉じる括弧の順でトークンが並ぶことを意味します。

変更後には、このルールが convtype '(' expr ocomma ')' となっています。ここに ocomma という新しい非終端記号が追加されています。

src/cmd/gc/y.tab.c の変更:

このファイルは go.y から自動生成されるため、go.y の変更に伴い、その内容が大幅に更新されています。具体的には、Yaccが生成する内部テーブル(yyprhs, yyrhs, yydefact, yydefgoto, yypact, yytable, yycheck, yystos)の数値が変更され、それに伴いファイル全体の行数が増減しています。これは、文法規則の変更がパーサの内部状態と遷移に影響を与えるためです。

src/cmd/gc/yerr.h の変更:

このファイルもYaccによって生成されるもので、エラーメッセージの定義が含まれています。文法規則の変更により、エラーコードの割り当てや、特定の構文エラーに関連するメッセージのインデックスが更新されています。

test/fixedbugs/issue4162.go の追加:

// compile

// Copyright 2013 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.

// Issue 4162. Trailing commas now allowed in conversions.

package p

// All these are valid now.
var (
	_ = int(1.0,)      // comma was always permitted (like function call)
	_ = []byte("foo",) // was syntax error: unexpected comma
	_ = chan int(nil,) // was syntax error: unexpected comma
	_ = (func())(nil,) // was syntax error: unexpected comma
)

この新しいテストファイルは、このコミットで導入された変更を検証するためのものです。特に、これまで構文エラーとなっていた []byte("foo",)chan int(nil,)(func())(nil,) のような型変換が、末尾カンマを含んでいても正しくコンパイルされることを確認します。int(1.0,) は元々許容されていた形式であり、比較のために含まれています。

コアとなるコードの解説

このコミットの核心は、Go言語の構文解析器が、型変換の括弧内にオプションの末尾カンマを許容するように変更されたことです。

Go言語の構文解析は、Yaccによって生成されたパーサによって行われます。Yaccは、文法規則を定義した .y ファイルを読み込み、その文法を解析するためのC言語のコード(y.tab.c)を生成します。

変更前の go.y では、型変換の構文は以下のように定義されていました。

convtype '(' expr ')'

これは、convtype(変換先の型)、開く括弧 (expr(変換される式)、閉じる括弧 ) の順でトークンが並ぶことを意味します。この定義では、expr の後にカンマを置くことは許容されていませんでした。

このコミットでは、この定義が以下のように変更されました。

convtype '(' expr ocomma ')'

ここで導入された ocomma は、Yaccの文法定義でよく使われるパターンで、オプションのカンマを表現するための非終端記号です。ocomma は通常、以下のように定義されます。

ocomma: /* empty */
      | ','
      ;

この定義は、「ocomma は何も存在しない(空プロダクション)か、または単一のカンマ , である」ということを意味します。

convtype '(' expr ocomma ')' という新しいルールは、以下の2つのパターンを許容します。

  1. convtype '(' expr ')': ocomma が空プロダクションの場合。これは変更前の挙動と同じです。
  2. convtype '(' expr ',' ')': ocomma がカンマである場合。これにより、expr の後に末尾カンマを置くことが可能になります。

この変更により、[]byte("foo",)chan int(nil,) のような型変換も、構文的に有効なものとしてGoコンパイラに認識されるようになります。

y.tab.c の変更は、この go.y の文法規則の変更を反映したものです。Yaccは、文法規則に基づいてパーサの内部状態遷移テーブルを生成します。文法規則が変更されると、これらのテーブルの内容も再計算され、y.tab.c の内容が更新されます。このため、y.tab.c の差分は非常に大きく見えますが、これは自動生成されたコードの変更であり、手動で編集されたものではありません。

yerr.h の変更も同様に、Yaccの再生成によって発生したものです。文法規則の変更により、エラーメッセージのインデックスや関連する情報が更新されたため、このファイルも変更されています。

最終的に、test/fixedbugs/issue4162.go の追加により、この構文変更がGoコンパイラに正しく実装され、期待通りに動作することが検証されています。このテストは、これまでエラーとなっていた特定の型変換構文が、末尾カンマを含んでいてもエラーなくコンパイルされることを確認します。

この変更は、Go言語の構文の一貫性を高め、特に自動生成されるコードや、複数行にわたる型変換の記述において、開発者の利便性を向上させるものです。

関連リンク

参考にした情報源リンク