[インデックス 1659] ファイルの概要
このコミットは、Goコンパイラのsrc/cmd/gc/export.c
ファイルにおけるエクスポート処理のバグ修正です。具体的には、レキサー(字句解析器)が既に名前の隠蔽(hiding names)を適切に行うようになったため、以前に存在した「不器用なハック(clumsy hack)」が不要になったことを示しています。これにより、コードの複雑性が軽減され、よりクリーンな実装が実現されました。
コミット
commit 73dd4a37f9a6c6486ca80bf3f84cba7300cab5eb
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 10 13:57:31 2009 -0800
fix export bug Rob tripped over.
the lexer is already hiding names,
so this clumsy hack is no longer necessary.
R=ken
OCL=24783
CL=24783
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/73dd4a37f9a6c6486ca80bf3f84cba7300cab5eb
元コミット内容
fix export bug Rob tripped over.
the lexer is already hiding names,
so this clumsy hack is no longer necessary.
R=ken
OCL=24783
CL=24783
変更の背景
このコミットは、Goコンパイラの初期段階におけるエクスポートメカニズムの進化を示しています。コミットメッセージにある「Robが遭遇したエクスポートバグを修正」という記述は、特定のユーザー(Rob Pike氏である可能性が高い)が、パッケージのエクスポートに関する問題に直面したことを示唆しています。
問題の根源は、Go言語のパッケージシステムにおいて、外部に公開すべきでない内部的な型名やシンボルが、誤ってインポートされる可能性があったことにあります。これを防ぐために、以前はexport.c
内で「不器用なハック」が導入されていました。このハックは、特定の条件(s->export == 2
かつ!mypackage(ss)
)を満たす型名に対して、その字句的な型(s->lexical
)をLNAME
に設定することで、インポーターから見えないようにするものでした。LNAME
は、未定義の名前と同様に扱われる内部的なシンボルタイプを指します。
しかし、このコミットの時点(2009年2月)までに、Goコンパイラのレキサー(字句解析器)の機能が改善され、名前の可視性(visibility)と隠蔽(hiding)をより適切に処理できるようになりました。これにより、export.c
内の手動での名前隠蔽ロジックが冗長となり、削除しても問題ない状態になったため、このコミットで該当コードが削除されました。これは、コンパイラの設計が成熟し、より洗練された方法でシンボル解決と可視性が扱われるようになったことを意味します。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。
-
Go言語のパッケージとエクスポート:
- Go言語では、コードはパッケージ(
package
)に分割されます。 - パッケージ内の識別子(変数、関数、型など)は、その名前の最初の文字が大文字である場合、そのパッケージの外部にエクスポート(公開)されます。小文字で始まる識別子はパッケージ内部でのみ使用可能です。
- コンパイラは、他のパッケージがインポートする際に、どの識別子が見えるべきかを決定します。
- Go言語では、コードはパッケージ(
-
コンパイラのフェーズ:
- 字句解析(Lexical Analysis / Lexing): ソースコードをトークン(単語のようなもの)のストリームに変換するフェーズ。このフェーズで、識別子の名前や種類が認識されます。コミットメッセージにある「lexer」がこれにあたります。
- 構文解析(Syntactic Analysis / Parsing): トークンのストリームを解析し、プログラムの構造(抽象構文木 - AST)を構築するフェーズ。
- 意味解析(Semantic Analysis): ASTを走査し、型チェック、シンボル解決(識別子が何を指すか)、可視性チェックなどを行うフェーズ。エクスポートのルールもこのフェーズで適用されます。
-
シンボルテーブルとシンボル:
- コンパイラは、プログラム内で宣言されたすべての識別子(変数、関数、型など)に関する情報をシンボルテーブルに格納します。
- 各シンボルは、その名前、型、スコープ、可視性などの属性を持ちます。
Node *s
やType *t
は、Goコンパイラの内部表現におけるシンボルや型を表す構造体へのポインタであると推測されます。
-
export.c
の役割:- Goコンパイラの
src/cmd/gc/export.c
ファイルは、コンパイラがパッケージの外部に公開するシンボル(エクスポートされたシンボル)を処理し、他のパッケージがインポートできるようにするためのメタデータ(エクスポートデータ)を生成する役割を担っています。 importtype
関数は、他のパッケージから型をインポートする際の処理に関連していると考えられます。
- Goコンパイラの
-
s->lexical
とLNAME
:s->lexical
は、シンボルs
の字句的な種類や状態を示す内部的なフィールドであると推測されます。LNAME
は、Goコンパイラの内部で定義された定数で、おそらく「名前(識別子)」を表す字句的な種類、または未解決/未定義の名前の状態を示すものと考えられます。このコンテキストでは、シンボルを「未定義」として扱うことで、インポーターから見えなくする目的で使用されていました。
技術的詳細
このコミットの技術的詳細の中心は、src/cmd/gc/export.c
ファイル内のimporttype
関数から特定のコードブロックが削除されたことです。
削除されたコードブロックは以下の通りです。
// If type name should not be visible to importers,
// hide it by setting the lexical type to name.
// This will make references in the ordinary program
// (but not the import sections) look at s->oname,
// which is nil, as for an undefined name.
if(s->export == 2 && !mypackage(ss))
s->lexical = LNAME;
このコードは、型名がインポーターに対して可視であるべきでない場合に、その型名を隠蔽するためのロジックでした。
s->export == 2
: これは、シンボルs
のエクスポート状態を示すフラグであると推測されます。2
という値が具体的に何を意味するかはGoコンパイラの内部実装に依存しますが、おそらく「エクスポートされるべきではないが、何らかの理由でエクスポート処理の対象になっている」といった特殊な状態を示していたと考えられます。!mypackage(ss)
: これは、現在処理している型が、現在のパッケージ(ss
で示される)に属していないことをチェックしています。つまり、外部パッケージからインポートされる型、または外部パッケージにエクスポートされる型を処理している状況です。s->lexical = LNAME;
: 上記の条件が真の場合、シンボルs
の字句的な型をLNAME
に設定していました。これにより、通常のプログラム(インポートセクションを除く)での参照は、s->oname
(おそらく元の名前を指すフィールド)を見るようになりますが、s->oname
がnil
であるため、結果として未定義の名前として扱われ、インポーターからは見えなくなります。
この「不器用なハック」は、レキサーが名前の隠蔽をより適切に処理するようになったため、不要になりました。レキサーがシンボルを字句解析する段階で、そのシンボルの可視性やエクスポート状態を正確に判断し、不要なシンボルを適切にフィルタリングまたはマークするようになったため、export.c
で後から手動でs->lexical
を操作する必要がなくなったのです。
これは、コンパイラの設計がよりモジュール化され、各フェーズがそれぞれの責任範囲をより明確に持つようになったことを示唆しています。字句解析器が名前の可視性を初期段階で処理することで、後続のフェーズ(エクスポート処理など)は、よりクリーンな入力データを受け取ることができ、複雑なワークアラウンドが不要になります。
コアとなるコードの変更箇所
--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -410,14 +410,6 @@ importtype(Node *ss, Type *t)
s->otype->sym = s;
checkwidth(s->otype);
- // If type name should not be visible to importers,
- // hide it by setting the lexical type to name.
- // This will make references in the ordinary program
- // (but not the import sections) look at s->oname,
- // which is nil, as for an undefined name.
- if(s->export == 2 && !mypackage(ss))
- s->lexical = LNAME;
-
if(debug['e'])
print("import type %S %lT\n", s, t);
}
コアとなるコードの解説
変更はsrc/cmd/gc/export.c
ファイル内のimporttype
関数にあります。この関数は、Goコンパイラが型をインポートする際に呼び出されるものと推測されます。
削除されたコードブロックは、以下の目的を持っていました。
-
コメント:
// If type name should not be visible to importers, // hide it by setting the lexical type to name. // This will make references in the ordinary program // (but not the import sections) look at s->oname, // which is nil, as for an undefined name.
このコメントは、このコードブロックの意図を明確に説明しています。すなわち、「型名がインポーターに可視であるべきでない場合、字句的な型を
name
(LNAME
)に設定することで隠蔽する」というものです。これにより、通常のプログラムコードからの参照は、未定義の名前として扱われるs->oname
を見るようになり、結果としてその型名が隠蔽されるというメカニズムでした。 -
条件分岐:
if(s->export == 2 && !mypackage(ss))
この条件は、この隠蔽ロジックが適用されるべき特定のシナリオを定義していました。
s->export == 2
: シンボルs
が、エクスポートに関する特定の状態(おそらく内部的な、またはエクスポートすべきでない状態)にあることを示します。!mypackage(ss)
: 現在処理しているシンボルが、現在のパッケージ(ss
)に属していないことを意味します。これは、外部パッケージからインポートされるシンボル、または外部パッケージにエクスポートされるシンボルを扱っている状況でこのロジックが発動することを示唆しています。
-
隠蔽処理:
s->lexical = LNAME;
上記の条件が満たされた場合、シンボル
s
の内部的な字句型をLNAME
に設定していました。LNAME
は、Goコンパイラの内部で未定義の名前や特定の字句的な種類を示すために使われる定数であり、これを設定することで、そのシンボルが外部から参照された際に未定義として扱われるようにしていました。
このコミットは、この手動での隠蔽処理が、レキサーの改善により不要になったため、コードベースから削除されたことを示しています。これにより、コンパイラのコードがよりシンプルになり、可読性と保守性が向上しました。
関連リンク
- Go言語の初期のコミット履歴: https://github.com/golang/go/commits?author=rsc&after=2009-02-01&before=2009-03-01
- Go言語のパッケージとエクスポートに関する公式ドキュメント(現代版ですが、概念は共通): https://go.dev/doc/effective_go#names
参考にした情報源リンク
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語の公式ドキュメント: https://go.dev/doc/
- Goコンパイラの内部構造に関する一般的な知識(Goコンパイラのソースコードを読み解くための背景知識)
- コミットメッセージと差分から読み取れる情報
- Go言語の初期の設計に関する議論(一般的な知識として)
[インデックス 1659] ファイルの概要
このコミットは、Goコンパイラのsrc/cmd/gc/export.c
ファイルにおけるエクスポート処理のバグ修正です。具体的には、レキサー(字句解析器)が既に名前の隠蔽(hiding names)を適切に行うようになったため、以前に存在した「不器用なハック(clumsy hack)」が不要になったことを示しています。これにより、コードの複雑性が軽減され、よりクリーンな実装が実現されました。
コミット
commit 73dd4a37f9a6c6486ca80bf3f84cba7300cab5eb
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 10 13:57:31 2009 -0800
fix export bug Rob tripped over.
the lexer is already hiding names,
so this clumsy hack is no longer necessary.
R=ken
OCL=24783
CL=24783
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/73dd4a37f9a6c6486ca80bf3f84cba7300cab5eb
元コミット内容
fix export bug Rob tripped over.
the lexer is already hiding names,
so this clumsy hack is no longer necessary.
R=ken
OCL=24783
CL=24783
変更の背景
このコミットは、Goコンパイラの初期段階におけるエクスポートメカニズムの進化を示しています。コミットメッセージにある「Robが遭遇したエクスポートバグを修正」という記述は、特定のユーザー(Rob Pike氏である可能性が高い)が、パッケージのエクスポートに関する問題に直面したことを示唆しています。
問題の根源は、Go言語のパッケージシステムにおいて、外部に公開すべきでない内部的な型名やシンボルが、誤ってインポートされる可能性があったことにあります。これを防ぐために、以前はexport.c
内で「不器用なハック」が導入されていました。このハックは、特定の条件(s->export == 2
かつ!mypackage(ss)
)を満たす型名に対して、その字句的な型(s->lexical
)をLNAME
に設定することで、インポーターから見えないようにするものでした。LNAME
は、未定義の名前と同様に扱われる内部的なシンボルタイプを指します。
しかし、このコミットの時点(2009年2月)までに、Goコンパイラのレキサー(字句解析器)の機能が改善され、名前の可視性(visibility)と隠蔽(hiding)をより適切に処理できるようになりました。これにより、export.c
内の手動での名前隠蔽ロジックが冗長となり、削除しても問題ない状態になったため、このコミットで該当コードが削除されました。これは、コンパイラの設計が成熟し、より洗練された方法でシンボル解決と可視性が扱われるようになったことを意味します。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。
-
Go言語のパッケージとエクスポート:
- Go言語では、コードはパッケージ(
package
)に分割されます。 - パッケージ内の識別子(変数、関数、型など)は、その名前の最初の文字が大文字である場合、そのパッケージの外部にエクスポート(公開)されます。小文字で始まる識別子はパッケージ内部でのみ使用可能です。
- コンパイラは、他のパッケージがインポートする際に、どの識別子が見えるべきかを決定します。
- Go言語では、コードはパッケージ(
-
コンパイラのフェーズ:
- 字句解析(Lexical Analysis / Lexing): ソースコードをトークン(単語のようなもの)のストリームに変換するフェーズ。このフェーズで、識別子の名前や種類が認識されます。コミットメッセージにある「lexer」がこれにあたります。
- 構文解析(Syntactic Analysis / Parsing): トークンのストリームを解析し、プログラムの構造(抽象構文木 - AST)を構築するフェーズ。
- 意味解析(Semantic Analysis): ASTを走査し、型チェック、シンボル解決(識別子が何を指すか)、可視性チェックなどを行うフェーズ。エクスポートのルールもこのフェーズで適用されます。
-
シンボルテーブルとシンボル:
- コンパイラは、プログラム内で宣言されたすべての識別子(変数、関数、型など)に関する情報をシンボルテーブルに格納します。
- 各シンボルは、その名前、型、スコープ、可視性などの属性を持ちます。
Node *s
やType *t
は、Goコンパイラの内部表現におけるシンボルや型を表す構造体へのポインタであると推測されます。
-
export.c
の役割:- Goコンパイラの
src/cmd/gc/export.c
ファイルは、コンパイラがパッケージの外部に公開するシンボル(エクスポートされたシンボル)を処理し、他のパッケージがインポートできるようにするためのメタデータ(エクスポートデータ)を生成する役割を担っています。 importtype
関数は、他のパッケージから型をインポートする際の処理に関連していると考えられます。
- Goコンパイラの
-
s->lexical
とLNAME
:s->lexical
は、シンボルs
の字句的な種類や状態を示す内部的なフィールドであると推測されます。LNAME
は、Goコンパイラの内部で定義された定数で、おそらく「名前(識別子)」を表す字句的な種類、または未解決/未定義の名前の状態を示すものと考えられます。このコンテキストでは、シンボルを「未定義」として扱うことで、インポーターから見えなくする目的で使用されていました。
技術的詳細
このコミットの技術的詳細の中心は、src/cmd/gc/export.c
ファイル内のimporttype
関数から特定のコードブロックが削除されたことです。
削除されたコードブロックは以下の通りです。
// If type name should not be visible to importers,
// hide it by setting the lexical type to name.
// This will make references in the ordinary program
// (but not the import sections) look at s->oname,
// which is nil, as for an undefined name.
if(s->export == 2 && !mypackage(ss))
s->lexical = LNAME;
このコードは、型名がインポーターに対して可視であるべきでない場合に、その型名を隠蔽するためのロジックでした。
s->export == 2
: これは、シンボルs
のエクスポート状態を示すフラグであると推測されます。Web検索の結果によると、s->export
はGoコンパイラの内部でシンボルのエクスポート状態やプロパティを示すフィールドであり、2
という値は特定の状態(例えば、完全にエクスポートされ、他のGoパッケージから見えるシンボル、あるいはCgoを介してCコードにエクスポートされるシンボルなど)を示していた可能性があります。正確な意味は、当時のGoコンパイラのソースコードに依存しますが、何らかの形でエクスポートに関連する特殊な状態を示していたと考えられます。!mypackage(ss)
: これは、現在処理している型が、現在のパッケージ(ss
で示される)に属していないことをチェックしています。つまり、外部パッケージからインポートされる型、または外部パッケージにエクスポートされる型を処理している状況です。s->lexical = LNAME;
: 上記の条件が真の場合、シンボルs
の字句的な型をLNAME
に設定していました。これにより、通常のプログラム(インポートセクションを除く)での参照は、s->oname
(おそらく元の名前を指すフィールド)を見るようになりますが、s->oname
がnil
であるため、結果として未定義の名前として扱われ、インポーターからは見えなくなります。
この「不器用なハック」は、レキサーが名前の隠蔽をより適切に処理するようになったため、不要になりました。レキサーがシンボルを字句解析する段階で、そのシンボルの可視性やエクスポート状態を正確に判断し、不要なシンボルを適切にフィルタリングまたはマークするようになったため、export.c
で後から手動でs->lexical
を操作する必要がなくなったのです。
これは、コンパイラの設計がよりモジュール化され、各フェーズがそれぞれの責任範囲をより明確に持つようになったことを示唆しています。字句解析器が名前の可視性を初期段階で処理することで、後続のフェーズ(エクスポート処理など)は、よりクリーンな入力データを受け取ることができ、複雑なワークアラウンドが不要になります。
コアとなるコードの変更箇所
--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -410,14 +410,6 @@ importtype(Node *ss, Type *t)
s->otype->sym = s;
checkwidth(s->otype);
- // If type name should not be visible to importers,
- // hide it by setting the lexical type to name.
- // This will make references in the ordinary program
- // (but not the import sections) look at s->oname,
- // which is nil, as for an undefined name.
- if(s->export == 2 && !mypackage(ss))
- s->lexical = LNAME;
-
if(debug['e'])
print("import type %S %lT\\n", s, t);
}
コアとなるコードの解説
変更はsrc/cmd/gc/export.c
ファイル内のimporttype
関数にあります。この関数は、Goコンパイラが型をインポートする際に呼び出されるものと推測されます。
削除されたコードブロックは、以下の目的を持っていました。
-
コメント:
// If type name should not be visible to importers, // hide it by setting the lexical type to name. // This will make references in the ordinary program // (but not the import sections) look at s->oname, // which is nil, as for an undefined name.
このコメントは、このコードブロックの意図を明確に説明しています。すなわち、「型名がインポーターに可視であるべきでない場合、字句的な型を
name
(LNAME
)に設定することで隠蔽する」というものです。これにより、通常のプログラムコードからの参照は、未定義の名前として扱われるs->oname
を見るようになり、結果としてその型名が隠蔽されるというメカニズムでした。 -
条件分岐:
if(s->export == 2 && !mypackage(ss))
この条件は、この隠蔽ロジックが適用されるべき特定のシナリオを定義していました。
s->export == 2
: シンボルs
が、エクスポートに関する特定の状態(おそらく内部的な、またはエクスポートすべきでない状態)にあることを示します。Web検索の結果から、これはGoコンパイラの内部的なエクスポート状態を示すフラグであり、特定の可視性やエクスポートの特性を持つシンボルを識別するために使用されていたと考えられます。!mypackage(ss)
: 現在処理しているシンボルが、現在のパッケージ(ss
)に属していないことを意味します。これは、外部パッケージからインポートされるシンボル、または外部パッケージにエクスポートされるシンボルを扱っている状況でこのロジックが発動することを示唆しています。
-
隠蔽処理:
s->lexical = LNAME;
上記の条件が満たされた場合、シンボル
s
の内部的な字句型をLNAME
に設定していました。LNAME
は、Goコンパイラの内部で未定義の名前や特定の字句的な種類を示すために使われる定数であり、これを設定することで、そのシンボルが外部から参照された際に未定義として扱われるようにしていました。
このコミットは、この手動での隠蔽処理が、レキサーの改善により不要になったため、コードベースから削除されたことを示しています。これにより、コンパイラのコードがよりシンプルになり、可読性と保守性が向上しました。
関連リンク
- Go言語の初期のコミット履歴: https://github.com/golang/go/commits?author=rsc&after=2009-02-01&before=2009-03-01
- Go言語のパッケージとエクスポートに関する公式ドキュメント(現代版ですが、概念は共通): https://go.dev/doc/effective_go#names
参考にした情報源リンク
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語の公式ドキュメント: https://go.dev/doc/
- Goコンパイラの内部構造に関する一般的な知識(Goコンパイラのソースコードを読み解くための背景知識)
- コミットメッセージと差分から読み取れる情報
- Go言語の初期の設計に関する議論(一般的な知識として)
- Web検索結果: "Go compiler export.c LNAME s->export == 2"