[インデックス 16725] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)における定数(const
)および変数(var
)宣言の解析ロジックの変更に関するものです。具体的には、パーサーの厳密さを緩和し、一部の構文チェックをより後段の型チェッカーに委ねるように修正されています。これにより、パーサーはより寛容になり、型チェッカーが意味的な検証を行う役割を強化しています。
コミット
commit 4fdc81d001b02918728f8c8bcac99323c6a83b22
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jul 10 12:01:07 2013 -0700
go/parser: more tolerant parsing of const and var decls
Instead, rely on the type checker.
R=adonovan
CC=golang-dev
https://golang.org/cl/10826044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4fdc81d001b02918728f8c8bcac99323c6a83b22
元コミット内容
go/parser: more tolerant parsing of const and var decls
Instead, rely on the type checker.
R=adonovan
CC=golang-dev
https://golang.org/cl/10826044
変更の背景
Go言語のコンパイラは、ソースコードを機械語に変換する過程で、いくつかのフェーズを経ます。一般的なコンパイラのフェーズとして、字句解析(Lexical Analysis)、構文解析(Syntactic Analysis/Parsing)、意味解析(Semantic Analysis)、最適化(Optimization)、コード生成(Code Generation)などがあります。
このコミットが行われた背景には、Goコンパイラの設計思想、特にパーサーと型チェッカーの役割分担の最適化があります。以前のパーサーは、const
やvar
宣言において、初期値の有無や=
の存在について比較的厳密なチェックを行っていました。しかし、このような構文的な制約の一部は、実際には型チェッカーが意味的な観点からより適切に検証できるものでした。
例えば、const c;
のような宣言は、構文的には有効に見えるかもしれませんが、意味的には定数に値が割り当てられていないため不正です。このようなケースをパーサー段階でエラーとして扱うのではなく、より高レベルな意味解析を行う型チェッカーにその判断を委ねることで、パーサーの複雑さを軽減し、コンパイラ全体の柔軟性と堅牢性を向上させる狙いがありました。
この変更は、コンパイラの各フェーズの責任範囲を明確にし、エラー検出のタイミングを最適化する一般的なコンパイラ設計のプラクティスに沿ったものです。パーサーは純粋な構文構造の検証に集中し、意味的な整合性や型の一貫性に関するチェックは型チェッカーに任せるという方針転換を示しています。
前提知識の解説
Go言語のコンパイラとフェーズ
Go言語のソースコードが実行可能ファイルになるまでには、主に以下のフェーズを経ます。
- 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに分解します。
- 構文解析 (Syntactic Analysis / Parsing): トークンのストリームを受け取り、Go言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。このASTは、プログラムの構造を階層的に表現したものです。
go/parser
パッケージがこの役割を担います。 - 意味解析 (Semantic Analysis): ASTを走査し、プログラムの意味的な正当性を検証します。これには、型チェック、変数や関数の宣言と使用の一致、スコープの解決などが含まれます。Goコンパイラでは、このフェーズで型チェッカーが重要な役割を果たします。
- 中間コード生成 (Intermediate Code Generation): ASTから、特定のアーキテクチャに依存しない中間表現(IR)を生成します。
- 最適化 (Optimization): 中間コードを分析し、より効率的なコードに変換します。
- コード生成 (Code Generation): 最適化された中間コードを、ターゲットとなるCPUアーキテクチャの機械語に変換します。
go/parser
パッケージ
go/parser
パッケージは、Go言語のソースコードを解析し、抽象構文木(AST)を生成するための標準ライブラリです。このパッケージは、Goコンパイラのフロントエンドの一部として機能し、ソースコードがGo言語の文法規則に準拠しているかを検証します。
const
とvar
宣言
Go言語におけるconst
とvar
は、それぞれ定数と変数を宣言するためのキーワードです。
const
(定数):- コンパイル時に値が決定され、実行中に変更できない値を宣言します。
- 通常、宣言と同時に初期化が必要です(例:
const pi = 3.14
)。 - 型を明示しない場合、値から型が推論されます。
iota
キーワードと組み合わせて、連続する定数を宣言する慣用的な方法があります。const ( _ = iota // 0 KB = 1 << (10 * iota) // 1 << 10 = 1024 MB = 1 << (10 * iota) // 1 << 20 = 1048576 )
var
(変数):- 実行中に値が変更可能な変数を宣言します。
- 宣言時に初期化しない場合、ゼロ値(数値型は0、文字列型は""、ブール型はfalse、参照型はnilなど)で初期化されます。
- 型を明示しない場合、初期値から型が推論されます(例:
var x = 10
)。 - 型を明示し、初期値を省略することも可能です(例:
var s string
)。
型チェッカー (Type Checker)
型チェッカーは、コンパイラの意味解析フェーズの一部であり、プログラムが型システムの一貫性規則に従っているかを検証します。具体的には、以下のようなチェックを行います。
- 型の互換性: 演算子のオペランドの型が適切か、関数の引数の型がパラメータの型と一致するかなど。
- 未宣言の識別子: 使用されている変数や関数が宣言されているか。
- 初期化の要件: 定数や変数が適切に初期化されているか。
- スコープ規則: 識別子が適切なスコープ内で参照されているか。
このコミットでは、const c;
のような、構文的にはconst
宣言の形式を取っているが、意味的に初期値が欠けている不正なケースを、パーサーではなく型チェッカーが検出するように役割を移しています。
技術的詳細
このコミットの技術的な核心は、src/pkg/go/parser/parser.go
内の parseValueSpec
関数における変更です。この関数は、const
やvar
といったキーワードで始まる値の宣言(ValueSpec
)を解析する役割を担っています。
変更前のコードでは、token.ASSIGN
(=
)の存在をチェックする条件式が複雑でした。特に、const
宣言の場合、型が指定されているか、またはiota
が使用されているかによって、=
の存在を必須とするかどうかのロジックが含まれていました。
// 変更前
if p.tok == token.ASSIGN || keyword == token.CONST && (typ != nil || iota == 0) || keyword == token.VAR && typ == nil {
p.expect(token.ASSIGN)
values = p.parseRhsList()
}
この条件式は、以下のいずれかの条件が真であれば、=
(token.ASSIGN
)を期待し、右辺(RhsList
)を解析するというものでした。
- 現在のトークンが
=
である (p.tok == token.ASSIGN
) - キーワードが
const
であり、かつ型が指定されているか (typ != nil
)、またはiota
が0である (iota == 0
) - キーワードが
var
であり、かつ型が指定されていない (typ == nil
)
このロジックは、パーサーが初期化子の有無についてある程度の意味的な判断を行っていたことを示しています。例えば、const c;
のような宣言は、keyword == token.CONST && (typ != nil || iota == 0)
の条件に合致せず、p.tok == token.ASSIGN
も偽であるため、=
がない場合にパーサーがエラーを報告していました。これは、short_test.go
の invalids
リストに含まれていた const c; /* ERROR "expected '='" */
というテストケースからも確認できます。
変更後のコードは、この条件式を大幅に簡素化しています。
// 変更後
// always permit optional initialization for more tolerant parsing
if p.tok == token.ASSIGN {
p.next()
values = p.parseRhsList()
}
この変更により、パーサーは現在のトークンが=
である場合にのみ、初期化子(右辺)の解析に進むようになりました。それ以外のケース、つまり=
が存在しない場合は、パーサーは初期化子を期待せず、そのまま次のトークンに進みます。
この簡素化の意図は、コメント // always permit optional initialization for more tolerant parsing
に明確に示されています。パーサーは、const
やvar
宣言において初期化子が省略されている場合でも、それを構文エラーとして扱わなくなりました。
では、const c;
のような不正な宣言はどのように扱われるのでしょうか? コミットメッセージにある Instead, rely on the type checker.
がその答えです。パーサーは構文的に有効なASTを生成しますが、そのASTが意味的に正しいかどうか(例えば、定数に値が割り当てられているか)の検証は、後続の型チェッカーの役割となります。型チェッカーは、初期化されていない定数宣言を検出してエラーを報告するようになります。
この変更は、コンパイラの各フェーズの責任をより明確に分離し、パーサーをより「構文的」な役割に特化させるという設計原則に基づいています。これにより、パーサーのコードはよりシンプルになり、保守性が向上します。また、エラーメッセージの質も向上する可能性があります。なぜなら、型チェッカーはより豊富な意味的コンテキストを持っているため、より具体的で分かりやすいエラーメッセージを提供できるからです。
src/pkg/go/parser/short_test.go
から const c; /* ERROR "expected '='" */
のテストケースが削除されたのは、この変更によって、もはやパーサーがこのケースでエラーを報告しなくなったためです。このテストケースは、パーサーが=
の欠如を構文エラーとして検出することを期待していましたが、その期待が変更されたため削除されました。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go
index ded733489b..42a1c5e57c 100644
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -2180,8 +2180,9 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota\
idents := p.parseIdentList()\
typ := p.tryType()\
var values []ast.Expr\
- if p.tok == token.ASSIGN || keyword == token.CONST && (typ != nil || iota == 0) || keyword == token.VAR && typ == nil {\
- p.expect(token.ASSIGN)\
+ // always permit optional initialization for more tolerant parsing
+ if p.tok == token.ASSIGN {\
+ p.next()\
values = p.parseRhsList()\
}\
p.expectSemi() // call before accessing p.linecomment
diff --git a/src/pkg/go/parser/short_test.go b/src/pkg/go/parser/short_test.go
index a15b3ed35c..a581319e05 100644
--- a/src/pkg/go/parser/short_test.go
+++ b/src/pkg/go/parser/short_test.go
@@ -47,7 +47,6 @@ var invalids = []string{
`package p; func f() { if { /* ERROR "expected operand" */ } };`,\
`package p; func f() { if ; { /* ERROR "expected operand" */ } };`,\
`package p; func f() { if f(); { /* ERROR "expected operand" */ } };`,\
- `package p; const c; /* ERROR "expected '='" */`,\
`package p; func f() { if _ /* ERROR "expected condition" */ = range x; true {} };`,\
`package p; func f() { switch _ /* ERROR "expected condition" */ = range x; true {} };`,\
`package p; func f() { for _ = range x ; /* ERROR "expected '{'" */ ; {} };`,\
コアとなるコードの解説
src/pkg/go/parser/parser.go
の変更
parser.go
の parseValueSpec
関数は、const
や var
宣言の個々の仕様(例: x, y int = 1, 2
の x, y int = 1, 2
部分)を解析します。
変更前は、初期化子(=
とそれに続く値)の有無を判断する条件式が複雑でした。
// 変更前
if p.tok == token.ASSIGN || keyword == token.CONST && (typ != nil || iota == 0) || keyword == token.VAR && typ == nil {
p.expect(token.ASSIGN) // '=' を期待
values = p.parseRhsList() // 右辺を解析
}
この条件式は、以下の場合に=
を必須としていました。
- 現在のトークンが既に
=
の場合。 const
宣言で、型が明示されているか、またはiota
が使用されている場合。var
宣言で、型が明示されていない場合(この場合、初期化子から型が推論されるため、=
が必須)。
このロジックは、パーサーが構文解析の段階で、初期化子の有無に関する意味的な制約の一部を強制しようとしていたことを示しています。例えば、const c;
のような宣言は、=
がないため、この条件式によってパーサーエラーとなっていました。
変更後は、この条件式が以下のように簡素化されました。
// 変更後
// always permit optional initialization for more tolerant parsing
if p.tok == token.ASSIGN {
p.next() // 次のトークンに進む('='を消費)
values = p.parseRhsList() // 右辺を解析
}
この変更により、パーサーは現在のトークンが=
である場合にのみ、初期化子の解析に進みます。=
が存在しない場合、パーサーは初期化子を期待せず、エラーも報告しません。これは、const c;
のような宣言が、パーサーにとっては構文的に有効なものとして扱われるようになったことを意味します。
コメント // always permit optional initialization for more tolerant parsing
が示すように、この変更の目的は、パーサーをより「寛容」にすることです。初期化子の有無に関する意味的なチェックは、パーサーの後のフェーズである型チェッカーに完全に委ねられることになります。型チェッカーは、const c;
のような初期化されていない定数宣言を、意味的なエラーとして適切に検出します。
src/pkg/go/parser/short_test.go
の変更
short_test.go
は、Goパーサーの短いテストケースを集めたファイルです。
変更前は、invalids
というスライスに、パーサーがエラーを報告すべき不正なコードスニペットが含まれていました。
// 変更前 (invalids スライスの一部)
`package p; const c; /* ERROR "expected '='" */`,
この行は、const c;
という宣言が、expected '='
というエラーメッセージを伴ってパーサーによって拒否されることを期待していました。
変更後、この行は削除されました。
// 変更後 (invalids スライスから削除)
- `package p; const c; /* ERROR "expected '='" */`,
この削除は、parser.go
の変更と直接関連しています。パーサーがconst c;
を構文エラーとして扱わなくなったため、このテストケースはもはや適切ではなくなりました。このテストケースが削除されたことで、const c;
はパーサーを通過し、その後の型チェッカーによって意味的なエラーとして検出されるという新しい挙動が反映されています。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- Go言語のパーサーパッケージ (
go/parser
): https://pkg.go.dev/go/parser - Go言語の抽象構文木 (
go/ast
): https://pkg.go.dev/go/ast - Go言語のコンパイラ設計に関する一般的な情報: https://go.dev/doc/articles/go_compiler
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/4fdc81d001b02918728f8c8bcac99323c6a83b22
- Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://golang.org/cl/10826044
- Go言語のコンパイラに関する一般的な情報源(例: Goコンパイラの内部構造に関するブログ記事や論文など)
- "Go's Declaration Syntax" by Rob Pike: https://go.dev/blog/declaration-syntax (Goの宣言構文の背景理解に役立つ)
- "The Go Programming Language Specification": https://go.dev/ref/spec (Go言語の文法とセマンティクスの公式定義)
- "Go compiler internals" (非公式な解説やブログ記事): Web検索で "Go compiler internals" や "Go compiler phases" などのキーワードで検索すると、Goコンパイラの動作原理を解説した多くのリソースが見つかります。# [インデックス 16725] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)における定数(const
)および変数(var
)宣言の解析ロジックの変更に関するものです。具体的には、パーサーの厳密さを緩和し、一部の構文チェックをより後段の型チェッカーに委ねるように修正されています。これにより、パーサーはより寛容になり、型チェッカーが意味的な検証を行う役割を強化しています。
コミット
commit 4fdc81d001b02918728f8c8bcac99323c6a83b22
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jul 10 12:01:07 2013 -0700
go/parser: more tolerant parsing of const and var decls
Instead, rely on the type checker.
R=adonovan
CC=golang-dev
https://golang.org/cl/10826044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4fdc81d001b02918728f8c8bcac99323c6a83b22
元コミット内容
go/parser: more tolerant parsing of const and var decls
Instead, rely on the type checker.
R=adonovan
CC=golang-dev
https://golang.org/cl/10826044
変更の背景
Go言語のコンパイラは、ソースコードを機械語に変換する過程で、いくつかのフェーズを経ます。一般的なコンパイラのフェーズとして、字句解析(Lexical Analysis)、構文解析(Syntactic Analysis/Parsing)、意味解析(Semantic Analysis)、最適化(Optimization)、コード生成(Code Generation)などがあります。
このコミットが行われた背景には、Goコンパイラの設計思想、特にパーサーと型チェッカーの役割分担の最適化があります。以前のパーサーは、const
やvar
宣言において、初期値の有無や=
の存在について比較的厳密なチェックを行っていました。しかし、このような構文的な制約の一部は、実際には型チェッカーが意味的な観点からより適切に検証できるものでした。
例えば、const c;
のような宣言は、構文的には有効に見えるかもしれませんが、意味的には定数に値が割り当てられていないため不正です。このようなケースをパーサー段階でエラーとして扱うのではなく、より高レベルな意味解析を行う型チェッカーにその判断を委ねることで、パーサーの複雑さを軽減し、コンパイラ全体の柔軟性と堅牢性を向上させる狙いがありました。
この変更は、コンパイラの各フェーズの責任範囲を明確にし、エラー検出のタイミングを最適化する一般的なコンパイラ設計のプラクティスに沿ったものです。パーサーは純粋な構文構造の検証に集中し、意味的な整合性や型の一貫性に関するチェックは型チェッカーに任せるという方針転換を示しています。
前提知識の解説
Go言語のコンパイラとフェーズ
Go言語のソースコードが実行可能ファイルになるまでには、主に以下のフェーズを経ます。
- 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに分解します。
- 構文解析 (Syntactic Analysis / Parsing): トークンのストリームを受け取り、Go言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。このASTは、プログラムの構造を階層的に表現したものです。
go/parser
パッケージがこの役割を担います。 - 意味解析 (Semantic Analysis): ASTを走査し、プログラムの意味的な正当性を検証します。これには、型チェック、変数や関数の宣言と使用の一致、スコープの解決などが含まれます。Goコンパイラでは、このフェーズで型チェッカーが重要な役割を果たします。
- 中間コード生成 (Intermediate Code Generation): ASTから、特定のアーキテクチャに依存しない中間表現(IR)を生成します。
- 最適化 (Optimization): 中間コードを分析し、より効率的なコードに変換します。
- コード生成 (Code Generation): 最適化された中間コードを、ターゲットとなるCPUアーキテクチャの機械語に変換します。
go/parser
パッケージ
go/parser
パッケージは、Go言語のソースコードを解析し、抽象構文木(AST)を生成するための標準ライブラリです。このパッケージは、Goコンパイラのフロントエンドの一部として機能し、ソースコードがGo言語の文法規則に準拠しているかを検証します。
const
とvar
宣言
Go言語におけるconst
とvar
は、それぞれ定数と変数を宣言するためのキーワードです。
const
(定数):- コンパイル時に値が決定され、実行中に変更できない値を宣言します。
- 通常、宣言と同時に初期化が必要です(例:
const pi = 3.14
)。 - 型を明示しない場合、値から型が推論されます。
iota
キーワードと組み合わせて、連続する定数を宣言する慣用的な方法があります。const ( _ = iota // 0 KB = 1 << (10 * iota) // 1 << 10 = 1024 MB = 1 << (10 * iota) // 1 << 20 = 1048576 )
var
(変数):- 実行中に値が変更可能な変数を宣言します。
- 宣言時に初期化しない場合、ゼロ値(数値型は0、文字列型は""、ブール型はfalse、参照型はnilなど)で初期化されます。
- 型を明示しない場合、初期値から型が推論されます(例:
var x = 10
)。 - 型を明示し、初期値を省略することも可能です(例:
var s string
)。
型チェッカー (Type Checker)
型チェッカーは、コンパイラの意味解析フェーズの一部であり、プログラムが型システムの一貫性規則に従っているかを検証します。具体的には、以下のようなチェックを行います。
- 型の互換性: 演算子のオペランドの型が適切か、関数の引数の型がパラメータの型と一致するかなど。
- 未宣言の識別子: 使用されている変数や関数が宣言されているか。
- 初期化の要件: 定数や変数が適切に初期化されているか。
- スコープ規則: 識別子が適切なスコープ内で参照されているか。
このコミットでは、const c;
のような、構文的にはconst
宣言の形式を取っているが、意味的に初期値が欠けている不正なケースを、パーサーではなく型チェッカーが検出するように役割を移しています。
技術的詳細
このコミットの技術的な核心は、src/pkg/go/parser/parser.go
内の parseValueSpec
関数における変更です。この関数は、const
やvar
といったキーワードで始まる値の宣言(ValueSpec
)を解析する役割を担っています。
変更前のコードでは、token.ASSIGN
(=
)の存在をチェックする条件式が複雑でした。特に、const
宣言の場合、型が指定されているか、またはiota
が使用されているかによって、=
の存在を必須とするかどうかのロジックが含まれていました。
// 変更前
if p.tok == token.ASSIGN || keyword == token.CONST && (typ != nil || iota == 0) || keyword == token.VAR && typ == nil {
p.expect(token.ASSIGN)
values = p.parseRhsList()
}
この条件式は、以下のいずれかの条件が真であれば、=
(token.ASSIGN
)を期待し、右辺(RhsList
)を解析するというものでした。
- 現在のトークンが
=
である (p.tok == token.ASSIGN
) - キーワードが
const
であり、かつ型が指定されているか (typ != nil
)、またはiota
が0である (iota == 0
) - キーワードが
var
であり、かつ型が指定されていない (typ == nil
)
このロジックは、パーサーが初期化子の有無についてある程度の意味的な判断を行っていたことを示しています。例えば、const c;
のような宣言は、keyword == token.CONST && (typ != nil || iota == 0)
の条件に合致せず、p.tok == token.ASSIGN
も偽であるため、=
がない場合にパーサーがエラーを報告していました。これは、short_test.go
の invalids
リストに含まれていた const c; /* ERROR "expected '='" */
というテストケースからも確認できます。
変更後のコードは、この条件式を大幅に簡素化しています。
// 変更後
// always permit optional initialization for more tolerant parsing
if p.tok == token.ASSIGN {
p.next()
values = p.parseRhsList()
}
この変更により、パーサーは現在のトークンが=
である場合にのみ、初期化子の解析に進むようになりました。それ以外のケース、つまり=
が存在しない場合は、パーサーは初期化子を期待せず、そのまま次のトークンに進みます。
この簡素化の意図は、コメント // always permit optional initialization for more tolerant parsing
に明確に示されています。パーサーは、const
やvar
宣言において初期化子が省略されている場合でも、それを構文エラーとして扱わなくなりました。
では、const c;
のような不正な宣言はどのように扱われるのでしょうか? コミットメッセージにある Instead, rely on the type checker.
がその答えです。パーサーは構文的に有効なASTを生成しますが、そのASTが意味的に正しいかどうか(例えば、定数に値が割り当てられているか)の検証は、後続の型チェッカーの役割となります。型チェッカーは、初期化されていない定数宣言を検出してエラーを報告するようになります。
この変更は、コンパイラの各フェーズの責任をより明確に分離し、パーサーをより「構文的」な役割に特化させるという設計原則に基づいています。これにより、パーサーのコードはよりシンプルになり、保守性が向上します。また、エラーメッセージの質も向上する可能性があります。なぜなら、型チェッカーはより豊富な意味的コンテキストを持っているため、より具体的で分かりやすいエラーメッセージを提供できるからです。
src/pkg/go/parser/short_test.go
から const c; /* ERROR "expected '='" */
のテストケースが削除されたのは、この変更によって、もはやパーサーがこのケースでエラーを報告しなくなったためです。このテストケースは、パーサーが=
の欠如を構文エラーとして検出することを期待していましたが、その期待が変更されたため削除されました。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go
index ded733489b..42a1c5e57c 100644
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -2180,8 +2180,9 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota\
idents := p.parseIdentList()\
typ := p.tryType()\
var values []ast.Expr\
- if p.tok == token.ASSIGN || keyword == token.CONST && (typ != nil || iota == 0) || keyword == token.VAR && typ == nil {\
- p.expect(token.ASSIGN)\
+ // always permit optional initialization for more tolerant parsing
+ if p.tok == token.ASSIGN {\
+ p.next()\
values = p.parseRhsList()\
}\
p.expectSemi() // call before accessing p.linecomment
diff --git a/src/pkg/go/parser/short_test.go b/src/pkg/go/parser/short_test.go
index a15b3ed35c..a581319e05 100644
--- a/src/pkg/go/parser/short_test.go
+++ b/src/pkg/go/parser/short_test.go
@@ -47,7 +47,6 @@ var invalids = []string{\
`package p; func f() { if { /* ERROR "expected operand" */ } };`,\
`package p; func f() { if ; { /* ERROR "expected operand" */ } };`,\
`package p; func f() { if f(); { /* ERROR "expected operand" */ } };`,\
- `package p; const c; /* ERROR "expected '='" */`,\
`package p; func f() { if _ /* ERROR "expected condition" */ = range x; true {} };`,\
`package p; func f() { switch _ /* ERROR "expected condition" */ = range x; true {} };`,\
`package p; func f() { for _ = range x ; /* ERROR "expected '{'" */ ; {} };`,\
コアとなるコードの解説
src/pkg/go/parser/parser.go
の変更
parser.go
の parseValueSpec
関数は、const
や var
宣言の個々の仕様(例: x, y int = 1, 2
の x, y int = 1, 2
部分)を解析します。
変更前は、初期化子(=
とそれに続く値)の有無を判断する条件式が複雑でした。
// 変更前
if p.tok == token.ASSIGN || keyword == token.CONST && (typ != nil || iota == 0) || keyword == token.VAR && typ == nil {
p.expect(token.ASSIGN) // '=' を期待
values = p.parseRhsList() // 右辺を解析
}
この条件式は、以下の場合に=
を必須としていました。
- 現在のトークンが既に
=
の場合。 const
宣言で、型が明示されているか、またはiota
が使用されている場合。var
宣言で、型が明示されていない場合(この場合、初期化子から型が推論されるため、=
が必須)。
このロジックは、パーサーが構文解析の段階で、初期化子の有無に関する意味的な制約の一部を強制しようとしていたことを示しています。例えば、const c;
のような宣言は、=
がないため、この条件式によってパーサーエラーとなっていました。
変更後は、この条件式が以下のように簡素化されました。
// 変更後
// always permit optional initialization for more tolerant parsing
if p.tok == token.ASSIGN {
p.next() // 次のトークンに進む('='を消費)
values = p.parseRhsList() // 右辺を解析
}
この変更により、パーサーは現在のトークンが=
である場合にのみ、初期化子の解析に進みます。=
が存在しない場合、パーサーは初期化子を期待せず、エラーも報告しません。これは、const c;
のような宣言が、パーサーにとっては構文的に有効なものとして扱われるようになったことを意味します。
コメント // always permit optional initialization for more tolerant parsing
が示すように、この変更の目的は、パーサーをより「寛容」にすることです。初期化子の有無に関する意味的なチェックは、パーサーの後のフェーズである型チェッカーに完全に委ねられることになります。型チェッカーは、const c;
のような初期化されていない定数宣言を、意味的なエラーとして適切に検出します。
src/pkg/go/parser/short_test.go
の変更
short_test.go
は、Goパーサーの短いテストケースを集めたファイルです。
変更前は、invalids
というスライスに、パーサーがエラーを報告すべき不正なコードスニペットが含まれていました。
// 変更前 (invalids スライスの一部)
`package p; const c; /* ERROR "expected '='" */`,
この行は、const c;
という宣言が、expected '='
というエラーメッセージを伴ってパーサーによって拒否されることを期待していました。
変更後、この行は削除されました。
// 変更後 (invalids スライスから削除)
- `package p; const c; /* ERROR "expected '='" */`,
この削除は、parser.go
の変更と直接関連しています。パーサーがconst c;
を構文エラーとして扱わなくなったため、このテストケースはもはや適切ではなくなりました。このテストケースが削除されたことで、const c;
はパーサーを通過し、その後の型チェッカーによって意味的なエラーとして検出されるという新しい挙動が反映されています。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- Go言語のパーサーパッケージ (
go/parser
): https://pkg.go.dev/go/parser - Go言語の抽象構文木 (
go/ast
): https://pkg.go.dev/go/ast - Go言語のコンパイラ設計に関する一般的な情報: https://go.dev/doc/articles/go_compiler
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/4fdc81d001b02918728f8c8bcac99323c6a83b22
- Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://golang.org/cl/10826044
- Go言語のコンパイラに関する一般的な情報源(例: Goコンパイラの内部構造に関するブログ記事や論文など)
- "Go's Declaration Syntax" by Rob Pike: https://go.dev/blog/declaration-syntax (Goの宣言構文の背景理解に役立つ)
- "The Go Programming Language Specification": https://go.dev/ref/spec (Go言語の文法とセマンティクスの公式定義)
- "Go compiler internals" (非公式な解説やブログ記事): Web検索で "Go compiler internals" や "Go compiler phases" などのキーワードで検索すると、Goコンパイラの動作原理を解説した多くのリソースが見つかります。