[インデックス 11873] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)におけるエラーメッセージの改善に関するものです。具体的には、構文解析中にカンマが不足している場合に、より分かりやすいエラーメッセージを生成するように変更が加えられました。これにより、開発者がコードの誤りを特定しやすくなり、デバッグの効率が向上します。
コミット
commit e3f11b3f3c5f5aea874b44296cab2c66632d1965
Author: Robert Griesemer <gri@golang.org>
Date: Mon Feb 13 19:48:27 2012 -0800
go/parser: better error messages for missing commas
Fixes #3008.
R=rsc
CC=golang-dev
https://golang.org/cl/5660046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e3f11b3f3c5f5aea874b44296cab2c66632d1965
元コミット内容
Go言語のパーサーにおいて、カンマの欠落に対するエラーメッセージを改善する。
関連するIssue: #3008 を修正。
レビュー担当者: rsc CC: golang-dev 変更リスト: https://golang.org/cl/5660046
変更の背景
Go言語の構文解析器(パーサー)は、ソースコードを読み込み、その構造を理解するための重要なコンポーネントです。パーサーが構文エラーを検出した場合、開発者に対してエラーメッセージを報告します。しかし、初期のパーサーでは、特定の一般的なエラーパターン(例えば、改行の前にカンマが欠落している場合)に対して、必ずしも開発者が問題を迅速に特定できるような、具体的で分かりやすいエラーメッセージを提供していませんでした。
このコミットの背景には、Go言語のIssue #3008が存在します。このIssueでは、以下のようなコードでカンマが欠落している場合に、パーサーが生成するエラーメッセージが不明瞭であるという問題が指摘されていました。
type T struct {
A int
B int // ここにカンマがない
}
このような場合、パーサーは単に「予期しない改行」のような一般的なエラーを報告することがあり、開発者はどこに問題があるのかを特定するのに時間がかかる可能性がありました。このコミットは、このような一般的なケース、特に改行の前にカンマが欠落している場合に、より具体的で役立つエラーメッセージを提供することで、開発者のデバッグ体験を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念に関する基本的な知識が必要です。
- Go言語の構文解析 (Parsing):
- Goコンパイラのフロントエンドの一部であり、ソースコードを読み込み、その構文がGo言語の仕様に準拠しているかを検証するプロセスです。
- 構文解析は、字句解析(Lexical Analysis、トークン化とも呼ばれる)の次に行われます。字句解析器はソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに変換し、パーサーはそのトークンストリームを基に抽象構文木(AST: Abstract Syntax Tree)を構築します。
go/parser
パッケージは、Go言語のソースコードを解析し、ASTを生成するための標準ライブラリです。
- トークン (Token):
- ソースコードの最小の意味のある単位です。例えば、
func
はキーワードトークン、main
は識別子トークン、(
は括弧トークンです。 token
パッケージは、Go言語のトークン型を定義しています。token.SEMICOLON
はセミコロン(;
)トークンを表します。Go言語では、文の終わりには通常セミコロンが暗黙的に挿入されますが、明示的に書かれることもあります。特に改行がセミコロンとして扱われる「自動セミコロン挿入」のルールが重要です。token.RPAREN
は右括弧()
)トークン、token.RBRACE
は右中括弧(}
)トークンを表します。
- ソースコードの最小の意味のある単位です。例えば、
- 字句 (Literal):
- トークンが表す実際の文字列値です。例えば、識別子トークン
main
の字句は"main"
です。 - 改行文字も字句として扱われることがあります。
- トークンが表す実際の文字列値です。例えば、識別子トークン
- 抽象構文木 (AST: Abstract Syntax Tree):
- ソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラの後の段階(型チェック、コード生成など)で利用されます。
- エラーハンドリング (Error Handling):
- パーサーは、構文エラーを検出した場合に、そのエラーを報告するメカニズムを持っています。通常、エラーの位置(ファイル名、行番号、列番号)とエラーメッセージが含まれます。
- 良いエラーメッセージは、開発者が問題を迅速に理解し、修正するために不可欠です。
技術的詳細
このコミットは、go/parser
パッケージ内のエラー報告ロジックを改善しています。主な変更点は以下の通りです。
-
p.lit
の比較の修正:- 変更前:
p.tok == token.SEMICOLON && p.lit[0] == '\n'
- 変更後:
p.tok == token.SEMICOLON && p.lit == "\n"
- これは、字句(
p.lit
)が改行文字であるかどうかを正確にチェックするための修正です。以前は文字列の最初の文字だけを見ていましたが、これはp.lit
が単一の改行文字である場合にのみ正しく機能します。p.lit == "\n"
とすることで、字句全体が改行文字であるかを厳密に比較します。これは、Goの自動セミコロン挿入のルールにより、改行がセミコロンとして解釈される状況を正確に検出するために重要です。
- 変更前:
-
expectClosing
ヘルパー関数の導入:- このコミットの最も重要な変更点です。
expectClosing
は、expect
関数(特定のトークンを期待し、それが見つからない場合にエラーを報告する関数)のラッパーとして機能します。 expectClosing
は、期待されるトークン(例:token.RPAREN
やtoken.RBRACE
)が見つからず、かつ現在のトークンがtoken.SEMICOLON
であり、その字句が改行("\n"
)である場合に、特別なエラーメッセージを生成します。- 具体的には、「
missing ',' before newline in
[構文要素名]」という形式のエラーメッセージを出力します。ここで[構文要素名]
は、argument list
(引数リスト)やcomposite literal
(複合リテラル)など、エラーが発生したコンテキストを示します。 - この関数は、一般的な「カンマ忘れ」のシナリオ、特に改行が自動セミコロンとして解釈されるためにカンマが欠落していると見なされる場合に、非常に具体的なエラーメッセージを提供します。
- このコミットの最も重要な変更点です。
-
parseCallOrConversion
とparseLiteralValue
でのexpectClosing
の利用:parseCallOrConversion
関数は、関数呼び出しや型変換の構文を解析します。この関数内で、右括弧(token.RPAREN
)を期待する際に、従来のp.expect(token.RPAREN)
の代わりにp.expectClosing(token.RPAREN, "argument list")
を使用するように変更されました。これにより、引数リストの終わりにカンマが欠落している場合に、より適切なエラーメッセージが生成されます。parseLiteralValue
関数は、複合リテラル(例: 構造体リテラル、配列リテラル)の構文を解析します。この関数内で、右中括弧(token.RBRACE
)を期待する際に、従来のp.expect(token.RBRACE)
の代わりにp.expectClosing(token.RBRACE, "composite literal")
を使用するように変更されました。これにより、複合リテラルの要素の終わりにカンマが欠落している場合に、より適切なエラーメッセージが生成されます。
これらの変更により、パーサーは、開発者がよく遭遇する「カンマ忘れ」のエラーに対して、より具体的でデバッグに役立つ情報を提供できるようになりました。
コアとなるコードの変更箇所
src/pkg/go/parser/parser.go
ファイルに以下の変更が加えられました。
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -335,7 +335,7 @@ func (p *parser) errorExpected(pos token.Pos, msg string) {
if pos == p.pos {
// the error happened at the current position;
// make the error message more specific
- if p.tok == token.SEMICOLON && p.lit[0] == '\n' {
+ if p.tok == token.SEMICOLON && p.lit == "\n" {
msg += ", found newline"
} else {
msg += ", found '" + p.tok.String() + "'"
@@ -356,6 +356,17 @@ func (p *parser) expect(tok token.Token) token.Pos {
return pos
}
+// expectClosing is like expect but provides a better error message
+// for the common case of a missing comma before a newline.
+//
+func (p *parser) expectClosing(tok token.Token, construct string) token.Pos {
+ if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n" {
+ p.error(p.pos, "missing ',' before newline in "+construct)
+ p.next()
+ }
+ return p.expect(tok)
+}
+
func (p *parser) expectSemi() {
if p.tok != token.RPAREN && p.tok != token.RBRACE {
p.expect(token.SEMICOLON)
@@ -1056,7 +1067,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr {
p.next()
}
p.exprLev--
- rparen := p.expect(token.RPAREN)
+ rparen := p.expectClosing(token.RPAREN, "argument list")
return &ast.CallExpr{fun, lparen, list, ellipsis, rparen}
}
@@ -1111,7 +1122,7 @@ func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr {
elts = p.parseElementList()
}
p.exprLev--
- rbrace := p.expect(token.RBRACE)
+ rbrace := p.expectClosing(token.RBRACE, "composite literal")
return &ast.CompositeLit{typ, lbrace, elts, rbrace}
}
コアとなるコードの解説
-
func (p *parser) errorExpected(pos token.Pos, msg string)
の変更:- この関数は、パーサーが予期しないトークンに遭遇した際にエラーメッセージを生成するために使用されます。
p.lit[0] == '\n'
からp.lit == "\n"
への変更は、字句が正確に改行文字であるかをチェックするためのものです。これにより、自動セミコロン挿入によってセミコロンが挿入された場合に、その原因が改行であることをより正確に識別できるようになります。
-
func (p *parser) expectClosing(tok token.Token, construct string) token.Pos
の追加:- この新しいヘルパー関数は、閉じ括弧や閉じ中括弧など、特定の終了トークンを期待する際に使用されます。
if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n"
: この条件は、パーサーが期待するトークン(tok
)に遭遇せず、代わりに現在のトークンがセミコロン(token.SEMICOLON
)であり、そのセミコロンが改行("\n"
)によって自動挿入されたものである場合に真となります。p.error(p.pos, "missing ',' before newline in "+construct)
: 上記の条件が真の場合、パーサーは「missing ',' before newline in
[構文要素名]」という具体的なエラーメッセージを生成します。construct
引数には、エラーが発生した構文要素(例: "argument list"や"composite literal")が渡されます。p.next()
: エラーを報告した後、パーサーは次のトークンに進みます。これにより、パーサーが同じ位置で繰り返しエラーを報告するのを防ぎ、回復を試みます。return p.expect(tok)
: 最終的に、この関数は元のp.expect(tok)
を呼び出します。これは、特定のトークンを期待する通常のロジックを実行し、必要に応じて一般的なエラーを報告します。
-
parseCallOrConversion
とparseLiteralValue
でのexpectClosing
の利用:parseCallOrConversion
関数では、関数呼び出しの引数リストの終わりを示す右括弧(token.RPAREN
)を解析する際に、rparen := p.expectClosing(token.RPAREN, "argument list")
が使用されます。これにより、引数リスト内でカンマが欠落し、改行によってセミコロンが挿入された場合に、より具体的なエラーメッセージが表示されます。parseLiteralValue
関数では、複合リテラルの終わりを示す右中括弧(token.RBRACE
)を解析する際に、rbrace := p.expectClosing(token.RBRACE, "composite literal")
が使用されます。これにより、複合リテラル内でカンマが欠落し、改行によってセミコロンが挿入された場合に、より具体的なエラーメッセージが表示されます。
これらの変更により、Goパーサーは、開発者が遭遇しやすい特定の構文エラー(特にカンマの欠落)に対して、より具体的で役立つ診断情報を提供できるようになり、デバッグの労力を軽減します。
関連リンク
- Go言語のIssue #3008: このコミットが修正した元の問題に関する情報が含まれている可能性があります。
- Go言語の
go/parser
パッケージのドキュメント: Go言語のパーサーの機能と使い方に関する詳細情報。 - Go言語の自動セミコロン挿入ルール: Go言語の構文におけるセミコロンの扱いに関する詳細。
参考にした情報源リンク
- Go言語の公式ドキュメント (go.dev)
- GitHubのGo言語リポジトリ (github.com/golang/go)
- Stack OverflowなどのプログラミングQ&Aサイト (stackoverflow.com)
- Go言語の変更リスト (golang.org/cl)