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

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

このコミットは、Go言語のコンパイラ(cmd/gc)において、括弧で囲まれた .(type) 式を型スイッチ(type switch)のコンテキストで拒否するように変更を加えるものです。具体的には、switch (expr.(type)) のような構文が不正とされるようになります。

コミット

commit c956dcdc54937ac17a4e1a01e7353cd9110b7400
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Sat Dec 22 17:36:10 2012 +0100

    cmd/gc: Reject parenthesised .(type) expressions.
    
    Fixes #4470.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6949073

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

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

元コミット内容

cmd/gc: Reject parenthesised .(type) expressions.

Fixes #4470.

変更の背景

この変更は、Go言語のIssue 4470「issue4470: parens are not allowed around .(type) "expressions"」を修正するために行われました。Go言語の型スイッチ構文 switch v := i.(type)switch i.(type) において、.(type) の部分を (i.(type)) のように括弧で囲むことが、文法的に曖昧さや不必要な複雑さを生む可能性がありました。

Go言語の設計思想として、明確で簡潔な構文が重視されます。.(type) は型スイッチの特別な構文要素であり、通常の式とは異なる振る舞いをします。そのため、通常の式のように括弧で囲むことを許可すると、パーサーの実装が複雑になったり、開発者が混乱したりする原因となる可能性があります。このコミットは、このような曖昧な、あるいは不必要な構文をコンパイラレベルで拒否することで、言語の整合性と明確性を保つことを目的としています。

前提知識の解説

Go言語の型スイッチ(Type Switch)

Go言語には、インターフェース型の変数が実行時に保持している具体的な型に基づいて異なる処理を行うための「型スイッチ」という構文があります。 基本的な構文は以下の通りです。

switch v := i.(type) {
case int:
    // i が int 型の場合の処理
case string:
    // i が string 型の場合の処理
default:
    // その他の型の場合の処理
}

または、変数を宣言しない形式もあります。

switch i.(type) {
case int:
    // i が int 型の場合の処理
case string:
    // i が string 型の場合の処理
default:
    // その他の型の場合の処理
}

ここで i.(type) は、インターフェース i の基底型を評価する特別な構文です。これは通常の式とは異なり、型スイッチの switch ステートメントの直後でのみ有効です。

コンパイラの構文解析(Parsing)とYacc/Bison

Go言語のコンパイラ gc は、ソースコードを機械語に変換する過程で、まずソースコードの構文が正しいかを解析します。この構文解析のフェーズでは、字句解析器(lexer)がソースコードをトークンに分解し、構文解析器(parser)がこれらのトークンが言語の文法規則に沿っているかを確認し、抽象構文木(AST)を構築します。

Goコンパイラの一部(特に初期のバージョン)では、Yacc(Yet Another Compiler Compiler)やそのGNU版であるBisonのようなツールが使用されていました。これらのツールは、BNF(Backus-Naur Form)に似た文法規則の定義ファイル(.y 拡張子を持つことが多い)から、C言語の構文解析器のソースコード(.tab.c 拡張子を持つことが多い)を自動生成します。

  • go.y: Go言語の文法規則が定義されているファイルです。このファイルに記述された規則に基づいて、Goのソースコードがどのように解釈されるべきかが決定されます。
  • y.tab.c: go.y からYacc/Bisonによって生成されたC言語のソースファイルです。このファイルには、Go言語の構文解析を行うための状態機械やアクションが実装されています。

抽象構文木(Abstract Syntax Tree, AST)のノード

コンパイラは構文解析の過程で、ソースコードの構造を表現する抽象構文木(AST)を構築します。ASTの各ノードは、プログラムの特定の構文要素(例えば、変数宣言、関数呼び出し、演算子など)を表します。Goコンパイラでは、これらの構文要素を内部的に表現するために、OPAREN(括弧)、OTYPESW(型スイッチ)などのオペレーションコード(OP)を持つノードを使用します。

技術的詳細

このコミットの技術的な核心は、Go言語のパーサーが .(type) 式を括弧で囲むことを許可しないように、文法規則を修正することにあります。

  1. src/cmd/gc/go.y の変更: go.y はGo言語の文法を定義するYaccファイルです。このファイルには、式(pexpr)が括弧で囲まれる場合の規則が記述されています。 変更前は、OPACK, OTYPE, OLITERAL といったノードタイプが括弧で囲まれることを許可していました。

    --- a/src/cmd/gc/go.y
    +++ b/src/cmd/gc/go.y
    @@ -1020,6 +1020,7 @@ pexpr:
     		case OPACK:
     		case OTYPE:
     		case OLITERAL:
    +		case OTYPESW:
     		\t$$ = nod(OPAREN, $$, N);
     		}
     	}
    

    この変更により、OTYPESW(型スイッチの .(type) 部分を表す内部ノードタイプ)も OPAREN(括弧)ノードの子として許可されるリストに追加されました。しかし、これは一見すると括弧を許可するように見えますが、実際には go.ypexpr ルールは、特定のノードタイプが括弧で囲まれた場合に OPAREN ノードを生成するという意味です。このコミットの意図は「Reject parenthesised .(type) expressions」であるため、この go.y の変更は、OTYPESWpexpr のコンテキストで現れた場合に OPAREN ノードを生成する可能性を示唆していますが、実際の拒否ロジックは y.tab.c の生成コードや、その後のセマンティックチェックの段階で行われると推測されます。

    補足: Yacc/Bisonの文法定義では、pexpr のようなルールは、そのプロダクションがマッチしたときにどのようなASTノードを生成するかを定義します。case OTYPESW: の追加は、.(type) 式が pexpr として認識され、それが括弧で囲まれた場合に OPAREN ノードが生成されることを意味します。しかし、この OPAREN ノードが生成された後、コンパイラのセマンティックチェックの段階で、.(type) 式が switch ステートメントのコンテキスト外で括弧で囲まれている場合にエラーを出すロジックが追加されたと考えられます。

  2. src/cmd/gc/y.tab.c の変更: y.tab.cgo.y から自動生成されるC言語のパーサーコードです。このファイルには、文法規則に対応する多数の yyreduce 関数が含まれています。 このコミットでは、y.tab.c の多くの行番号が変更されています。これは、go.y の変更によって生成されるコードの構造が変化したためです。特に、yyreduce 関数内の case OTYPESW: の追加と、それに伴う yyval.node = nod(OPAREN, (yyval.node), N); の処理が反映されています。

    --- a/src/cmd/gc/y.tab.c
    +++ b/src/cmd/gc/y.tab.c
    @@ -3809,6 +3809,7 @@ yyreduce:
     		case OPACK:
     		case OTYPE:
     		case OLITERAL:
    +		case OTYPESW:
     		\t(yyval.node) = nod(OPAREN, (yyval.node), N);\
     		}\
     	}\
    

    このコードは、パーサーが OPACK, OTYPE, OLITERAL、そして新しく追加された OTYPESW のいずれかのノードを処理する際に、そのノードを OPAREN ノードの子としてラップすることを示しています。これは、パーサーが (expr) のような構文を認識し、それをAST上で OPAREN ノードとして表現するための一般的なメカニズムです。

    この変更のポイントは、OTYPESWOPAREN でラップされることをパーサーが認識するようになったことです。そして、この OPAREN でラップされた OTYPESW が、型スイッチのコンテキスト外で出現した場合、または型スイッチのコンテキストであっても特定のルールに違反する場合に、コンパイラの後のフェーズ(セマンティック解析など)でエラーとして検出されるようになります。

  3. test/fixedbugs/issue4470.go の追加: このファイルは、このコミットによって修正されるべきバグ(Issue 4470)を再現するためのテストケースです。

    // errorcheck
    
    // Copyright 2012 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 4470: parens are not allowed around .(type) "expressions"
    
    package main
    
    func main() {
    	var i interface{}
    	switch (i.(type)) { // ERROR "outside type switch"
    	default:
    	}
    }
    

    このテストケースは、switch (i.(type)) という構文がコンパイルエラーになることを期待しています。// ERROR "outside type switch" というコメントは、errorcheck ツールがこの行で指定されたエラーメッセージが出力されることを検証するためのものです。 このエラーメッセージ「outside type switch」は、.(type) 構文が型スイッチの switch キーワードの直後でのみ有効であり、それ以外の場所(この場合は括弧で囲まれた結果、パーサーが「通常の式」として扱おうとしたため)では不正であることを示唆しています。

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

  • src/cmd/gc/go.y: pexpr ルールに OTYPESW を追加し、括弧で囲まれた .(type) 式をパーサーが認識できるようにする。
  • src/cmd/gc/y.tab.c: go.y の変更に伴い自動生成されたパーサーコード。yyreduce 関数内の関連する部分が更新されている。
  • test/fixedbugs/issue4470.go: 括弧で囲まれた .(type) 式がコンパイルエラーとなることを検証するテストケース。

コアとなるコードの解説

このコミットの主要な変更は、Goコンパイラの構文解析器が .(type) 式の扱いを厳格化する点にあります。

go.y の変更は、パーサーが (OTYPESW) のような構造を認識し、それを OPAREN ノードとしてASTに組み込むことを可能にします。これは、パーサーがこの構文を「文法的に有効な括弧で囲まれた式」として解釈するようになったことを意味します。

しかし、test/fixedbugs/issue4470.go// ERROR "outside type switch" コメントが示すように、この構文は最終的にコンパイルエラーとなります。これは、パーサーが構文木を構築した後、コンパイラのセマンティック解析フェーズで、.(type) 式が switch ステートメントの特別なコンテキスト外で(この場合は括弧によって通常の式として扱われようとしたため)使用されていると判断され、エラーが報告されるためです。

つまり、この変更は、.(type) が型スイッチの特別な構文要素であり、通常の式のように括弧で囲むべきではないというGo言語の設計意図を、コンパイラがより厳密に強制するためのものです。これにより、言語の構文がより明確になり、開発者が意図しない形で .(type) を使用することを防ぎます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(型スイッチに関する記述)
  • Yacc/Bisonのドキュメンテーション(文法定義とパーサー生成に関する一般的な情報)
  • Goコンパイラのソースコード(src/cmd/gc ディレクトリ内の他のファイルや、コンパイラの全体的な構造に関する理解)
  • Go言語のIssueトラッカー(Issue 4470の詳細な議論)
  • Go言語のコードレビューシステム(CL 6949073のレビューコメント)
  • Go言語の仕様書(.(type) 構文の正式な定義)