[インデックス 12480] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
)におけるエラー同期メカニズムの改善を目的としています。具体的には、構文解析中にカンマ(,
)が欠落している場合に、より適切にエラーを検出し、回復するためのロジックが修正されています。特に、改行の前にカンマが欠落しているケースに対するハンドリングが強化されています。
コミット
commit 9e8e4a9313f8bff5c40d82166818f98b4a8cc9ed
Author: Robert Griesemer <gri@golang.org>
Date: Wed Mar 7 10:19:32 2012 -0800
go/parser: better error sync. if commas are missing
This time for sure.
Runs all tests.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5752060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9e8e4a9313f8bff5c40d82166818f98b4a8cc9ed
元コミット内容
go/parser
: カンマが欠落している場合のエラー同期を改善。
今度こそは。
全てのテストを実行。
変更の背景
Go言語のコンパイラやツールチェーンにおいて、ソースコードの構文解析(パース)は非常に重要なフェーズです。パーサーは、与えられたGoのソースコードが言語仕様に準拠しているかを検証し、抽象構文木(AST)を構築します。この過程で構文エラーが発生した場合、パーサーはエラーを報告し、可能であれば残りのコードの解析を続行(エラー回復)して、できるだけ多くの診断情報を提供することが望ましいとされます。
このコミットの背景には、Goのパーサーがカンマの欠落、特に改行の直前でカンマが欠落している場合に、エラー回復が不十分であったという問題があったと考えられます。例えば、構造体のフィールドリストや関数の引数リストなどでカンマが欠落していると、パーサーがその後の構文を正しく解釈できなくなり、連鎖的なエラー(カスケードエラー)を引き起こす可能性がありました。
コミットメッセージの「This time for sure.」という表現から、以前にも同様の問題に対する修正が試みられたが、完全には解決していなかったことが示唆されます。今回の変更は、この特定のエラーシナリオにおけるパーサーの堅牢性を高め、より正確なエラー報告と回復を可能にすることを目的としています。
前提知識の解説
go/parser
パッケージ
go/parser
パッケージは、Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成するためのパッケージです。Goのコンパイラ、go fmt
、go vet
などのツールで利用されています。パーサーは、字句解析器(lexer)からトークンストリームを受け取り、Go言語の文法規則に従って構文を検証し、ASTを構築します。
トークン (token.COMMA
, token.SEMICOLON
)
字句解析器は、ソースコードを意味のある最小単位である「トークン」に分割します。
token.COMMA
: カンマ(,
)を表すトークンです。Go言語では、変数リスト、引数リスト、構造体リテラルなどで要素の区切りとして使用されます。token.SEMICOLON
: セミコロン(;
)を表すトークンです。Go言語では、文の区切りとして使用されますが、多くの場合、改行によって自動的に挿入(ASI: Automatic Semicolon Insertion)されます。
抽象構文木(AST)と ast.Expr
ASTは、ソースコードの構造を木構造で表現したものです。go/ast
パッケージで定義されています。
ast.Expr
: ASTノードの一種で、式(expression)を表すインターフェースです。変数、リテラル、関数呼び出しなどがこれに該当します。
parser
構造体とメソッド
go/parser
パッケージの内部では、parser
構造体が構文解析の状態を管理します。
p.tok
: 現在処理中のトークンの種類を示します。p.lit
: 現在処理中のトークンのリテラル値(文字列)を示します。例えば、token.IDENT
であれば識別子の名前、token.STRING
であれば文字列リテラルの内容など。p.pos
: 現在処理中のトークンのソースコード上の位置(行番号、列番号など)を示します。p.error(pos, msg)
: 指定された位置pos
でエラーメッセージmsg
を報告するためのメソッドです。p.next()
: 次のトークンを読み込み、p.tok
とp.lit
を更新するメソッドです。p.expectSemi()
: セミコロンを期待するメソッドです。GoのASIルールを考慮して、改行や特定のトークンの後にセミコロンがなくても許容するロジックが含まれます。
エラー回復 (Error Recovery)
構文解析中にエラーが発生した場合、パーサーは通常、そのエラーを報告し、解析を停止するか、あるいはエラーを乗り越えて解析を続行しようとします。後者のプロセスがエラー回復です。エラー回復の目的は、単一の構文エラーによって後続の有効な構文が全てエラーとして報告される「カスケードエラー」を防ぎ、開発者にできるだけ多くの有用な診断情報を提供することです。
技術的詳細
このコミットの主要な変更点は、parser.go
ファイル内の seesComma
メソッドのロファクタリングと、それに伴うカンマ欠落時のエラー同期ロジックの改善です。
seesComma
から atComma
へのリネームとロジック変更
元の seesComma
メソッドは、現在のトークンが token.COMMA
であるか、または token.SEMICOLON
であり、かつそのリテラルが改行(\n
)である場合に true
を返していました。後者のケースでは、改行の前にカンマが欠落していると判断し、エラーを報告しつつも true
を返すことで、パーサーがカンマが存在するかのように振る舞い、解析を続行できるようにしていました。これは、エラー回復の一種です。
変更後、メソッド名は atComma
に変更されました。そして、重要な変更として、seesComma
メソッド内にコメントアウトされていたブロック(/* ... */
)が削除され、そのロジックが直接 atComma
メソッドの本体に組み込まれました。
// 変更前 (seesComma)
func (p *parser) seesComma(context string) bool {
if p.tok == token.COMMA {
return true
}
/*
if p.tok == token.SEMICOLON && p.lit == "\n" {
p.error(p.pos, "missing ',' before newline in "+context)
return true // "insert" the comma and continue
}
*/
return false
}
// 変更後 (atComma)
func (p *parser) atComma(context string) bool {
if p.tok == token.COMMA {
return true
}
if p.tok == token.SEMICOLON && p.lit == "\n" {
p.error(p.pos, "missing ',' before newline in "+context)
return true // "insert" the comma and continue
}
return false
}
この変更により、atComma
メソッドは、現在のトークンがカンマであるか、または改行による自動セミコロン挿入の対象となるセミコロンである場合に、カンマが存在すると見なすようになりました。これにより、カンマが欠落しているが改行がある場合に、パーサーがより早くエラーを検出し、かつ解析を継続できるようになったと考えられます。
p.tok != token.COMMA
への変更
parseVarList
, parseParameterList
, parseCallOrConversion
, parseElementList
といった、リスト構造を解析する複数のメソッドにおいて、ループの継続条件が !p.seesComma(...)
から p.tok != token.COMMA
に変更されました。
これは非常に重要な変更です。
- 変更前 (
!p.seesComma(...)
):seesComma
がtrue
を返す(つまり、実際のカンマがあるか、改行による擬似カンマがある)限りループが継続していました。 - 変更後 (
p.tok != token.COMMA
): ループは実際のtoken.COMMA
が存在しない場合にのみ終了します。
一見すると、エラー回復のロジックが弱くなったように見えますが、そうではありません。atComma
メソッド自体が、改行によるカンマ欠落を検出してエラーを報告しつつ true
を返すようになったため、p.tok != token.COMMA
という条件と組み合わせることで、より正確なエラー同期が可能になります。
具体的には、atComma
が true
を返しても、p.tok
が token.COMMA
でなければループは終了します。しかし、atComma
の内部で p.error
が呼び出されているため、エラーは適切に報告されます。これにより、パーサーはカンマが欠落していることを認識しつつ、その後のトークンに基づいて次の構文要素の解析を試みることができます。これは、エラー発生時にパーサーが「どこまで読み飛ばすべきか」を判断する上で非常に重要です。
この変更は、パーサーが構文エラーに遭遇した際に、不必要なトークンを読み飛ばしすぎることなく、かつ解析を継続できるような、より洗練されたエラー回復戦略を実装するためのものです。
コアとなるコードの変更箇所
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -376,17 +376,15 @@ func (p *parser) expectSemi() {
}
}
-func (p *parser) seesComma(context string) bool {
+func (p *parser) atComma(context string) bool {
if p.tok == token.COMMA {
return true
}
- /*
- if p.tok == token.SEMICOLON && p.lit == "\n" {
- p.error(p.pos, "missing ',' before newline in "+context)
- return true // "insert" the comma and continue
-
- }
- */
+ if p.tok == token.SEMICOLON && p.lit == "\n" {
+ p.error(p.pos, "missing ',' before newline in "+context)
+ return true // "insert" the comma and continue
+
+ }
return false
}
@@ -661,7 +659,7 @@ func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) {
// accept them all for more robust parsing and complain later
for typ := p.parseVarType(isParam); typ != nil; {
list = append(list, typ)
- if !p.seesComma("variable list") {
+ if p.tok != token.COMMA {
break
}
p.next()
@@ -702,7 +700,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [
// Go spec: The scope of an identifier denoting a function
// parameter or result variable is the function body.
p.declare(field, nil, scope, ast.Var, idents...)
- if !p.seesComma("parameter list") {
+ if !p.atComma("parameter list") {
break
}
p.next()
@@ -1092,7 +1090,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr {
ellipsis = p.pos
p.next()
}
- if !p.seesComma("argument list") {
+ if !p.atComma("argument list") {
break
}
p.next()
@@ -1132,7 +1130,7 @@ func (p *parser) parseElementList() (list []ast.Expr) {
for p.tok != token.RBRACE && p.tok != token.EOF {
list = append(list, p.parseElement(true))
- if !p.seesComma("composite literal") {
+ if !p.atComma("composite literal") {
break
}
p.next()
コアとなるコードの解説
func (p *parser) seesComma(context string) bool
から func (p *parser) atComma(context string) bool
への変更
- メソッド名の変更:
seesComma
からatComma
へと変更されました。これは、メソッドの意図をより明確にするためのリファクタリングです。seesComma
は「カンマが見えるか」というニュアンスでしたが、atComma
は「現在カンマの位置にいるか(またはカンマと見なせる状況か)」という、より現在のパーサーの状態に焦点を当てた表現になっています。 - コメントアウトされたコードの統合: 以前
seesComma
内でコメントアウトされていた、p.tok == token.SEMICOLON && p.lit == "\n"
の条件でエラーを報告しつつtrue
を返すロジックが、atComma
の本体に直接組み込まれました。これにより、改行の前にカンマが欠落している場合に、パーサーがその場でエラーを報告し、「カンマが挿入された」かのように振る舞って解析を続行できるようになります。これは、エラー回復の重要な部分です。
parseVarList
メソッド内の変更
- if !p.seesComma("variable list") {
+ if p.tok != token.COMMA {
break
}
parseVarList
は変数宣言リストを解析するメソッドです。ループの継続条件が !p.seesComma("variable list")
から p.tok != token.COMMA
に変更されました。
- 変更前:
seesComma
がtrue
を返す限り(つまり、実際のカンマがあるか、改行による擬似カンマがある限り)ループが継続していました。 - 変更後: ループは実際の
token.COMMA
が存在しない場合にのみ終了します。これにより、atComma
が改行によるカンマ欠落を検出してエラーを報告した場合でも、p.tok
がtoken.COMMA
でなければループは終了し、パーサーは次の構文要素の解析に進むことができます。これは、エラー発生時にパーサーが不必要なトークンを読み飛ばしすぎることなく、かつ解析を継続できるような、より洗練されたエラー回復戦略を実装するためのものです。
parseParameterList
, parseCallOrConversion
, parseElementList
メソッド内の変更
これらのメソッドも同様に、リスト構造(パラメータリスト、引数リスト、複合リテラルの要素リスト)を解析する部分で、ループの継続条件が !p.seesComma(...)
から !p.atComma(...)
または p.tok != token.COMMA
に変更されています。
-
parseParameterList
:- if !p.seesComma("parameter list") { + if !p.atComma("parameter list") { break }
ここでは
!p.atComma
に変更されています。atComma
がエラーを報告しつつtrue
を返す場合、!p.atComma
はfalse
となり、ループは継続します。これは、パラメータリストの解析において、カンマが欠落していても、その後のパラメータを解析し続けようとする意図があることを示唆しています。 -
parseCallOrConversion
:- if !p.seesComma("argument list") { + if !p.atComma("argument list") { break }
関数呼び出しや型変換の引数リストの解析です。ここも
!p.atComma
に変更されています。 -
parseElementList
:- if !p.seesComma("composite literal") { + if !p.atComma("composite literal") { break }
複合リテラル(配列、スライス、マップ、構造体リテラル)の要素リストの解析です。ここも
!p.atComma
に変更されています。
これらの変更は、Goのパーサーが、カンマの欠落という一般的な構文エラーに対して、より堅牢かつ柔軟に対応できるようにするためのものです。エラーを適切に報告しつつ、可能な限り解析を続行することで、開発者にとってより有用なエラーメッセージを提供し、コンパイルプロセス全体の効率を向上させます。
関連リンク
- Go言語のパーサーに関する公式ドキュメントやチュートリアルは、Goの公式ウェブサイトやGoのソースコードリポジトリで確認できます。
- Go言語の構文解析に関する詳細な情報は、
go/parser
およびgo/ast
パッケージのドキュメントを参照してください。
参考にした情報源リンク
- golang/go GitHub repository
- Go language specification
- Go packages documentation (go/parser, go/ast)
- Go CL 5752060 (これはコミットメッセージに記載されているリンクであり、この変更のコードレビューページです)
[インデックス 12480] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
)におけるエラー同期メカニズムの改善を目的としています。具体的には、構文解析中にカンマ(,
)が欠落している場合に、より適切にエラーを検出し、回復するためのロジックが修正されています。特に、改行の前にカンマが欠落しているケースに対するハンドリングが強化されています。
コミット
commit 9e8e4a9313f8bff5c40d82166818f98b4a8cc9ed
Author: Robert Griesemer <gri@golang.org>
Date: Wed Mar 7 10:19:32 2012 -0800
go/parser: better error sync. if commas are missing
This time for sure.
Runs all tests.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5752060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9e8e4a9313f8bff5c40d82166818f98b4a8cc9ed
元コミット内容
go/parser
: カンマが欠落している場合のエラー同期を改善。
今度こそは。
全てのテストを実行。
変更の背景
Go言語のコンパイラやツールチェーンにおいて、ソースコードの構文解析(パース)は非常に重要なフェーズです。パーサーは、与えられたGoのソースコードが言語仕様に準拠しているかを検証し、抽象構文木(AST)を構築します。この過程で構文エラーが発生した場合、パーサーはエラーを報告し、可能であれば残りのコードの解析を続行(エラー回復)して、できるだけ多くの診断情報を提供することが望ましいとされます。
このコミットの背景には、Goのパーサーがカンマの欠落、特に改行の直前でカンマが欠落している場合に、エラー回復が不十分であったという問題があったと考えられます。例えば、構造体のフィールドリストや関数の引数リストなどでカンマが欠落していると、パーサーがその後の構文を正しく解釈できなくなり、連鎖的なエラー(カスケードエラー)を引き起こす可能性がありました。
コミットメッセージの「This time for sure.」という表現から、以前にも同様の問題に対する修正が試みられたが、完全には解決していなかったことが示唆されています。今回の変更は、この特定のエラーシナリオにおけるパーサーの堅牢性を高め、より正確なエラー報告と回復を可能にすることを目的としています。
前提知識の解説
go/parser
パッケージ
go/parser
パッケージは、Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成するためのパッケージです。Goのコンパイラ、go fmt
、go vet
などのツールで利用されています。パーサーは、字句解析器(lexer)からトークンストリームを受け取り、Go言語の文法規則に従って構文を検証し、ASTを構築します。
トークン (token.COMMA
, token.SEMICOLON
)
字句解析器は、ソースコードを意味のある最小単位である「トークン」に分割します。
token.COMMA
: カンマ(,
)を表すトークンです。Go言語では、変数リスト、引数リスト、構造体リテラルなどで要素の区切りとして使用されます。token.SEMICOLON
: セミコロン(;
)を表すトークンです。Go言語では、文の区切りとして使用されますが、多くの場合、改行によって自動的に挿入(ASI: Automatic Semicolon Insertion)されます。
抽象構文木(AST)と ast.Expr
ASTは、ソースコードの構造を木構造で表現したものです。go/ast
パッケージで定義されています。
ast.Expr
: ASTノードの一種で、式(expression)を表すインターフェースです。変数、リテラル、関数呼び出しなどがこれに該当します。
parser
構造体とメソッド
go/parser
パッケージの内部では、parser
構造体が構文解析の状態を管理します。
p.tok
: 現在処理中のトークンの種類を示します。p.lit
: 現在処理中のトークンのリテラル値(文字列)を示します。例えば、token.IDENT
であれば識別子の名前、token.STRING
であれば文字列リテラルの内容など。p.pos
: 現在処理中のトークンのソースコード上の位置(行番号、列番号など)を示します。p.error(pos, msg)
: 指定された位置pos
でエラーメッセージmsg
を報告するためのメソッドです。p.next()
: 次のトークンを読み込み、p.tok
とp.lit
を更新するメソッドです。p.expectSemi()
: セミコロンを期待するメソッドです。GoのASIルールを考慮して、改行や特定のトークンの後にセミコロンがなくても許容するロジックが含まれます。
エラー回復 (Error Recovery)
構文解析中にエラーが発生した場合、パーサーは通常、そのエラーを報告し、解析を停止するか、あるいはエラーを乗り越えて解析を続行しようとします。後者のプロセスがエラー回復です。エラー回復の目的は、単一の構文エラーによって後続の有効な構文が全てエラーとして報告される「カスケードエラー」を防ぎ、開発者にできるだけ多くの有用な診断情報を提供することです。
技術的詳細
このコミットの主要な変更点は、parser.go
ファイル内の seesComma
メソッドのロファクタリングと、それに伴うカンマ欠落時のエラー同期ロジックの改善です。
seesComma
から atComma
へのリネームとロジック変更
元の seesComma
メソッドは、現在のトークンが token.COMMA
であるか、または token.SEMICOLON
であり、かつそのリテラルが改行(\n
)である場合に true
を返していました。後者のケースでは、改行の前にカンマが欠落していると判断し、エラーを報告しつつも true
を返すことで、パーサーがカンマが存在するかのように振る舞い、解析を続行できるようにしていました。これは、エラー回復の一種です。
変更後、メソッド名は atComma
に変更されました。そして、重要な変更として、seesComma
メソッド内にコメントアウトされていたブロック(/* ... */
)が削除され、そのロジックが直接 atComma
メソッドの本体に組み込まれました。
// 変更前 (seesComma)
func (p *parser) seesComma(context string) bool {
if p.tok == token.COMMA {
return true
}
/*
if p.tok == token.SEMICOLON && p.lit == "\n" {
p.error(p.pos, "missing ',' before newline in "+context)
return true // "insert" the comma and continue
}
*/
return false
}
// 変更後 (atComma)
func (p *parser) atComma(context string) bool {
if p.tok == token.COMMA {
return true
}
if p.tok == token.SEMICOLON && p.lit == "\n" {
p.error(p.pos, "missing ',' before newline in "+context)
return true // "insert" the comma and continue
}
return false
}
この変更により、atComma
メソッドは、現在のトークンがカンマであるか、または改行による自動セミコロン挿入の対象となるセミコロンである場合に、カンマが存在すると見なすようになりました。これにより、カンマが欠落しているが改行がある場合に、パーサーがより早くエラーを検出し、かつ解析を継続できるようになったと考えられます。
p.tok != token.COMMA
への変更
parseVarList
, parseParameterList
, parseCallOrConversion
, parseElementList
といった、リスト構造を解析する複数のメソッドにおいて、ループの継続条件が !p.seesComma(...)
から p.tok != token.COMMA
に変更されました。
これは非常に重要な変更です。
- 変更前 (
!p.seesComma(...)
):seesComma
がtrue
を返す(つまり、実際のカンマがあるか、改行による擬似カンマがある)限りループが継続していました。 - 変更後 (
p.tok != token.COMMA
): ループは実際のtoken.COMMA
が存在しない場合にのみ終了します。
一見すると、エラー回復のロジックが弱くなったように見えますが、そうではありません。atComma
メソッド自体が、改行によるカンマ欠落を検出してエラーを報告しつつ true
を返すようになったため、p.tok != token.COMMA
という条件と組み合わせることで、より正確なエラー同期が可能になります。
具体的には、atComma
が true
を返しても、p.tok
が token.COMMA
でなければループは終了します。しかし、atComma
の内部で p.error
が呼び出されているため、エラーは適切に報告されます。これにより、パーサーはカンマが欠落していることを認識しつつ、その後のトークンに基づいて次の構文要素の解析を試みることができます。これは、エラー発生時にパーサーが「どこまで読み飛ばすべきか」を判断する上で非常に重要です。
この変更は、パーサーが構文エラーに遭遇した際に、不必要なトークンを読み飛ばしすぎることなく、かつ解析を継続できるような、より洗練されたエラー回復戦略を実装するためのものです。
コアとなるコードの変更箇所
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -376,17 +376,15 @@ func (p *parser) expectSemi() {
}
}
-func (p *parser) seesComma(context string) bool {
+func (p *parser) atComma(context string) bool {
if p.tok == token.COMMA {
return true
}
- /*
- if p.tok == token.SEMICOLON && p.lit == "\n" {
- p.error(p.pos, "missing ',' before newline in "+context)
- return true // "insert" the comma and continue
-
- }
- */
+ if p.tok == token.SEMICOLON && p.lit == "\n" {
+ p.error(p.pos, "missing ',' before newline in "+context)
+ return true // "insert" the comma and continue
+
+ }
return false
}
@@ -661,7 +659,7 @@ func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) {
// accept them all for more robust parsing and complain later
for typ := p.parseVarType(isParam); typ != nil; {
list = append(list, typ)
- if !p.seesComma("variable list") {
+ if p.tok != token.COMMA {
break
}
p.next()
@@ -702,7 +700,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [
// Go spec: The scope of an identifier denoting a function
// parameter or result variable is the function body.
p.declare(field, nil, scope, ast.Var, idents...)\n- if !p.seesComma("parameter list") {
+ if !p.atComma("parameter list") {
break
}
p.next()
@@ -1092,7 +1090,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr {\n ellipsis = p.pos
p.next()
}
- if !p.seesComma("argument list") {
+ if !p.atComma("argument list") {
break
}
p.next()
@@ -1132,7 +1130,7 @@ func (p *parser) parseElementList() (list []ast.Expr) {\n
for p.tok != token.RBRACE && p.tok != token.EOF {
list = append(list, p.parseElement(true))
- if !p.seesComma("composite literal") {
+ if !p.atComma("composite literal") {
break
}
p.next()
コアとなるコードの解説
func (p *parser) seesComma(context string) bool
から func (p *parser) atComma(context string) bool
への変更
- メソッド名の変更:
seesComma
からatComma
へと変更されました。これは、メソッドの意図をより明確にするためのリファクタリングです。seesComma
は「カンマが見えるか」というニュアンスでしたが、atComma
は「現在カンマの位置にいるか(またはカンマと見なせる状況か)」という、より現在のパーサーの状態に焦点を当てた表現になっています。 - コメントアウトされたコードの統合: 以前
seesComma
内でコメントアウトされていた、p.tok == token.SEMICOLON && p.lit == "\n"
の条件でエラーを報告しつつtrue
を返すロジックが、atComma
の本体に直接組み込まれました。これにより、改行の前にカンマが欠落している場合に、パーサーがその場でエラーを報告し、「カンマが挿入された」かのように振る舞って解析を続行できるようになります。これは、エラー回復の重要な部分です。
parseVarList
メソッド内の変更
- if !p.seesComma("variable list") {
+ if p.tok != token.COMMA {
break
}
parseVarList
は変数宣言リストを解析するメソッドです。ループの継続条件が !p.seesComma("variable list")
から p.tok != token.COMMA
に変更されました。
- 変更前:
seesComma
がtrue
を返す限り(つまり、実際のカンマがあるか、改行による擬似カンマがある限り)ループが継続していました。 - 変更後: ループは実際の
token.COMMA
が存在しない場合にのみ終了します。これにより、atComma
が改行によるカンマ欠落を検出してエラーを報告した場合でも、p.tok
がtoken.COMMA
でなければループは終了し、パーサーは次の構文要素の解析に進むことができます。これは、エラー発生時にパーサーが不必要なトークンを読み飛ばしすぎることなく、かつ解析を継続できるような、より洗練されたエラー回復戦略を実装するためのものです。
parseParameterList
, parseCallOrConversion
, parseElementList
メソッド内の変更
これらのメソッドも同様に、リスト構造(パラメータリスト、引数リスト、複合リテラルの要素リスト)を解析する部分で、ループの継続条件が !p.seesComma(...)
から !p.atComma(...)
または p.tok != token.COMMA
に変更されています。
-
parseParameterList
:- if !p.seesComma("parameter list") { + if !p.atComma("parameter list") { break }
ここでは
!p.atComma
に変更されています。atComma
がエラーを報告しつつtrue
を返す場合、!p.atComma
はfalse
となり、ループは継続します。これは、パラメータリストの解析において、カンマが欠落していても、その後のパラメータを解析し続けようとする意図があることを示唆しています。 -
parseCallOrConversion
:- if !p.seesComma("argument list") { + if !p.atComma("argument list") { break }
関数呼び出しや型変換の引数リストの解析です。ここも
!p.atComma
に変更されています。 -
parseElementList
:- if !p.seesComma("composite literal") { + if !p.atComma("composite literal") { break }
複合リテラル(配列、スライス、マップ、構造体リテラル)の要素リストの解析です。ここも
!p.atComma
に変更されています。
これらの変更は、Goのパーサーが、カンマの欠落という一般的な構文エラーに対して、より堅牢かつ柔軟に対応できるようにするためのものです。エラーを適切に報告しつつ、可能な限り解析を続行することで、開発者にとってより有用なエラーメッセージを提供し、コンパイルプロセス全体の効率を向上させます。
関連リンク
- Go言語のパーサーに関する公式ドキュメントやチュートリアルは、Goの公式ウェブサイトやGoのソースコードリポジトリで確認できます。
- Go言語の構文解析に関する詳細な情報は、
go/parser
およびgo/ast
パッケージのドキュメントを参照してください。
参考にした情報源リンク
- golang/go GitHub repository
- Go language specification
- Go packages documentation (go/parser, go/ast)
- Go CL 5752060 (これはコミットメッセージに記載されているリンクであり、この変更のコードレビューページです)
- Go parser error recovery missing comma newline - Google Search
[インデックス 12480] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
)におけるエラー同期メカニズムの改善を目的としています。具体的には、構文解析中にカンマ(,
)が欠落している場合に、より適切にエラーを検出し、回復するためのロジックが修正されています。特に、改行の前にカンマが欠落しているケースに対するハンドリングが強化されています。
コミット
commit 9e8e4a9313f8bff5c40d82166818f98b4a8cc9ed
Author: Robert Griesemer <gri@golang.org>
Date: Wed Mar 7 10:19:32 2012 -0800
go/parser: better error sync. if commas are missing
This time for sure.
Runs all tests.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5752060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9e8e4a9313f8bff5c40d82166818f98b4a8cc9ed
元コミット内容
go/parser
: カンマが欠落している場合のエラー同期を改善。
今度こそは。
全てのテストを実行。
変更の背景
Go言語のコンパイラやツールチェーンにおいて、ソースコードの構文解析(パース)は非常に重要なフェーズです。パーサーは、与えられたGoのソースコードが言語仕様に準拠しているかを検証し、抽象構文木(AST)を構築します。この過程で構文エラーが発生した場合、パーサーはエラーを報告し、可能であれば残りのコードの解析を続行(エラー回復)して、できるだけ多くの診断情報を提供することが望ましいとされます。
このコミットの背景には、Goのパーサーがカンマの欠落、特に改行の直前でカンマが欠落している場合に、エラー回復が不十分であったという問題があったと考えられます。例えば、構造体のフィールドリストや関数の引数リストなどでカンマが欠落していると、パーサーがその後の構文を正しく解釈できなくなり、連鎖的なエラー(カスケードエラー)を引き起こす可能性がありました。
コミットメッセージの「This time for sure.」という表現から、以前にも同様の問題に対する修正が試みられたが、完全には解決していなかったことが示唆されています。今回の変更は、この特定のエラーシナリオにおけるパーサーの堅牢性を高め、より正確なエラー報告と回復を可能にすることを目的としています。
前提知識の解説
go/parser
パッケージ
go/parser
パッケージは、Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成するためのパッケージです。Goのコンパイラ、go fmt
、go vet
などのツールで利用されています。パーサーは、字句解析器(lexer)からトークンストリームを受け取り、Go言語の文法規則に従って構文を検証し、ASTを構築します。
トークン (token.COMMA
, token.SEMICOLON
)
字句解析器は、ソースコードを意味のある最小単位である「トークン」に分割します。
token.COMMA
: カンマ(,
)を表すトークンです。Go言語では、変数リスト、引数リスト、構造体リテラルなどで要素の区切りとして使用されます。token.SEMICOLON
: セミコロン(;
)を表すトークンです。Go言語では、文の区切りとして使用されますが、多くの場合、改行によって自動的に挿入(ASI: Automatic Semicolon Insertion)されます。
抽象構文木(AST)と ast.Expr
ASTは、ソースコードの構造を木構造で表現したものです。go/ast
パッケージで定義されています。
ast.Expr
: ASTノードの一種で、式(expression)を表すインターフェースです。変数、リテラル、関数呼び出しなどがこれに該当します。
parser
構造体とメソッド
go/parser
パッケージの内部では、parser
構造体が構文解析の状態を管理します。
p.tok
: 現在処理中のトークンの種類を示します。p.lit
: 現在処理中のトークンのリテラル値(文字列)を示します。例えば、token.IDENT
であれば識別子の名前、token.STRING
であれば文字列リテラルの内容など。p.pos
: 現在処理中のトークンのソースコード上の位置(行番号、列番号など)を示します。p.error(pos, msg)
: 指定された位置pos
でエラーメッセージmsg
を報告するためのメソッドです。p.next()
: 次のトークンを読み込み、p.tok
とp.lit
を更新するメソッドです。p.expectSemi()
: セミコロンを期待するメソッドです。GoのASIルールを考慮して、改行や特定のトークンの後にセミコロンがなくても許容するロジックが含まれます。
エラー回復 (Error Recovery)
構文解析中にエラーが発生した場合、パーサーは通常、そのエラーを報告し、解析を停止するか、あるいはエラーを乗り越えて解析を続行しようとします。後者のプロセスがエラー回復です。エラー回復の目的は、単一の構文エラーによって後続の有効な構文が全てエラーとして報告される「カスケードエラー」を防ぎ、開発者にできるだけ多くの有用な診断情報を提供することです。
技術的詳細
このコミットの主要な変更点は、parser.go
ファイル内の seesComma
メソッドのロファクタリングと、それに伴うカンマ欠落時のエラー同期ロジックの改善です。
seesComma
から atComma
へのリネームとロジック変更
元の seesComma
メソッドは、現在のトークンが token.COMMA
であるか、または token.SEMICOLON
であり、かつそのリテラルが改行(\n
)である場合に true
を返していました。後者のケースでは、改行の前にカンマが欠落していると判断し、エラーを報告しつつも true
を返すことで、パーサーがカンマが存在するかのように振る舞い、解析を続行できるようにしていました。これは、エラー回復の一種です。
変更後、メソッド名は atComma
に変更されました。そして、重要な変更として、seesComma
メソッド内にコメントアウトされていたブロック(/* ... */
)が削除され、そのロジックが直接 atComma
メソッドの本体に組み込まれました。
// 変更前 (seesComma)
func (p *parser) seesComma(context string) bool {
if p.tok == token.COMMA {
return true
}
/*
if p.tok == token.SEMICOLON && p.lit == "\n" {
p.error(p.pos, "missing ',' before newline in "+context)
return true // "insert" the comma and continue
}
*/
return false
}
// 変更後 (atComma)
func (p *parser) atComma(context string) bool {
if p.tok == token.COMMA {
return true
}
if p.tok == token.SEMICOLON && p.lit == "\n" {
p.error(p.pos, "missing ',' before newline in "+context)
return true // "insert" the comma and continue
}
return false
}
この変更により、atComma
メソッドは、現在のトークンがカンマであるか、または改行による自動セミコロン挿入の対象となるセミコロンである場合に、カンマが存在すると見なすようになりました。これにより、カンマが欠落しているが改行がある場合に、パーサーがより早くエラーを検出し、かつ解析を継続できるようになったと考えられます。
p.tok != token.COMMA
への変更
parseVarList
, parseParameterList
, parseCallOrConversion
, parseElementList
といった、リスト構造を解析する複数のメソッドにおいて、ループの継続条件が !p.seesComma(...)
から p.tok != token.COMMA
に変更されました。
これは非常に重要な変更です。
- 変更前 (
!p.seesComma(...)
):seesComma
がtrue
を返す(つまり、実際のカンマがあるか、改行による擬似カンマがある)限りループが継続していました。 - 変更後 (
p.tok != token.COMMA
): ループは実際のtoken.COMMA
が存在しない場合にのみ終了します。
一見すると、エラー回復のロジックが弱くなったように見えますが、そうではありません。atComma
メソッド自体が、改行によるカンマ欠落を検出してエラーを報告しつつ true
を返すようになったため、p.tok != token.COMMA
という条件と組み合わせることで、より正確なエラー同期が可能になります。
具体的には、atComma
が true
を返しても、p.tok
が token.COMMA
でなければループは終了します。しかし、atComma
の内部で p.error
が呼び出されているため、エラーは適切に報告されます。これにより、パーサーはカンマが欠落していることを認識しつつ、その後のトークンに基づいて次の構文要素の解析を試みることができます。これは、エラー発生時にパーサーが「どこまで読み飛ばすべきか」を判断する上で非常に重要です。
この変更は、パーサーが構文エラーに遭遇した際に、不必要なトークンを読み飛ばしすぎることなく、かつ解析を継続できるような、より洗練されたエラー回復戦略を実装するためのものです。
コアとなるコードの変更箇所
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -376,17 +376,15 @@ func (p *parser) expectSemi() {
}
}
-func (p *parser) seesComma(context string) bool {
+func (p *parser) atComma(context string) bool {
if p.tok == token.COMMA {
return true
}
- /*
- if p.tok == token.SEMICOLON && p.lit == "\n" {
- p.error(p.pos, "missing ',' before newline in "+context)
- return true // "insert" the comma and continue
-
- }
- */
+ if p.tok == token.SEMICOLON && p.lit == "\n" {
+ p.error(p.pos, "missing ',' before newline in "+context)
+ return true // "insert" the comma and continue
+
+ }
return false
}
@@ -661,7 +659,7 @@ func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) {
// accept them all for more robust parsing and complain later
for typ := p.parseVarType(isParam); typ != nil; {
list = append(list, typ)
- if !p.seesComma("variable list") {
+ if p.tok != token.COMMA {
break
}
p.next()
@@ -702,7 +700,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [
// Go spec: The scope of an identifier denoting a function
// parameter or result variable is the function body.
p.declare(field, nil, scope, ast.Var, idents...)\n- if !p.seesComma("parameter list") {
+ if !p.atComma("parameter list") {
break
}
p.next()
@@ -1092,7 +1090,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr {\n ellipsis = p.pos
p.next()
}
- if !p.seesComma("argument list") {
+ if !p.atComma("argument list") {
break
}
p.next()
@@ -1132,7 +1130,7 @@ func (p *parser) parseElementList() (list []ast.Expr) {\n
for p.tok != token.RBRACE && p.tok != token.EOF {
list = append(list, p.parseElement(true))
- if !p.seesComma("composite literal") {
+ if !p.atComma("composite literal") {
break
}
p.next()
コアとなるコードの解説
func (p *parser) seesComma(context string) bool
から func (p *parser) atComma(context string) bool
への変更
- メソッド名の変更:
seesComma
からatComma
へと変更されました。これは、メソッドの意図をより明確にするためのリファクタリングです。seesComma
は「カンマが見えるか」というニュアンスでしたが、atComma
は「現在カンマの位置にいるか(またはカンマと見なせる状況か)」という、より現在のパーサーの状態に焦点を当てた表現になっています。 - コメントアウトされたコードの統合: 以前
seesComma
内でコメントアウトされていた、p.tok == token.SEMICOLON && p.lit == "\n"
の条件でエラーを報告しつつtrue
を返すロジックが、atComma
の本体に直接組み込まれました。これにより、改行の前にカンマが欠落している場合に、パーサーがその場でエラーを報告し、「カンマが挿入された」かのように振る舞って解析を続行できるようになります。これは、エラー回復の重要な部分です。
parseVarList
メソッド内の変更
- if !p.seesComma("variable list") {
+ if p.tok != token.COMMA {
break
}
parseVarList
は変数宣言リストを解析するメソッドです。ループの継続条件が !p.seesComma("variable list")
から p.tok != token.COMMA
に変更されました。
- 変更前:
seesComma
がtrue
を返す限り(つまり、実際のカンマがあるか、改行による擬似カンマがある限り)ループが継続していました。 - 変更後: ループは実際の
token.COMMA
が存在しない場合にのみ終了します。これにより、atComma
が改行によるカンマ欠落を検出してエラーを報告した場合でも、p.tok
がtoken.COMMA
でなければループは終了し、パーサーは次の構文要素の解析に進むことができます。これは、エラー発生時にパーサーが不必要なトークンを読み飛ばしすぎることなく、かつ解析を継続できるような、より洗練されたエラー回復戦略を実装するためのものです。
parseParameterList
, parseCallOrConversion
, parseElementList
メソッド内の変更
これらのメソッドも同様に、リスト構造(パラメータリスト、引数リスト、複合リテラルの要素リスト)を解析する部分で、ループの継続条件が !p.seesComma(...)
から !p.atComma(...)
または p.tok != token.COMMA
に変更されています。
-
parseParameterList
:- if !p.seesComma("parameter list") { + if !p.atComma("parameter list") { break }
ここでは
!p.atComma
に変更されています。atComma
がエラーを報告しつつtrue
を返す場合、!p.atComma
はfalse
となり、ループは継続します。これは、パラメータリストの解析において、カンマが欠落していても、その後のパラメータを解析し続けようとする意図があることを示唆しています。 -
parseCallOrConversion
:- if !p.seesComma("argument list") { + if !p.atComma("argument list") { break }
関数呼び出しや型変換の引数リストの解析です。ここも
!p.atComma
に変更されています。 -
parseElementList
:- if !p.seesComma("composite literal") { + if !p.atComma("composite literal") { break }
複合リテラル(配列、スライス、マップ、構造体リテラル)の要素リストの解析です。ここも
!p.atComma
に変更されています。
これらの変更は、Goのパーサーが、カンマの欠落という一般的な構文エラーに対して、より堅牢かつ柔軟に対応できるようにするためのものです。エラーを適切に報告しつつ、可能な限り解析を続行することで、開発者にとってより有用なエラーメッセージを提供し、コンパイルプロセス全体の効率を向上させます。
関連リンク
- Go言語のパーサーに関する公式ドキュメントやチュートリアルは、Goの公式ウェブサイトやGoのソースコードリポジトリで確認できます。
- Go言語の構文解析に関する詳細な情報は、
go/parser
およびgo/ast
パッケージのドキュメントを参照してください。
参考にした情報源リンク
- golang/go GitHub repository
- Go language specification
- Go packages documentation (go/parser, go/ast)
- Go CL 5752060 (これはコミットメッセージに記載されているリンクであり、この変更のコードレビューページです)
- Go parser error recovery missing comma newline - Google Search