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

[インデックス 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コンパイラの設計思想、特にパーサーと型チェッカーの役割分担の最適化があります。以前のパーサーは、constvar宣言において、初期値の有無や=の存在について比較的厳密なチェックを行っていました。しかし、このような構文的な制約の一部は、実際には型チェッカーが意味的な観点からより適切に検証できるものでした。

例えば、const c; のような宣言は、構文的には有効に見えるかもしれませんが、意味的には定数に値が割り当てられていないため不正です。このようなケースをパーサー段階でエラーとして扱うのではなく、より高レベルな意味解析を行う型チェッカーにその判断を委ねることで、パーサーの複雑さを軽減し、コンパイラ全体の柔軟性と堅牢性を向上させる狙いがありました。

この変更は、コンパイラの各フェーズの責任範囲を明確にし、エラー検出のタイミングを最適化する一般的なコンパイラ設計のプラクティスに沿ったものです。パーサーは純粋な構文構造の検証に集中し、意味的な整合性や型の一貫性に関するチェックは型チェッカーに任せるという方針転換を示しています。

前提知識の解説

Go言語のコンパイラとフェーズ

Go言語のソースコードが実行可能ファイルになるまでには、主に以下のフェーズを経ます。

  1. 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに分解します。
  2. 構文解析 (Syntactic Analysis / Parsing): トークンのストリームを受け取り、Go言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。このASTは、プログラムの構造を階層的に表現したものです。go/parserパッケージがこの役割を担います。
  3. 意味解析 (Semantic Analysis): ASTを走査し、プログラムの意味的な正当性を検証します。これには、型チェック、変数や関数の宣言と使用の一致、スコープの解決などが含まれます。Goコンパイラでは、このフェーズで型チェッカーが重要な役割を果たします。
  4. 中間コード生成 (Intermediate Code Generation): ASTから、特定のアーキテクチャに依存しない中間表現(IR)を生成します。
  5. 最適化 (Optimization): 中間コードを分析し、より効率的なコードに変換します。
  6. コード生成 (Code Generation): 最適化された中間コードを、ターゲットとなるCPUアーキテクチャの機械語に変換します。

go/parserパッケージ

go/parserパッケージは、Go言語のソースコードを解析し、抽象構文木(AST)を生成するための標準ライブラリです。このパッケージは、Goコンパイラのフロントエンドの一部として機能し、ソースコードがGo言語の文法規則に準拠しているかを検証します。

constvar宣言

Go言語におけるconstvarは、それぞれ定数と変数を宣言するためのキーワードです。

  • 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 関数における変更です。この関数は、constvarといったキーワードで始まる値の宣言(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)を解析するというものでした。

  1. 現在のトークンが=である (p.tok == token.ASSIGN)
  2. キーワードがconstであり、かつ型が指定されているか (typ != nil)、またはiotaが0である (iota == 0)
  3. キーワードがvarであり、かつ型が指定されていない (typ == nil)

このロジックは、パーサーが初期化子の有無についてある程度の意味的な判断を行っていたことを示しています。例えば、const c; のような宣言は、keyword == token.CONST && (typ != nil || iota == 0) の条件に合致せず、p.tok == token.ASSIGN も偽であるため、= がない場合にパーサーがエラーを報告していました。これは、short_test.goinvalids リストに含まれていた 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 に明確に示されています。パーサーは、constvar宣言において初期化子が省略されている場合でも、それを構文エラーとして扱わなくなりました。

では、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.goparseValueSpec 関数は、constvar 宣言の個々の仕様(例: x, y int = 1, 2x, 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; はパーサーを通過し、その後の型チェッカーによって意味的なエラーとして検出されるという新しい挙動が反映されています。

関連リンク

参考にした情報源リンク

  • 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コンパイラの設計思想、特にパーサーと型チェッカーの役割分担の最適化があります。以前のパーサーは、constvar宣言において、初期値の有無や=の存在について比較的厳密なチェックを行っていました。しかし、このような構文的な制約の一部は、実際には型チェッカーが意味的な観点からより適切に検証できるものでした。

例えば、const c; のような宣言は、構文的には有効に見えるかもしれませんが、意味的には定数に値が割り当てられていないため不正です。このようなケースをパーサー段階でエラーとして扱うのではなく、より高レベルな意味解析を行う型チェッカーにその判断を委ねることで、パーサーの複雑さを軽減し、コンパイラ全体の柔軟性と堅牢性を向上させる狙いがありました。

この変更は、コンパイラの各フェーズの責任範囲を明確にし、エラー検出のタイミングを最適化する一般的なコンパイラ設計のプラクティスに沿ったものです。パーサーは純粋な構文構造の検証に集中し、意味的な整合性や型の一貫性に関するチェックは型チェッカーに任せるという方針転換を示しています。

前提知識の解説

Go言語のコンパイラとフェーズ

Go言語のソースコードが実行可能ファイルになるまでには、主に以下のフェーズを経ます。

  1. 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに分解します。
  2. 構文解析 (Syntactic Analysis / Parsing): トークンのストリームを受け取り、Go言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。このASTは、プログラムの構造を階層的に表現したものです。go/parserパッケージがこの役割を担います。
  3. 意味解析 (Semantic Analysis): ASTを走査し、プログラムの意味的な正当性を検証します。これには、型チェック、変数や関数の宣言と使用の一致、スコープの解決などが含まれます。Goコンパイラでは、このフェーズで型チェッカーが重要な役割を果たします。
  4. 中間コード生成 (Intermediate Code Generation): ASTから、特定のアーキテクチャに依存しない中間表現(IR)を生成します。
  5. 最適化 (Optimization): 中間コードを分析し、より効率的なコードに変換します。
  6. コード生成 (Code Generation): 最適化された中間コードを、ターゲットとなるCPUアーキテクチャの機械語に変換します。

go/parserパッケージ

go/parserパッケージは、Go言語のソースコードを解析し、抽象構文木(AST)を生成するための標準ライブラリです。このパッケージは、Goコンパイラのフロントエンドの一部として機能し、ソースコードがGo言語の文法規則に準拠しているかを検証します。

constvar宣言

Go言語におけるconstvarは、それぞれ定数と変数を宣言するためのキーワードです。

  • 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 関数における変更です。この関数は、constvarといったキーワードで始まる値の宣言(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)を解析するというものでした。

  1. 現在のトークンが=である (p.tok == token.ASSIGN)
  2. キーワードがconstであり、かつ型が指定されているか (typ != nil)、またはiotaが0である (iota == 0)
  3. キーワードがvarであり、かつ型が指定されていない (typ == nil)

このロジックは、パーサーが初期化子の有無についてある程度の意味的な判断を行っていたことを示しています。例えば、const c; のような宣言は、keyword == token.CONST && (typ != nil || iota == 0) の条件に合致せず、p.tok == token.ASSIGN も偽であるため、= がない場合にパーサーがエラーを報告していました。これは、short_test.goinvalids リストに含まれていた 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 に明確に示されています。パーサーは、constvar宣言において初期化子が省略されている場合でも、それを構文エラーとして扱わなくなりました。

では、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.goparseValueSpec 関数は、constvar 宣言の個々の仕様(例: x, y int = 1, 2x, 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; はパーサーを通過し、その後の型チェッカーによって意味的なエラーとして検出されるという新しい挙動が反映されています。

関連リンク

参考にした情報源リンク

  • 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コンパイラの動作原理を解説した多くのリソースが見つかります。