[インデックス 13141] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)における内部的なクリーンアップとスコープ管理の改善に関するものです。具体的には、パーサーの初期化ロジックを簡素化し、parseFile
関数がGoソースファイルのパースにおける唯一のエントリーポイントであることを前提としたスコープの対称的な開閉を保証するように変更されています。
コミット
commit 7482822bba44be97fd0b08ff396fa92f777baa42
Author: Robert Griesemer <gri@golang.org>
Date: Wed May 23 09:37:48 2012 -0700
go/parser: minor cleanup
- there is no label scope at package level
- open/close all scopes symmetrically now
that there is only one parse entry point
(parseFile)
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6230047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7482822bba44be97fd0b08ff396fa92f777baa42
元コミット内容
Goパーサーのマイナーなクリーンアップ。
- パッケージレベルにはラベルスコープが存在しない。
parseFile
が唯一のパースエントリーポイントとなったため、全てのスコープを対称的に開閉する。
変更の背景
このコミットの背景には、Go言語のパーサー(go/parser
)の内部設計の進化があります。初期のパーサーは、完全なGoソースファイルだけでなく、式(ParseExpr
)や宣言など、より小さなコード片をパースするための複数のエントリーポイントを持っていました。そのため、パーサーのinit
メソッド内で、パッケージスコープ(pkgScope
)やラベルスコープを事前に設定する必要がありました。これは、どのエントリーポイントからパースが開始されても、基本的なスコープが利用可能であることを保証するためです。
しかし、Goパーサーの設計が成熟するにつれて、完全なGoソースファイルをパースする主要な関数はparseFile
に集約されていきました。この変更により、init
メソッドで事前にスコープを設定する必要がなくなり、parseFile
関数内で必要なスコープを明示的に開閉する方が、コードの意図が明確になり、スコープ管理がより対称的で堅牢になるという判断がなされました。
また、「パッケージレベルにはラベルスコープが存在しない」という点は、Go言語のセマンティクスに基づいています。Goにおいてラベル(goto
文やbreak
/continue
文のターゲット)は、関数やブロックの内部でのみ意味を持ち、パッケージのトップレベルで宣言されることはありません。したがって、パッケージレベルでラベルスコープを管理する必要がないという認識が、このクリーンアップの一環として反映されています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラ/パーサーの基本的な概念を理解しておく必要があります。
-
Go言語の
go/parser
パッケージ:- Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成するための標準ライブラリです。
- コンパイラやリンター、コード分析ツールなどがGoのコードを理解するために利用します。
parser
構造体は、パース処理中の状態(現在のトークン、エラー、スコープ情報など)を保持します。
-
抽象構文木(AST: Abstract Syntax Tree):
- ソースコードの構造を木構造で表現したものです。
go/ast
パッケージで定義されており、ast.File
がGoソースファイル全体のASTのルートノードとなります。
-
スコープ(Scope):
- プログラム内で識別子(変数名、関数名、型名など)が参照可能な範囲を定義する概念です。
- Go言語では、ブロック、関数、パッケージなど、様々なレベルでスコープが存在します。
go/ast
パッケージのast.Scope
構造体は、このスコープ情報を表現し、識別子の解決(どの識別子がどの宣言に対応するかを見つけること)に利用されます。parser
内部では、openScope()
で新しいスコープを作成し、現在のスコープスタックにプッシュし、closeScope()
で現在のスコープをポップすることで、スコープの階層を管理します。
-
ラベルスコープ(Label Scope):
goto
文や、ラベル付きbreak
/continue
文のターゲットとなるラベルの可視性を管理する特殊なスコープです。- 通常の識別子スコープとは別に管理されることが一般的です。
-
token.FileSet
:go/token
パッケージの一部で、ソースコード内の位置情報(行番号、列番号など)を管理するためのものです。- パーサーは、エラーメッセージの生成やデバッグのために、この
FileSet
を利用します。
-
assert
関数:- Goの標準ライブラリや内部コードでよく見られる、開発時の不変条件(invariant)チェックのためのヘルパー関数です。
- 条件が満たされない場合にパニックを引き起こし、プログラムの論理的な誤りを示します。
技術的詳細
このコミットの技術的詳細は、go/parser
パッケージのparser
構造体のinit
メソッドとparseFile
メソッドにおけるスコープ管理の変更に集約されます。
変更前:
parser.init()
メソッド内で、p.openScope()
を呼び出してトップレベルのスコープを開き、それをp.pkgScope
として設定していました。これは、ParseExpr
のような他のパースエントリーポイントが存在するため、init
時に基本的なパッケージスコープを準備しておく必要があったためです。- 同様に、
p.openLabelScope()
もinit
メソッド内で呼び出され、パッケージレベルのラベルスコープが設定されていました。 parseFile()
の最後では、assert(p.topScope == p.pkgScope, "imbalanced scopes")
というアサーションがあり、parseFile
の実行中に開かれたスコープが全て閉じられ、最終的にpkgScope
がトップスコープに戻っていることを確認していました。
変更後:
parser.init()
メソッドから、p.openScope()
とp.pkgScope = p.topScope
、およびp.openLabelScope()
の呼び出しが削除されました。これにより、init
メソッドはよりシンプルになり、パーサーの初期状態はスコープが閉じられた状態になります。parseFile()
メソッドの冒頭で、p.openScope()
を呼び出して新しいスコープを開き、それをp.pkgScope
として設定するようになりました。これは、parseFile
がGoソースファイルのパースにおける唯一の「完全な」エントリーポイントであるという新しい前提に基づいています。これにより、parseFile
の実行中に必要なトップレベルのスコープが明示的に作成されます。parseFile()
メソッドの最後で、p.closeScope()
が明示的に呼び出されるようになりました。これにより、parseFile
内で開かれたトップレベルのスコープが対称的に閉じられます。- アサーションも変更され、
assert(p.topScope == nil, "unbalanced scopes")
とassert(p.labelScope == nil, "unbalanced label scopes")
になりました。これは、parseFile
の終了時には、全てのスコープ(通常のスコープとラベルスコープの両方)が適切に閉じられ、スコープスタックが空になっているべきであるという、より厳密な不変条件を強制します。
この変更により、スコープのライフサイクルがparseFile
関数内に完全にカプセル化され、パーサーの他の部分や他のパースエントリーポイント(もし存在したとしても)からの影響を受けにくくなります。また、パッケージレベルでラベルスコープが存在しないというGoのセマンティクスがコードに反映され、不要なスコープ管理が削除されました。
コアとなるコードの変更箇所
変更はsrc/pkg/go/parser/parser.go
ファイルに集中しています。
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -56,7 +56,7 @@ type parser struct {
unresolved []*ast.Ident // unresolved identifiers
imports []*ast.ImportSpec // list of imports
- // Label scope
+ // Label scopes
// (maintained by open/close LabelScope)
labelScope *ast.Scope // label scope for current function
targetStack [][]*ast.Ident // stack of unresolved labels
@@ -75,14 +75,6 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod
p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
p.next()
-
- // set up the pkgScope here (as opposed to in parseFile) because
- // there are other parser entry points (ParseExpr, etc.)
- p.openScope()
- p.pkgScope = p.topScope
-
- // for the same reason, set up a label scope
- p.openLabelScope()
}
// ----------------------------------------------------------------------------
@@ -2297,11 +2289,12 @@ func (p *parser) parseFile() *ast.File {
}
p.expectSemi()
- var decls []ast.Decl
-
// Don't bother parsing the rest if we had errors already.
// Likely not a Go source file at all.
+ p.openScope()
+ p.pkgScope = p.topScope
+ var decls []ast.Decl
if p.errors.Len() == 0 && p.mode&PackageClauseOnly == 0 {
// import decls
for p.tok == token.IMPORT {
@@ -2315,8 +2308,9 @@ func (p *parser) parseFile() *ast.File {
}
}
}
-
- assert(p.topScope == p.pkgScope, "imbalanced scopes")
+
+ p.closeScope()
+ assert(p.topScope == nil, "unbalanced scopes")
+ assert(p.labelScope == nil, "unbalanced label scopes")
// resolve global identifiers within the same file
i := 0
コアとなるコードの解説
-
parser.init()
からのスコープ初期化の削除:parser
構造体のinit
メソッド(行69-78)から、p.openScope()
、p.pkgScope = p.topScope
、p.openLabelScope()
の呼び出しが削除されました。- これは、
parseFile
が唯一の主要なパースエントリーポイントであるという新しい設計思想を反映しています。これにより、パーサーの初期化はより軽量になり、スコープの管理は実際のパース処理の開始時に行われるようになります。
-
parseFile()
内でのスコープ初期化の移動と対称的な開閉:parseFile()
メソッド(行2302-2304)の冒頭に、p.openScope()
とp.pkgScope = p.topScope
が移動されました。これにより、ファイル全体のパースが開始されると同時に、そのファイルに対応するトップレベルのパッケージスコープが作成されます。parseFile()
の終了間際(行2315)に、p.closeScope()
が追加されました。これは、parseFile
の冒頭で開かれたスコープを明示的に閉じることで、スコープの開閉が対称的であることを保証します。
-
スコープバランスのアサーションの強化:
parseFile()
の最後のアサーション(行2316-2317)が、assert(p.topScope == p.pkgScope, "imbalanced scopes")
から、assert(p.topScope == nil, "unbalanced scopes")
とassert(p.labelScope == nil, "unbalanced label scopes")
に変更されました。- これは、
parseFile
が完了した時点で、通常のスコープスタックもラベルスコープスタックも完全に空になっているべきであるという、より厳密なチェックを導入しています。これにより、パーサーがスコープを適切に管理し、メモリリークや論理的な不整合がないことを保証します。
これらの変更は、Goパーサーの内部ロジックを簡素化し、parseFile
をGoソースファイルパースの主要なエントリーポイントとして確立することで、スコープ管理の堅牢性と可読性を向上させています。
関連リンク
- Go言語の
go/parser
パッケージのドキュメント: https://pkg.go.dev/go/parser - Go言語の
go/ast
パッケージのドキュメント: https://pkg.go.dev/go/ast - Go言語の
go/token
パッケージのドキュメント: https://pkg.go.dev/go/token - Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://go-review.googlesource.com/
参考にした情報源リンク
- コミットメッセージと差分情報:
commit_data/13141.txt
の内容 - Go言語の公式ドキュメント(
go/parser
,go/ast
,go/token
パッケージ) - Go言語のソースコード(
src/pkg/go/parser/parser.go
の変更前後のコード) - Gerrit Change-Id:
6230047
(コミットメッセージに記載されているGerritの変更ID)- https://golang.org/cl/6230047 (このリンクは古いGerritのURL形式であり、現在はリダイレクトされるか、直接アクセスできない場合があります。新しい形式は
https://go-review.googlesource.com/c/go/+/6230047
のようになりますが、このコミットは非常に古いため、Gerrit上での詳細なレビュー履歴は追跡が難しい場合があります。)
- https://golang.org/cl/6230047 (このリンクは古いGerritのURL形式であり、現在はリダイレクトされるか、直接アクセスできない場合があります。新しい形式は