[インデックス 18460] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)におけるエラーメッセージの改善を目的としています。具体的には、if
、switch
、for
ステートメントの条件式や表現が不正な場合に表示されるエラーメッセージを、より具体的で分かりやすいものに変更しています。これにより、開発者が構文エラーの原因を特定しやすくなり、デバッグの効率が向上します。
コミット
- コミットハッシュ:
947aaf275cd5ede46c83322514f96a9fe9373171
- Author: Robert Griesemer gri@golang.org
- Date: Tue Feb 11 16:45:31 2014 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/947aaf275cd5ede46c83322514f96a9fe9373171
元コミット内容
go/parser: better error messages for if/switch/for conditions/expressions
Fixes #7102.
LGTM=adonovan
R=adonovan
CC=golang-codereviews
https://golang.org/cl/56770045
変更の背景
この変更は、Go言語のパーサーが生成するエラーメッセージの質を向上させるために行われました。特に、if
、switch
、for
といった制御フロー構造において、条件式や初期化ステートメントが期待される形式と異なる場合に、一般的な「expected condition」のようなメッセージではなく、より具体的な「expected boolean expression」や「expected switch expression」といったメッセージを出すように改善されています。
元のエラーメッセージは、問題の根本原因を特定するのに十分な情報を提供していませんでした。例えば、if
ステートメントの条件部にブール式ではないものが来た場合でも、単に「expected condition」と表示されるだけでした。これは、特にGo言語に不慣れな開発者にとって、何が問題なのかを理解する上で混乱を招く可能性がありました。
このコミットは、Issue #7102("go/parser: better error messages for if/switch/for conditions/expressions")を解決するために作成されました。このIssueは、まさにこのエラーメッセージの不明瞭さを指摘し、改善を求めていたものです。より詳細なエラーメッセージを提供することで、コンパイルエラーの解決にかかる時間を短縮し、開発者の生産性を向上させることが目的です。
前提知識の解説
Go言語のパーサー (go/parser
)
go/parser
パッケージは、Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成するためのGo標準ライブラリの一部です。ASTは、ソースコードの構造を木構造で表現したもので、コンパイラやリンター、コード分析ツールなどがコードを理解し処理するために利用します。
パーサーの主な役割は以下の通りです。
- 字句解析(Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに分解します。
- 構文解析(Syntactic Analysis): トークンのストリームを文法規則に従って解析し、ASTを構築します。この段階で文法エラーが検出されます。
抽象構文木(AST: Abstract Syntax Tree)
ASTは、プログラムのソースコードの抽象的な構文構造を、そのコードの各部分がどのように関連しているかを示すツリー形式で表現したものです。例えば、if
ステートメントは、条件式、thenブロック、elseブロックといった子ノードを持つASTノードとして表現されます。
エラーハンドリングとエラーメッセージ
パーサーは、ソースコードがGo言語の文法規則に違反している場合、構文エラーを報告します。この際、エラーメッセージは、開発者が問題を理解し、修正するために非常に重要です。良いエラーメッセージは、エラーの種類、発生箇所、そして期待される構文を明確に示します。
Go言語の制御フロー構造
if
ステートメント: 条件が真の場合にコードブロックを実行します。条件は必ずブール式である必要があります。if condition { // ... }
switch
ステートメント: 式の値に基づいて異なるコードブロックを実行します。switch
の後に続く式は、任意の型を持つことができます。switch expression { case value1: // ... case value2: // ... default: // ... }
for
ステートメント: ループを定義します。Goのfor
は、C言語のfor
、while
、無限ループの機能を兼ね備えています。条件式はブール式であるか、range
句を使用することができます。for initialization; condition; post { // ... } for condition { // ... } for { // ... } for key, value := range collection { // ... }
これらのステートメントでは、特定の場所に特定の種類の式(ブール式、任意の式、範囲式など)が期待されます。パーサーは、この期待に反する構文を検出した場合にエラーを報告します。
技術的詳細
このコミットの主要な変更点は、go/parser
パッケージ内のmakeExpr
関数に新しい引数kind
を追加し、それを使ってより具体的なエラーメッセージを生成するようにしたことです。
makeExpr
関数の変更
makeExpr
関数は、パーサーがステートメントを式として解釈しようとする際に使用されるヘルパー関数です。以前は、ステートメントが式として期待される場所で単純なステートメントが見つかった場合、一律に「expected condition, found simple statement (missing parentheses around composite literal?)」というエラーメッセージを生成していました。
変更後、makeExpr
関数はkind
という文字列引数を受け取るようになりました。このkind
引数は、期待される式の種類(例: "boolean expression", "switch expression", "boolean or range expression")を記述します。
// 変更前
func (p *parser) makeExpr(s ast.Stmt) ast.Expr {
// ...
p.error(s.Pos(), "expected condition, found simple statement (missing parentheses around composite literal?)")
// ...
}
// 変更後
func (p *parser) makeExpr(s ast.Stmt, kind string) ast.Expr {
// ...
p.error(s.Pos(), fmt.Sprintf("expected %s, found simple statement (missing parentheses around composite literal?)", kind))
// ...
}
この変更により、エラーメッセージはfmt.Sprintf
を使用して動的に生成され、kind
引数の値が埋め込まれるようになりました。
parseIfStmt
, parseSwitchStmt
, parseForStmt
の変更
if
、switch
、for
ステートメントを解析する関数内で、makeExpr
を呼び出す際に、そのコンテキストに応じた適切なkind
文字列が渡されるようになりました。
parseIfStmt
:if
ステートメントの条件式を解析する際に、makeExpr
に"boolean expression"
が渡されるようになりました。これにより、if
の条件がブール式でない場合に「expected boolean expression」というエラーが出力されます。parseSwitchStmt
:switch
ステートメントの式を解析する際に、makeExpr
に"switch expression"
が渡されるようになりました。これにより、switch
の式が不正な場合に「expected switch expression」というエラーが出力されます。parseForStmt
:for
ステートメントの条件式を解析する際に、makeExpr
に"boolean or range expression"
が渡されるようになりました。これにより、for
の条件がブール式でもrange
式でもない場合に「expected boolean or range expression」というエラーが出力されます。
テストケースの更新 (short_test.go
)
src/pkg/go/parser/short_test.go
ファイルでは、これらのエラーメッセージの変更に合わせて、期待されるエラー文字列が更新されています。これにより、パーサーが新しい、より具体的なエラーメッセージを正しく生成していることが保証されます。
例えば、以前は「expected condition」と期待されていたテストケースが、新しいエラーメッセージ(例: 「expected boolean expression」)を期待するように変更されています。
コアとなるコードの変更箇所
src/pkg/go/parser/parser.go
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -1762,14 +1762,14 @@ func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt {
return &ast.BranchStmt{TokPos: pos, Tok: tok, Label: label}\n }\n \n-func (p *parser) makeExpr(s ast.Stmt) ast.Expr {\n+func (p *parser) makeExpr(s ast.Stmt, kind string) ast.Expr {\n \tif s == nil {\n \t\treturn nil\n \t}\n \tif es, isExpr := s.(*ast.ExprStmt); isExpr {\n \t\treturn p.checkExpr(es.X)\n \t}\n-\tp.error(s.Pos(), \"expected condition, found simple statement (missing parentheses around composite literal?)\")\n+\tp.error(s.Pos(), fmt.Sprintf(\"expected %s, found simple statement (missing parentheses around composite literal?)\", kind))\n \treturn &ast.BadExpr{From: s.Pos(), To: s.End()}\n }\n \n@@ -1796,7 +1796,7 @@ func (p *parser) parseIfStmt() *ast.IfStmt {\n \t\t\t\tp.next()\n \t\t\t\tx = p.parseRhs()\n \t\t\t} else {\n-\t\t\t\tx = p.makeExpr(s)\n+\t\t\t\tx = p.makeExpr(s, \"boolean expression\")\n \t\t\t\ts = nil\n \t\t\t}\n \t\t}\n@@ -1927,7 +1927,7 @@ func (p *parser) parseSwitchStmt() ast.Stmt {\n \t\treturn &ast.TypeSwitchStmt{Switch: pos, Init: s1, Assign: s2, Body: body}\n \t}\n \n-\treturn &ast.SwitchStmt{Switch: pos, Init: s1, Tag: p.makeExpr(s2), Body: body}\n+\treturn &ast.SwitchStmt{Switch: pos, Init: s1, Tag: p.makeExpr(s2, \"switch expression\"), Body: body}\n }\n \n func (p *parser) parseCommClause() *ast.CommClause {\n@@ -2072,7 +2072,7 @@ func (p *parser) parseForStmt() ast.Stmt {\n \treturn &ast.ForStmt{\n \t\tFor: pos,\n \t\tInit: s1,\n-\t\tCond: p.makeExpr(s2),\n+\t\tCond: p.makeExpr(s2, \"boolean or range expression\"),\n \t\tPost: s3,\n \t\tBody: body,\n \t}\n```
### `src/pkg/go/parser/short_test.go`
```diff
--- a/src/pkg/go/parser/short_test.go
+++ b/src/pkg/go/parser/short_test.go
@@ -48,14 +48,14 @@ var invalids = []string{\n `package p; func f() { if { /* ERROR \"expected operand\" */ } };`,\n `package p; func f() { if ; { /* ERROR \"expected operand\" */ } };`,\n `package p; func f() { if f(); { /* ERROR \"expected operand\" */ } };`,\n-\t`package p; func f() { if _ /* ERROR \"expected condition\" */ = range x; true {} };`,\n-\t`package p; func f() { switch _ /* ERROR \"expected condition\" */ = range x; true {} };`,\n+\t`package p; func f() { if _ /* ERROR \"expected boolean expression\" */ = range x; true {} };`,\n+\t`package p; func f() { switch _ /* ERROR \"expected switch expression\" */ = range x; true {} };`,\n \t`package p; func f() { for _ = range x ; /* ERROR \"expected \'{\'\" */ ; {} };`,\n \t`package p; func f() { for ; ; _ = range /* ERROR \"expected operand\" */ x {} };`,\n-\t`package p; func f() { for ; _ /* ERROR \"expected condition\" */ = range x ; {} };`,\n-\t`package p; func f() { switch t /* ERROR \"expected condition\" */ = t.(type) {} };`,\n-\t`package p; func f() { switch t /* ERROR \"expected condition\" */ , t = t.(type) {} };`,\n-\t`package p; func f() { switch t /* ERROR \"expected condition\" */ = t.(type), t {} };`,\n+\t`package p; func f() { for ; _ /* ERROR \"expected boolean or range expression\" */ = range x ; {} };`,\n+\t`package p; func f() { switch t /* ERROR \"expected switch expression\" */ = t.(type) {} };`,\n+\t`package p; func f() { switch t /* ERROR \"expected switch expression\" */ , t = t.(type) {} };`,\n+\t`package p; func f() { switch t /* ERROR \"expected switch expression\" */ = t.(type), t {} };`,\n \t`package p; var a = [ /* ERROR \"expected expression\" */ 1]int;`,\n \t`package p; var a = [ /* ERROR \"expected expression\" */ ...]int;`,\n \t`package p; var a = struct /* ERROR \"expected expression\" */ {}`,\n@@ -82,6 +82,10 @@ var invalids = []string{\n \t`package p; func f() { var s []int; _ = s[: /* ERROR \"2nd index required\" */ :] };`,\n \t`package p; func f() { var s []int; _ = s[: /* ERROR \"2nd index required\" */ ::] };`,\n \t`package p; func f() { var s []int; _ = s[i:j:k: /* ERROR \"expected \']\'\" */ l] };`,\n+\t`package p; func f() { for x /* ERROR \"boolean or range expression\" */ = []string {} }`,\n+\t`package p; func f() { for x /* ERROR \"boolean or range expression\" */ := []string {} }`,\n+\t`package p; func f() { for i /* ERROR \"boolean or range expression\" */ , x = []string {} }`,\n+\t`package p; func f() { for i /* ERROR \"boolean or range expression\" */ , x := []string {} }`,\n }\n \n func TestInvalid(t *testing.T) {\n```
## コアとなるコードの解説
このコミットの核心は、`makeExpr`関数のシグネチャ変更と、それに伴う呼び出し元の修正です。
1. **`makeExpr`関数のシグネチャ変更**:
* 変更前: `func (p *parser) makeExpr(s ast.Stmt) ast.Expr`
* 変更後: `func (p *parser) makeExpr(s ast.Stmt, kind string) ast.Expr`
* `kind`という新しい`string`型の引数が追加されました。この引数は、エラーメッセージ内で「期待される式の種類」を動的に指定するために使用されます。
2. **エラーメッセージの生成ロジックの変更**:
* `makeExpr`関数内でエラーを報告する際に、以前はハードコードされていたエラーメッセージの一部が、`fmt.Sprintf`と`kind`引数を使って動的に生成されるようになりました。
* これにより、例えば`if`ステートメントのコンテキストでは`kind`が"boolean expression"となり、「expected boolean expression, found simple statement...」というメッセージが生成されます。
3. **`parseIfStmt`、`parseSwitchStmt`、`parseForStmt`での`makeExpr`の呼び出し変更**:
* これらの関数は、それぞれが解析している制御フロー構造のコンテキストに応じて、`makeExpr`を呼び出す際に適切な`kind`文字列を渡すようになりました。
* `parseIfStmt`では`"boolean expression"`
* `parseSwitchStmt`では`"switch expression"`
* `parseForStmt`では`"boolean or range expression"`
* これにより、各制御フロー構造の条件部や式部に不正な構文があった場合に、より具体的で文脈に即したエラーメッセージがユーザーに提示されるようになります。
4. **`short_test.go`のテストケースの更新**:
* `invalids`変数内の文字列リテラルが更新され、期待されるエラーメッセージが新しい、より具体的なものに修正されました。これは、変更が正しく機能し、パーサーが期待通りのエラーメッセージを生成していることを検証するために不可欠です。
これらの変更により、Go言語のパーサーは、構文エラーが発生した際に、開発者にとってより有益で理解しやすいフィードバックを提供するようになりました。これは、Go言語の使いやすさと開発体験の向上に貢献します。
## 関連リンク
* **Go Issue #7102**: [https://github.com/golang/go/issues/7102](https://github.com/golang/go/issues/7102)
* **Go Code Review (CL) 56770045**: [https://golang.org/cl/56770045](https://golang.org/cl/56770045)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (`go/parser`パッケージ): [https://pkg.go.dev/go/parser](https://pkg.go.dev/go/parser)
* Go言語の公式ドキュメント (`go/ast`パッケージ): [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
* Go言語の公式ドキュメント (`fmt`パッケージ): [https://pkg.go.dev/fmt](https://pkg.go.dev/fmt)
* 抽象構文木 (AST) についての一般的な情報源 (例: Wikipediaなど)
* Go言語の`if`, `switch`, `for`ステートメントに関する公式ドキュメントやチュートリアル。