[インデックス 1516] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)における構造体フィールドのドットアクセスに関する名前解決の挙動を修正するものです。具体的には、構造体のフィールドアクセスにおいて、他のパッケージの名前が誤って解釈されることを防ぎます。これにより、Go言語のパッケージとエクスポートのルールがより厳密に適用され、コンパイラの堅牢性が向上します。
コミット
commit 06869eedf96d5ee695d83f645497ed18fcb661d2
Author: Russ Cox <rsc@golang.org>
Date: Fri Jan 16 15:25:52 2009 -0800
disallow other package's names in struct field dot.
R=ken
OCL=22996
CL=22996
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/06869eedf96d5ee695d83f645497ed18fcb661d2
元コミット内容
disallow other package's names in struct field dot.
R=ken
OCL=22996
CL=22996
変更の背景
Go言語では、構造体のフィールドにアクセスする際にドット記法(例: myStruct.FieldName
)を使用します。また、Goのパッケージシステムでは、他のパッケージからアクセス可能な識別子(関数、変数、型、フィールドなど)は、その名前が大文字で始まる必要があります(エクスポートされた識別子)。小文字で始まる識別子は、そのパッケージ内でのみアクセス可能です(非エクスポート識別子)。
このコミットが導入される以前のGoコンパイラ(gc
)では、構造体のフィールドアクセスにおいて、誤って他のパッケージの名前がフィールド名の一部として解釈されてしまう可能性がありました。例えば、myStruct.somePackage.someField
のような記述があった場合、コンパイラが somePackage
をフィールド名の一部として扱ってしまうような、意図しない名前解決が行われるケースがあったと考えられます。これは、Goの設計思想である「明示的なパッケージインポートとエクスポートルール」に反する挙動であり、コンパイラの正確性を保つ上で修正が必要でした。
この変更の目的は、このような曖昧な、あるいは誤った名前解決を防ぎ、Go言語の仕様に厳密に従ったコンパイルを保証することにあります。特に、構造体フィールドのドットアクセスにおいて、非エクスポート識別子やパッケージ名が不適切に解釈されることを防ぎ、コンパイラが常に正しいシンボルを解決できるようにすることが狙いです。
前提知識の解説
このコミットの理解には、以下のGo言語およびコンパイラに関する知識が役立ちます。
-
Go言語のパッケージとエクスポートルール:
- Goのコードは「パッケージ」という単位で整理されます。
- 他のパッケージから利用可能にする識別子(変数、関数、型、構造体フィールドなど)は、その名前を大文字で始める必要があります。これを「エクスポートされた識別子」と呼びます。
- 小文字で始まる識別子は、そのパッケージ内でのみ利用可能です。これを「非エクスポート識別子」と呼びます。
- 他のパッケージの識別子を利用するには、
import
ステートメントでそのパッケージをインポートし、パッケージ名.識別子
の形式でアクセスします(例:fmt.Println
)。
-
構造体とフィールドアクセス:
- Goの構造体は、異なる型のフィールドをまとめた複合データ型です。
- 構造体のフィールドには、ドット記法(例:
myStructInstance.FieldName
)でアクセスします。
-
Goコンパイラ (
gc
) の内部構造:- このコミットは
src/cmd/gc
ディレクトリ内のファイルに対する変更です。gc
はGo言語の公式コンパイラです。 dcl.c
: コンパイラの「宣言(declaration)」フェーズを担当するC言語のソースファイルです。シンボル(変数、関数、型など)の宣言を処理し、シンボルテーブルに登録する役割を担います。名前解決(識別子がどの宣言に対応するかを決定するプロセス)もこのフェーズの重要な一部です。go.h
:gc
コンパイラの共通ヘッダファイルです。データ構造の定義や関数のプロトタイプ宣言が含まれます。go.y
: Yacc/Bison形式で記述されたGo言語の文法定義ファイルです。Go言語の構文規則を定義し、パーサー(構文解析器)を生成するために使用されます。import
ステートメントのような言語構造の解析ルールが記述されています。
- このコミットは
-
シンボルテーブルと名前解決:
- コンパイラは、プログラム内のすべての識別子(変数名、関数名など)とその属性(型、スコープなど)を管理するために「シンボルテーブル」を使用します。
- 「名前解決」とは、ソースコード中の識別子が、シンボルテーブル内のどの宣言に対応するかを決定するプロセスです。このプロセスは、識別子のスコープ、可視性(エクスポートされているか否か)、およびパッケージのコンテキストに基づいて行われます。
技術的詳細
このコミットの技術的な核心は、Goコンパイラの名前解決ロジック、特に構造体フィールドのドットアクセスにおけるパッケージ名の扱いを厳密化することにあります。
変更は主に以下の3つのファイルにわたります。
-
src/cmd/gc/dcl.c
:- このファイルは、Goの宣言処理と名前解決の中心的な部分を担っています。
- 追加されたコードは、構造体フィールドのシンボル解決時に実行されます。
if(pkgimportname != S && !exportname(f->sym->name))
という条件が重要です。pkgimportname != S
: これは、現在コンパイラがインポートされたパッケージのコンテキスト内で処理を行っていることを示唆しています。S
はおそらく、シンボルが設定されていない状態(nullまたは空)を表す定数です。!exportname(f->sym->name)
:exportname
は新しく追加された関数で、与えられたシンボル名がGoのエクスポートルールに従ってエクスポートされているか(つまり、大文字で始まっているか)をチェックします。!
は「ではない」を意味するため、この条件は「シンボル名がエクスポートされていない」ことを意味します。
- この条件が真(つまり、インポートされたパッケージのコンテキストにいて、かつフィールドのシンボル名がエクスポートされていない)の場合、
f->sym = pkglookup(f->sym->name, pkgimportname->name);
が実行されます。pkglookup
は、指定されたパッケージ(pkgimportname->name
)内で、指定されたシンボル名(f->sym->name
)を検索する関数です。- この行は、もし非エクスポートのシンボル名がインポートされたパッケージのコンテキストで現れた場合、それを現在のパッケージのシンボルとしてではなく、インポートされたパッケージ内のシンボルとして再解決しようとすることを示唆しています。しかし、コミットメッセージの「disallow other package's names in struct field dot」と合わせると、これはむしろ、非エクスポート名が他のパッケージのコンテキストで誤って解釈されるのを防ぐためのガードとして機能していると考えられます。つまり、もし非エクスポート名が他のパッケージのコンテキストで現れたら、それは不正な参照である可能性が高く、
pkglookup
が失敗するか、あるいは意図しない解決を避けるためのロジックの一部として機能します。
-
src/cmd/gc/go.h
:int exportname(char*);
という新しい関数のプロトタイプ宣言が追加されています。この関数は、Goの識別子が大文字で始まるかどうかをチェックし、エクスポートされているか否かを判断するために使用されます。
-
src/cmd/gc/go.y
:import_there
ルール(import
ステートメントの処理に関連する文法規則)のブロック内にpkgimportname = S;
が追加されています。- これは、
import
ステートメントの処理が完了した後、pkgimportname
変数をリセットすることを意味します。これにより、pkgimportname
の値が、import
ブロックの外部で誤って使用されたり、以前のインポートコンテキストが次の名前解決に影響を与えたりするのを防ぎます。これは、コンパイラの状態管理をクリーンに保ち、名前解決の正確性を保証するために非常に重要です。
これらの変更は全体として、Goコンパイラが構造体フィールドのドットアクセスにおいて、パッケージ名とフィールド名の区別をより厳密に行い、Go言語のエクスポートルールに違反するような曖昧な名前解決を排除することを目的としています。
コアとなるコードの変更箇所
src/cmd/gc/dcl.c
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -512,6 +512,8 @@ loop:
f->nname = n->left;
f->embedded = n->embedded;
f->sym = f->nname->sym;
+ if(pkgimportname != S && !exportname(f->sym->name))
+ f->sym = pkglookup(f->sym->name, pkgimportname->name);
}
*t = f;
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -741,6 +741,7 @@ void constiter(Node*, Type*, Node*);
*/
void renamepkg(Node*);
void autoexport(Sym*);
+int exportname(char*);
void exportsym(Sym*);
void packagesym(Sym*);
void dumpe(Sym*);
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -160,6 +160,7 @@ import_there:
{
checkimports();
unimportfile();
+ pkgimportname = S;
}
|\tLIMPORT '$$' '$$' hidden_import_list '$$' '$$'
{
コアとなるコードの解説
src/cmd/gc/dcl.c
の変更
この変更は、構造体フィールドのシンボル(f->sym
)を解決するロジックの一部です。
f->sym = f->nname->sym;
の行は、フィールドの名前ノード(f->nname
)からシンボルを取得しています。
その直後に追加された if
文がこのコミットの主要なロジックです。
if(pkgimportname != S && !exportname(f->sym->name))
f->sym = pkglookup(f->sym->name, pkgimportname->name);
pkgimportname != S
: これは、現在コンパイラがインポートされたパッケージのコンテキストで名前解決を行っていることを示します。S
はシンボルが設定されていない状態を表す定数です。!exportname(f->sym->name)
:exportname
関数は、与えられたシンボル名がGoのエクスポートルールに従って大文字で始まっているか(エクスポートされているか)をチェックします。!
は否定なので、この条件は「シンボル名がエクスポートされていない」ことを意味します。
この if
文の全体的な意味は、「もし現在インポートされたパッケージのコンテキストにいて、かつ参照しようとしているフィールドのシンボル名がエクスポートされていない(つまり小文字で始まる)場合」というものです。
このような状況は、通常、Goの言語仕様では許されません。他のパッケージから非エクスポートの識別子に直接アクセスすることはできないからです。
この条件が真の場合に実行される f->sym = pkglookup(f->sym->name, pkgimportname->name);
は、pkgimportname
で示されるパッケージ内で、その非エクスポートシンボル名を再検索しようとします。
このロジックの意図は、コミットメッセージ「disallow other package's names in struct field dot」と合わせて考えると、以下のようになります。
以前のコンパイラでは、myStruct.somePackage.someField
のような不正な記述があった場合、somePackage
が構造体フィールドとして誤って解釈され、その後の someField
の解決が混乱する可能性がありました。この if
文は、そのような誤解釈を防ぐためのガードです。もし somePackage
が非エクスポート名であり、かつインポートコンテキストで現れた場合、それは不正な参照であると判断し、pkglookup
を通じて正しいエラー処理や名前解決の失敗を促すことで、コンパイラが不正なコードを正しく拒否できるようにします。
src/cmd/gc/go.h
の変更
int exportname(char*);
これは、exportname
という新しい関数のプロトタイプ宣言です。この関数は char*
型の引数(シンボル名)を取り、int
型の値を返します。Goの識別子のエクスポートルール(大文字で始まるか否か)をチェックするために使用されます。この関数の追加により、コンパイラは識別子の可視性をプログラム的に判断できるようになります。
src/cmd/gc/go.y
の変更
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -160,6 +160,7 @@ import_there:
{
checkimports();
unimportfile();
+ pkgimportname = S;
}
import_there
ルールは、Goの import
ステートメントが処理される際の文法規則の一部です。このルールが完了するブロック({ ... }
内)に pkgimportname = S;
が追加されました。
これは、import
ステートメントの処理が終了した直後に、pkgimportname
という変数を S
(シンボルが設定されていない状態)にリセットすることを意味します。
このリセットは、コンパイラの状態管理において非常に重要です。pkgimportname
は、おそらく現在処理中のインポートされたパッケージの名前を保持している一時的な変数です。この変数をリセットすることで、以前の import
ステートメントのコンテキストが、その後のコードの名前解決に誤って影響を与えることを防ぎます。これにより、名前解決のスコープが適切に管理され、コンパイラの正確性と予測可能性が向上します。
関連リンク
- Go言語の仕様: https://go.dev/ref/spec
- Goコンパイラのソースコード: https://github.com/golang/go/tree/master/src/cmd/gc
参考にした情報源リンク
- Go言語のパッケージとエクスポートルールに関する公式ドキュメントやチュートリアル。
- コンパイラの設計と実装に関する一般的な知識。
- Yacc/Bisonの文法定義に関する情報。
- Go言語の初期のコミット履歴と設計に関する議論(もし公開されているものがあれば)。
- Go言語のソースコード内のコメントや関連する関数定義。