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

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

このコミットは、Go言語の公式フォーマッタであるgofmtが、型スイッチ(type switch)の式における括弧の扱いをテストするためのものです。具体的には、x.(type)という形式の型アサーションが、誤って(x.(type))のように括弧で囲まれて記述された場合に、gofmtがこれを正しく非括弧形式に整形(rewrite)できることを検証するテストを追加しています。これは、Go言語のパーサーの特定の挙動(issue 4470)に関連する問題への対応を含んでいます。

コミット

commit 42a854b7465e332f27ece06328cc706909abbef3
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Dec 6 09:20:03 2012 -0800

    gofmt: test rewrite of (x.(type)) -> x.(type)
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6867062
---
 src/cmd/gofmt/gofmt_test.go              |  3 +--
 src/cmd/gofmt/testdata/typeswitch.golden | 60 ++++++++++++++++++++++++++++++++
 src/cmd/gofmt/testdata/typeswitch.input  | 60 ++++++++++++++++++++++++++++++++
 3 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/src/cmd/gofmt/gofmt_test.go b/src/cmd/gofmt/gofmt_test.go
index 1f19d64eee..ee943989b6 100644
--- a/src/cmd/gofmt/gofmt_test.go
+++ b/src/cmd/gofmt/gofmt_test.go
@@ -83,7 +83,8 @@ var tests = []struct {
 	{"testdata/stdin*.input", "-stdin"},
 	{"testdata/comments.input", ""},
 	{"testdata/import.input", ""},
-\t{"testdata/crlf.input", ""}, // test case for issue 3961; see also TestCRLF
+\t{"testdata/crlf.input", ""},       // test case for issue 3961; see also TestCRLF
+\t{"testdata/typeswitch.input", ""}, // test case for issue 4470
 }\n 
 func TestRewrite(t *testing.T) {
diff --git a/src/cmd/gofmt/testdata/typeswitch.golden b/src/cmd/gofmt/testdata/typeswitch.golden
new file mode 100644
index 0000000000..87e9161815
--- /dev/null
+++ b/src/cmd/gofmt/testdata/typeswitch.golden
@@ -0,0 +1,60 @@
+/*
+\tParenthesized type switch expressions originally
+\taccepted by gofmt must continue to be rewritten
+\tinto the correct unparenthesized form.\n
+\tOnly type-switches that didn\'t declare a variable
+\tin the the type switch type assertion and which
+\tcontained only \"expression-like\" (named) types in their
+\tcases were permitted to have their type assertion parenthesized
+\tby go/parser (due to a weak predicate in the parser). All others
+\twere rejected always, either with a syntax error in the
+\ttype switch header or in the case.\n
+\tSee also issue 4470.
+*/
+package p
+
+func f() {
+\tvar x interface{}\n
+\tswitch x.(type) { // should remain the same
+\t}\n
+\tswitch x.(type) { // should become: switch x.(type) {
+\t}\n
+\n
+\tswitch x.(type) { // should remain the same
+\tcase int:\n
+\t}\n
+\tswitch x.(type) { // should become: switch x.(type) {
+\tcase int:\n
+\t}\n
+\n
+\tswitch x.(type) { // should remain the same
+\tcase []int:\n
+\t}\n
+\n
+\t// Parenthesized (x.(type)) in type switches containing cases
+\t// with unnamed (literal) types were never permitted by gofmt;
+\t// thus there won\'t be any code in the wild using this style if
+\t// the code was gofmt-ed.\n
+\t/*
+\t\tswitch (x.(type)) {\n
+\t\tcase []int:\n
+\t\t}\n
+\t*/\n
+\n
+\tswitch t := x.(type) { // should remain the same
+\tdefault:\n
+\t\t_ = t\n+\t}\n+\n+\t// Parenthesized (x.(type)) in type switches declaring a variable
+\t// were never permitted by gofmt; thus there won\'t be any code in
+\t// the wild using this style if the code was gofmt-ed.\n
+\t/*
+\t\tswitch t := (x.(type)) {\n
+\t\tdefault:\n
+\t\t\t_ = t\n+\t\t}\n+\t*/\n+}\ndiff --git a/src/cmd/gofmt/testdata/typeswitch.input b/src/cmd/gofmt/testdata/typeswitch.input
new file mode 100644
index 0000000000..f90f28949a
--- /dev/null
+++ b/src/cmd/gofmt/testdata/typeswitch.input
@@ -0,0 +1,60 @@
+/*
+\tParenthesized type switch expressions originally
+\taccepted by gofmt must continue to be rewritten
+\tinto the correct unparenthesized form.\n
+\tOnly type-switches that didn\'t declare a variable
+\tin the the type switch type assertion and which
+\tcontained only \"expression-like\" (named) types in their
+\tcases were permitted to have their type assertion parenthesized
+\tby go/parser (due to a weak predicate in the parser). All others
+\twere rejected always, either with a syntax error in the
+\ttype switch header or in the case.\n
+\tSee also issue 4470.
+*/
+package p
+
+func f() {
+\tvar x interface{}\n
+\tswitch x.(type) { // should remain the same
+\t}\n
+\tswitch (x.(type)) { // should become: switch x.(type) {
+\t}\n
+\n
+\tswitch x.(type) { // should remain the same
+\tcase int:\n
+\t}\n
+\tswitch (x.(type)) { // should become: switch x.(type) {
+\tcase int:\n
+\t}\n
+\n
+\tswitch x.(type) { // should remain the same
+\tcase []int:\n
+\t}\n
+\n
+\t// Parenthesized (x.(type)) in type switches containing cases
+\t// with unnamed (literal) types were never permitted by gofmt;
+\t// thus there won\'t be any code in the wild using this style if
+\t// the code was gofmt-ed.\n
+\t/*
+\t\tswitch (x.(type)) {\n
+\t\tcase []int:\n
+\t\t}\n
+\t*/\n
+\n
+\tswitch t := x.(type) { // should remain the same
+\tdefault:\n
+\t\t_ = t\n+\t}\n+\n+\t// Parenthesized (x.(type)) in type switches declaring a variable
+\t// were never permitted by gofmt; thus there won\'t be any code in
+\t// the wild using this style if the code was gofmt-ed.\n
+\t/*
+\t\tswitch t := (x.(type)) {\n
+\t\tdefault:\n
+\t\t\t_ = t\n+\t\t}\n+\t*/\n+}\n

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

https://github.com/golang/go/commit/42a854b7465e332f27ece06328cc706909abbef3

元コミット内容

gofmt: test rewrite of (x.(type)) -> x.(type)

R=rsc
CC=golang-dev
https://golang.org/cl/6867062

変更の背景

このコミットは、Go言語のパーサー(go/parser)が型スイッチの式において、本来は不要な括弧(x.(type))を一部の場合に誤って許容してしまうという問題(Go issue 4470)に対応するために行われました。

gofmtはGo言語のコードを整形するツールであり、Goのコードベースの標準的なスタイルを強制する役割を担っています。Go言語の仕様では、型スイッチの式はswitch x.(type)という形式であり、x.(type)の部分をさらに括弧で囲むことは通常許容されません。しかし、特定の条件下でgo/parserがこの非標準的な形式をパースしてしまうことがありました。

この問題は、特に型スイッチが変数を宣言しない場合、かつケース節が「式のような」(名前付きの)型のみを含む場合に顕著でした。このようなコードがgofmtによって整形された場合、gofmtはGoの標準的なスタイルに従って、誤って付加された括弧を削除し、x.(type)という正しい形式に書き換える必要があります。

このコミットの目的は、gofmtがこのような非標準的な入力((x.(type)))を正しく処理し、標準的な形式(x.(type))に整形できることを保証するためのテストケースを追加することです。これにより、Go言語のコードベース全体で一貫したスタイルが維持され、パーサーの挙動の差異によって生じる可能性のあるフォーマットの不整合が解消されます。

前提知識の解説

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

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

switch v := i.(type) {
case int:
    // v は int 型
case string:
    // v は string 型
default:
    // v は他の型
}

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

switch i.(type) {
case int:
    // ...
case string:
    // ...
default:
    // ...
}

ここで重要なのは、i.(type)という部分が「型アサーション」であり、この式自体が型スイッチの対象となることです。Goの構文では、このi.(type)の部分をさらに括弧で囲むことは通常想定されていません。

gofmt

gofmtは、Go言語のソースコードを自動的に整形するツールです。Go言語のプロジェクトでは、gofmtによって整形されたコードが標準とされており、これによりコードの可読性と一貫性が保たれます。gofmtは単なるインデントやスペースの調整だけでなく、Goの慣習に沿ったコードの書き換え(rewrite)も行います。例えば、不要な括弧の削除や、特定の構文の正規化などです。

Go言語のパーサー (go/parser)

Go言語のコンパイラツールチェーンの一部として、ソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成するパーサーが存在します。このパーサーは、Go言語の文法規則に従ってコードを解釈します。しかし、パーサーの実装によっては、厳密な文法規則から逸脱した、あるいは曖昧な構文を一時的に許容してしまう「弱い述語(weak predicate)」を持つことがあります。今回の問題は、この「弱い述語」が原因で、本来は構文エラーとなるべき(x.(type))のような形式が、特定の条件下でパースされてしまうことにありました。

技術的詳細

Go言語のパーサーは、型スイッチの構文switch expr.(type)において、expr.(type)の部分を解析します。本来、このexpr.(type)はそれ自体が完全な型アサーションの式であり、これをさらに括弧で囲む(expr.(type))はGoの標準的な構文ではありません。しかし、Goの初期のパーサー実装(go/parser)には、特定の条件下でこの非標準的な括弧付きの型アサーションを誤って受け入れてしまう「弱い述語」が存在しました。

具体的には、以下の条件が揃った場合にこの問題が発生しました。

  1. 型スイッチが変数を宣言しない場合: switch x.(type)のように、switch文のヘッダーで新しい変数を宣言しない形式。
  2. ケース節が「式のような」(名前付きの)型のみを含む場合: case int:case string:のように、既存の型名を使用するケース。case []int:のようなリテラル型(無名型)を含む場合は、gofmtは元々この形式を許可していませんでした。

これらの条件下では、パーサーは(x.(type))を有効な構文として解釈してしまい、結果としてgofmtが整形対象として受け取ってしまう可能性がありました。しかし、gofmtの役割はGoの標準的なスタイルを強制することであるため、このような非標準的な形式は正しい形式x.(type)に書き換えられるべきです。

このコミットは、このパーサーの挙動とgofmtの整形能力の間の整合性を確保するために行われました。gofmtは、たとえパーサーが一時的に許容したとしても、最終的にはGoの標準的な構文に準拠するようにコードを整形する必要があります。このテストケースは、gofmt(x.(type))という入力を受け取った際に、それを正しくx.(type)に変換できることを検証します。

typeswitch.goldenファイル内のコメントがこの状況を詳しく説明しています。

Parenthesized type switch expressions originally accepted by gofmt must continue to be rewritten into the correct unparenthesized form. Only type-switches that didn't declare a variable in the the type switch type assertion and which contained only "expression-like" (named) types in their cases were permitted to have their type assertion parenthesized by go/parser (due to a weak predicate in the parser). All others were rejected always, either with a syntax error in the type switch header or in the case. See also issue 4470.

このコメントは、gofmtが元々受け入れていた括弧付きの型スイッチ式を、引き続き正しい非括弧形式に書き換える必要があることを強調しています。また、go/parserの「弱い述語」が、変数を宣言しない型スイッチで、かつケース節が名前付き型のみを含む場合に、型アサーションを括弧で囲むことを許可していたことを説明しています。それ以外のケースでは、常に構文エラーとして拒否されていました。

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

このコミットでは、主に以下の3つのファイルが変更されています。

  1. src/cmd/gofmt/gofmt_test.go:

    • testsというテストケースのスライスに、新しいエントリが追加されました。
    • {"testdata/typeswitch.input", ""}, // test case for issue 4470
    • この行は、gofmttestdata/typeswitch.inputファイルを処理し、その結果が期待される出力(この場合はtestdata/typeswitch.goldenの内容)と一致するかどうかをテストすることを示しています。コメントでissue 4470への関連が明記されています。
  2. src/cmd/gofmt/testdata/typeswitch.golden:

    • 新しく追加されたファイルです。
    • これは、gofmttypeswitch.inputファイルを整形した後に期待される「正しい」出力形式を定義しています。
    • ファイルの内容は、x.(type)のように括弧がない標準的な型スイッチの構文を含んでいます。
    • ファイル冒頭のコメントは、このテストケースの目的と、go/parserの「弱い述語」によって過去に許容されていた(x.(type))形式が、gofmtによってどのように整形されるべきかを詳細に説明しています。
  3. src/cmd/gofmt/testdata/typeswitch.input:

    • 新しく追加されたファイルです。
    • これは、gofmtが整形する対象となる「入力」コードを含んでいます。
    • このファイルには、switch (x.(type))のように、型アサーションが誤って括弧で囲まれた形式の型スイッチの例が含まれています。
    • typeswitch.goldenと同様に、ファイル冒頭のコメントでこのテストケースの背景が説明されています。

コアとなるコードの解説

src/cmd/gofmt/gofmt_test.go

このファイルはgofmtのテストスイートの一部です。testsスライスに新しいエントリを追加することで、gofmttypeswitch.inputファイルを処理する際の挙動を検証するテストが有効になります。TestRewrite関数(このコミットでは直接変更されていませんが、このテストケースを実行する関数)は、inputファイルの内容をgofmtで整形し、その結果を対応するgoldenファイルの内容と比較します。これにより、gofmtが期待通りにコードを整形したかどうかが確認されます。

src/cmd/gofmt/testdata/typeswitch.input

このファイルは、gofmtが整形すべき「問題のある」コードスニペットを含んでいます。具体的には、switch (x.(type))のように、型スイッチの式x.(type)が不必要に括弧で囲まれているパターンが複数含まれています。これらのパターンは、Goのパーサーの「弱い述語」によって過去に許容されてしまった可能性のあるコードをシミュレートしています。このファイルの目的は、gofmtがこれらの非標準的な形式を正しく認識し、整形対象として処理できることを確認することです。

src/cmd/gofmt/testdata/typeswitch.golden

このファイルは、typeswitch.inputファイルがgofmtによって整形された後に期待される「正しい」コードスニペットを含んでいます。typeswitch.input内の(x.(type))という形式は、このgoldenファイルではすべてx.(type)という標準的な形式に修正されています。これは、gofmtが不必要な括弧を削除し、Goの標準的なスタイルに準拠したコードを生成する能力を持っていることを示しています。このファイルは、テストの「正解」として機能し、gofmtの出力がこのgoldenファイルと完全に一致すれば、テストは成功とみなされます。

これらのファイルが連携することで、gofmtがGo言語のパーサーの特定の挙動によって生じる可能性のあるフォーマットの不整合を検出し、修正できることが保証されます。

関連リンク

  • Go issue 4470: https://go.dev/issue/4470 (Web検索結果から推測される公式Issueトラッカーのリンク)
  • Gerrit Change-Id: I42a854b7465e332f27ece06328cc706909abbef3 (コミットメッセージ内のhttps://golang.org/cl/6867062に対応するGerritの変更リンク)

参考にした情報源リンク