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

[インデックス 1951] ファイルの概要

このコミットは、Goコンパイラ(gc)の内部的な関数リテラル(クロージャ)の命名規則と、ソースファイル名の処理方法に関する重要な改善を導入しています。具体的には、src/cmd/gc/dcl.csrc/cmd/gc/lex.cの2つのファイルが変更されています。

src/cmd/gc/dcl.cはGoコンパイラの宣言処理を担当する部分であり、関数リテラルの内部的な名前生成ロジックが含まれています。 src/cmd/gc/lex.cはGoコンパイラの字句解析(レキシカルアナリシス)を担当する部分であり、ファイル名の処理や正規化に関するロジックが含まれています。

コミット

commit 416b27548ed2c6ac89c28c192880900cbc2ffa6d
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 2 18:32:57 2009 -0700

    use _f007·filename for func literals.
    this avoids problems people have run into with
    multiple closures in the same package.
    
    when preparing filename, only cut off .go, not .anything.
    this fixes a bug tgs ran into with foo.pb.go and foo.go
    in the same package.
    
    also turn bad identifier chars from filename into
    underscores: a-b.pb.go => a_b_pb
    
    R=ken
    OCL=27050
    CL=27050

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/416b27548ed2c6ac89c28c192880900cbc2ffa6d

元コミット内容

関数リテラルに _f007·filename の形式を使用するように変更。これにより、同じパッケージ内の複数のクロージャで発生していた問題を回避する。

ファイル名を準備する際、.go のみを取り除き、.anything は取り除かないように変更。これにより、foo.pb.gofoo.go が同じパッケージにある場合に tgs が遭遇したバグを修正する。

また、ファイル名に含まれる不正な識別子文字をアンダースコアに変換する。例: a-b.pb.goa_b_pb になる。

変更の背景

このコミットは、Goコンパイラの初期段階における2つの具体的な問題に対処するために行われました。

  1. 同じパッケージ内の複数クロージャにおける名前衝突の問題: Goの関数リテラル(クロージャ)は、コンパイル時に内部的に一意な名前が割り当てられます。初期の実装では、この内部名が十分にユニークでなかったため、同じパッケージ内に複数のクロージャが存在する場合に、コンパイラが生成する内部名が衝突し、予期せぬコンパイルエラーや動作不良を引き起こす可能性がありました。これは、特に大規模なコードベースや、コード生成ツール(例: Protocol Buffers)を使用する際に顕著になる問題でした。

  2. 特殊なファイル名(例: foo.pb.go)の不適切な処理: Goのビルドシステムでは、同じパッケージに属するすべての.goファイルがまとめてコンパイルされます。しかし、foo.pb.goのような、.go以外の拡張子(この場合は.pb)を含むファイル名が、コンパイラの内部的なファイル名処理ロジックによって誤って短縮される問題がありました。具体的には、setfilename関数がファイル名から最初の.以降を無条件に削除していたため、foo.pb.gofooとして認識され、同じディレクトリにあるfoo.goと名前が衝突してしまう可能性がありました。これは、Protocol Buffersのようなコード生成ツールが生成するファイルと手書きのコードが共存するシナリオで問題となります。

これらの問題は、Go言語がまだ初期開発段階にあった2009年頃に、実際のユーザー(tgsなど)が遭遇した具体的なバグとして報告され、その解決が求められていました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

  • Goコンパイラ (gc): Go言語の公式コンパイラは、伝統的にgc(Go Compiler)と呼ばれています。これは、C言語で書かれた初期のGoコンパイラであり、Goプログラムを機械語に変換する役割を担っています。このコミットが対象としているのは、このgcコンパイラの内部実装です。

  • 関数リテラルとクロージャ: Go言語では、関数をその場で定義し、変数に代入したり、他の関数の引数として渡したりすることができます。これを「関数リテラル」と呼びます。関数リテラルが、それが定義されたスコープの外部変数(自由変数)を参照する場合、それは「クロージャ」となります。クロージャは、外部変数を「キャプチャ」し、その変数の状態を保持することができます。コンパイラは、これらの匿名関数を内部的に識別するために、一意な名前を生成する必要があります。

  • Goパッケージとファイル名: Goでは、同じディレクトリ内のすべての.goファイルが同じパッケージに属すると見なされます(ただし、_test.goファイルはテスト専用として扱われます)。これにより、同じパッケージ内のファイルは、互いに定義された型、関数、変数に直接アクセスできます。コンパイラは、これらのファイルをまとめて処理する際に、ファイル名を内部的に参照することがあります。

  • 識別子(Identifier): プログラミング言語において、変数名、関数名、型名などを識別するために使用される文字列のことです。Go言語の識別子には、特定の命名規則と使用可能な文字の制約があります。通常、英数字とアンダースコア(_)が使用でき、数字で始まることはできません。

  • Protocol Buffers (Protobuf): Googleが開発した、構造化されたデータをシリアライズするための言語ニュートラル、プラットフォームニュートラルな拡張可能なメカニズムです。.protoファイルでデータ構造を定義し、protocコンパイラを使って様々な言語(Goを含む)のソースコードを生成します。Goの場合、protocは通常、foo.protoからfoo.pb.goのようなファイルを生成します。

技術的詳細

このコミットは、Goコンパイラの内部的な命名戦略とファイル名処理ロジックを改善することで、前述の問題を解決しています。

  1. 関数リテラルの内部命名の改善 (src/cmd/gc/dcl.c):

    • 変更前: _f%.3ld
      • これは、関数リテラルに_fというプレフィックスと、vargenというカウンターの値をゼロ埋めした3桁の数字を組み合わせた名前を割り当てていました。例えば、_f001, _f002のようになります。
      • この命名規則では、同じパッケージ内で多くのクロージャが定義された場合、vargenカウンターがリセットされたり、異なるファイルで同じカウンター値が使われたりすると、名前が衝突する可能性がありました。
    • 変更後: _f%.3ld·%s
      • 新しい命名規則では、既存のカウンター値に加えて、·(ミドルドット、U+00B7)という特殊文字と、その関数リテラルが定義されているファイル名filename変数)を組み合わせるようになりました。
      • これにより、生成される内部名は _f001·main.go のように、ファイル名を含むことで、同じパッケージ内であっても異なるファイルに存在するクロージャの名前衝突を効果的に回避できるようになります。·はGoの識別子としては無効な文字であるため、通常のGoコードでこの形式の名前が誤って参照されることを防ぐ役割も果たします。
  2. ファイル名処理の堅牢化 (src/cmd/gc/lex.c):

    • setfilename関数は、ソースファイル名をコンパイラ内部で使用する形式に正規化する役割を担っています。
    • 拡張子処理の変更:
      • 変更前: p = strchr(namebuf, '.'); if(p != nil) *p = 0;
        • これは、ファイル名に含まれる最初の.を見つけて、その位置で文字列を終端させる(つまり、.以降をすべて削除する)ロジックでした。
        • このため、foo.pb.goのようなファイル名がfooに短縮され、foo.goと衝突する問題が発生していました。
      • 変更後: p = strrchr(namebuf, '.'); if(p != nil && strcmp(p, ".go") == 0) *p = 0;
        • 新しいロジックでは、ファイル名の最後の.を見つけ、さらにその.以降が厳密に.goである場合にのみ、その部分を削除するように変更されました。
        • これにより、foo.pb.go.pb.goの部分が削除されず、foo.pbとして適切に扱われるようになります(後述の識別子変換によりfoo_pbとなる)。foo.goは引き続きfooとなります。これにより、foo.pb.gofoo.goが同じパッケージに存在しても、コンパイラ内部で異なるファイル名として認識され、名前衝突が回避されます。
    • 不正な識別子文字の変換:
      • for(p=filename; *p; p++) { c = *p & 0xFF; if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_') *p = '_'; }
        • このループは、正規化されたファイル名(filename変数)を走査し、Goの識別子として不正な文字(英数字、アンダースコア以外のASCII文字)をすべてアンダースコア(_)に変換します。
        • 例えば、a-b.pb.goは、拡張子処理後にa-b.pbとなり、この処理によってa_b_pbに変換されます。これにより、内部的に生成される識別子が常に有効なGoの識別子規則に準拠するようになります。

これらの変更により、コンパイラはより堅牢になり、Go言語の柔軟なパッケージ構造とクロージャの使用をサポートできるようになりました。

コアとなるコードの変更箇所

diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 2e467249bb..51c76be752 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -583,7 +583,7 @@ funclit1(Type *type, Node *body)
 
 	// declare function.
 	vargen++;
-	snprint(namebuf, sizeof(namebuf), "_f%.3ld", vargen);
+	snprint(namebuf, sizeof(namebuf), "_f%.3ld·%s", vargen, filename);
 	f = newname(lookup(namebuf));
 	addvar(f, ft, PFUNC);
 	f->funcdepth = 0;
diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 3477a2cffc..4e577a63c8 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -138,15 +138,23 @@ void
 setfilename(char *file)
 {
 	char *p;
+	int c;
 
 	p = strrchr(file, '/');
 	if(p != nil)
 		file = p+1;
 	strncpy(namebuf, file, sizeof(namebuf));
-	p = strchr(namebuf, '.');
-	if(p != nil)
+	p = strrchr(namebuf, '.');
+	if(p != nil && strcmp(p, ".go") == 0)
 		*p = 0;
 	filename = strdup(namebuf);
+	
+	// turn invalid identifier chars into _
+	for(p=filename; *p; p++) {
+		c = *p & 0xFF;
+		if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_')
+			*p = '_';
+	}
 }
 
 int

コアとなるコードの解説

src/cmd/gc/dcl.c の変更

-	snprint(namebuf, sizeof(namebuf), "_f%.3ld", vargen);
+	snprint(namebuf, sizeof(namebuf), "_f%.3ld·%s", vargen, filename);

この変更は、関数リテラルの内部名を生成する部分です。

  • - で始まる行は削除された元のコードです。_fというプレフィックスに、vargen(変数生成カウンター)の値を3桁のゼロ埋め整数として結合していました。
  • + で始まる行は追加された新しいコードです。_fプレフィックスとvargenの値に加えて、·(ミドルドット)と、現在のソースファイル名を表すfilename変数の内容が追加されています。
    • vargen: コンパイラが内部的に使用する、生成された変数やエンティティの一意性を保証するためのカウンターです。
    • filename: lex.csetfilename関数によって正規化された、現在のソースファイル名です。 この変更により、生成される内部名は _fXXX·<filename> の形式となり、ファイル名が内部名の一部に含まれることで、異なるファイルに存在するクロージャ間の名前衝突が回避されます。

src/cmd/gc/lex.c の変更

 	char *p;
+	int c; // 新しく追加された変数。文字コードを一時的に保持するために使用。
 
 	p = strrchr(file, '/');
 	if(p != nil)
 		file = p+1;
 	strncpy(namebuf, file, sizeof(namebuf));
-	p = strchr(namebuf, '.');
-	if(p != nil)
+	p = strrchr(namebuf, '.'); // ファイル名の最後の '.' を検索
+	if(p != nil && strcmp(p, ".go") == 0) // '.' が見つかり、かつその後の文字列が ".go" と完全に一致する場合のみ
 		*p = 0; // その '.' の位置で文字列を終端させる(".go" を削除する)
 	filename = strdup(namebuf);
+	
+	// turn invalid identifier chars into _
+	for(p=filename; *p; p++) { // filename文字列を先頭から走査
+		c = *p & 0xFF; // 現在の文字をASCII値として取得
+		if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_') // ASCII文字で、かつ英字、数字、アンダースコアのいずれでもない場合
+			*p = '_'; // その文字をアンダースコアに置換
+	}

この変更は、ソースファイル名をコンパイラ内部で使用するfilename変数に設定するロジックを改善しています。

  1. int c; の追加: 文字を処理するための一時変数cが導入されました。
  2. 拡張子処理の変更:
    • 元のコード (p = strchr(namebuf, '.'); if(p != nil) *p = 0;) は、ファイル名中の最初の.以降を無条件に削除していました。これにより、foo.pb.gofooになってしまう問題がありました。
    • 新しいコード (p = strrchr(namebuf, '.'); if(p != nil && strcmp(p, ".go") == 0) *p = 0;) では、strchr(最初の出現)からstrrchr(最後の出現)に変更され、さらにstrcmp(p, ".go") == 0という条件が追加されました。これは、「ファイル名の最後の.を見つけ、その.以降が厳密に.goである場合にのみ、その.go部分を削除する」ことを意味します。これにより、foo.pb.go.pb.goが削除されず、foo.pbとして扱われるようになります。
  3. 不正な識別子文字の変換ロジックの追加:
    • 新しく追加されたforループは、filename変数に格納された文字列を文字ごとに走査します。
    • c = *p & 0xFF; は、現在の文字のASCII値を取得します。
    • if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_') は、文字がASCII範囲内であり(c < 0x80)、かつ英字(isalpha(c))、数字(isdigit(c))、アンダースコア(c != '_')のいずれでもない場合に真となります。
    • 条件が真の場合、*p = '_'; によってその文字はアンダースコアに置換されます。 このロジックにより、ファイル名に含まれるハイフン(-)やその他の特殊文字がアンダースコアに変換され、filename変数が常に有効なGoの識別子の一部として使用できる形式に正規化されます。例えば、a-b.pb.goa_b_pbに変換されます。

これらの変更は、Goコンパイラの内部的な堅牢性を高め、特にコード生成ツールとの連携や、複雑なファイル名を持つソースコードの処理において、より予測可能で安定した動作を保証するために不可欠でした。

関連リンク

参考にした情報源リンク

  • Go func literals
  • Go closure naming conventions
  • Go compiler filename handling
  • Go foo.pb.go foo.go same package
  • Go gc compiler (一般的な情報収集のため)
  • Go言語の識別子に関する一般的な情報I have generated the detailed explanation in Markdown format, following all the specified instructions and chapter structure. I have used the commit information, the provided metadata, and the insights from the web searches to create a comprehensive technical explanation in Japanese.

I will now output the generated Markdown to standard output.

# [インデックス 1951] ファイルの概要

このコミットは、Goコンパイラ(`gc`)の内部的な関数リテラル(クロージャ)の命名規則と、ソースファイル名の処理方法に関する重要な改善を導入しています。具体的には、`src/cmd/gc/dcl.c`と`src/cmd/gc/lex.c`の2つのファイルが変更されています。

`src/cmd/gc/dcl.c`はGoコンパイラの宣言処理を担当する部分であり、関数リテラルの内部的な名前生成ロジックが含まれています。
`src/cmd/gc/lex.c`はGoコンパイラの字句解析(レキシカルアナリシス)を担当する部分であり、ファイル名の処理や正規化に関するロジックが含まれています。

## コミット

commit 416b27548ed2c6ac89c28c192880900cbc2ffa6d Author: Russ Cox rsc@golang.org Date: Thu Apr 2 18:32:57 2009 -0700

use _f007·filename for func literals.
this avoids problems people have run into with
multiple closures in the same package.

when preparing filename, only cut off .go, not .anything.
this fixes a bug tgs ran into with foo.pb.go and foo.go
in the same package.

also turn bad identifier chars from filename into
underscores: a-b.pb.go => a_b_pb

R=ken
OCL=27050
CL=27050

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/416b27548ed2c6ac89c28c192880900cbc2ffa6d](https://github.com/golang/go/commit/416b27548ed2c6ac89c28c192880900cbc2ffa6d)

## 元コミット内容

関数リテラルに `_f007·filename` の形式を使用するように変更。これにより、同じパッケージ内の複数のクロージャで発生していた問題を回避する。

ファイル名を準備する際、`.go` のみを取り除き、`.anything` は取り除かないように変更。これにより、`foo.pb.go` と `foo.go` が同じパッケージにある場合に tgs が遭遇したバグを修正する。

また、ファイル名に含まれる不正な識別子文字をアンダースコアに変換する。例: `a-b.pb.go` は `a_b_pb` になる。

## 変更の背景

このコミットは、Goコンパイラの初期段階における2つの具体的な問題に対処するために行われました。

1.  **同じパッケージ内の複数クロージャにおける名前衝突の問題**: Goの関数リテラル(クロージャ)は、コンパイル時に内部的に一意な名前が割り当てられます。初期の実装では、この内部名が十分にユニークでなかったため、同じパッケージ内に複数のクロージャが存在する場合に、コンパイラが生成する内部名が衝突し、予期せぬコンパイルエラーや動作不良を引き起こす可能性がありました。これは、特に大規模なコードベースや、コード生成ツール(例: Protocol Buffers)を使用する際に顕著になる問題でした。

2.  **特殊なファイル名(例: `foo.pb.go`)の不適切な処理**: Goのビルドシステムでは、同じパッケージに属するすべての`.go`ファイルがまとめてコンパイルされます。しかし、`foo.pb.go`のような、`.go`以外の拡張子(この場合は`.pb`)を含むファイル名が、コンパイラの内部的なファイル名処理ロジックによって誤って短縮される問題がありました。具体的には、`setfilename`関数がファイル名から最初の`.`以降を無条件に削除していたため、`foo.pb.go`が`foo`として認識され、同じディレクトリにある`foo.go`と名前が衝突してしまう可能性がありました。これは、Protocol Buffersのようなコード生成ツールが生成するファイルと手書きのコードが共存するシナリオで問題となります。

これらの問題は、Go言語がまだ初期開発段階にあった2009年頃に、実際のユーザー(tgsなど)が遭遇した具体的なバグとして報告され、その解決が求められていました。

## 前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

*   **Goコンパイラ (`gc`)**: Go言語の公式コンパイラは、伝統的に`gc`(Go Compiler)と呼ばれています。これは、C言語で書かれた初期のGoコンパイラであり、Goプログラムを機械語に変換する役割を担っています。このコミットが対象としているのは、この`gc`コンパイラの内部実装です。

*   **関数リテラルとクロージャ**: Go言語では、関数をその場で定義し、変数に代入したり、他の関数の引数として渡したりすることができます。これを「関数リテラル」と呼びます。関数リテラルが、それが定義されたスコープの外部変数(自由変数)を参照する場合、それは「クロージャ」となります。クロージャは、外部変数を「キャプチャ」し、その変数の状態を保持することができます。コンパイラは、これらの匿名関数を内部的に識別するために、一意な名前を生成する必要があります。

*   **Goパッケージとファイル名**: Goでは、同じディレクトリ内のすべての`.go`ファイルが同じパッケージに属すると見なされます(ただし、`_test.go`ファイルはテスト専用として扱われます)。これにより、同じパッケージ内のファイルは、互いに定義された型、関数、変数に直接アクセスできます。コンパイラは、これらのファイルをまとめて処理する際に、ファイル名を内部的に参照することがあります。

*   **識別子(Identifier)**: プログラミング言語において、変数名、関数名、型名などを識別するために使用される文字列のことです。Go言語の識別子には、特定の命名規則と使用可能な文字の制約があります。通常、英数字とアンダースコア(`_`)が使用でき、数字で始まることはできません。

*   **Protocol Buffers (Protobuf)**: Googleが開発した、構造化されたデータをシリアライズするための言語ニュートラル、プラットフォームニュートラルな拡張可能なメカニズムです。`.proto`ファイルでデータ構造を定義し、`protoc`コンパイラを使って様々な言語(Goを含む)のソースコードを生成します。Goの場合、`protoc`は通常、`foo.proto`から`foo.pb.go`のようなファイルを生成します。

## 技術的詳細

このコミットは、Goコンパイラの内部的な命名戦略とファイル名処理ロジックを改善することで、前述の問題を解決しています。

1.  **関数リテラルの内部命名の改善 (`src/cmd/gc/dcl.c`)**:
    *   変更前: `_f%.3ld`
        *   これは、関数リテラルに`_f`というプレフィックスと、`vargen`というカウンターの値をゼロ埋めした3桁の数字を組み合わせた名前を割り当てていました。例えば、`_f001`, `_f002`のようになります。
        *   この命名規則では、同じパッケージ内で多くのクロージャが定義された場合、`vargen`カウンターがリセットされたり、異なるファイルで同じカウンター値が使われたりすると、名前が衝突する可能性がありました。
    *   変更後: `_f%.3ld·%s`
        *   新しい命名規則では、既存のカウンター値に加えて、`·`(ミドルドット、U+00B7)という特殊文字と、その関数リテラルが定義されている**ファイル名**(`filename`変数)を組み合わせるようになりました。
        *   これにより、生成される内部名は `_f001·main.go` のように、ファイル名を含むことで、同じパッケージ内であっても異なるファイルに存在するクロージャの名前衝突を効果的に回避できるようになります。`·`はGoの識別子としては無効な文字であるため、通常のGoコードでこの形式の名前が誤って参照されることを防ぐ役割も果たします。

2.  **ファイル名処理の堅牢化 (`src/cmd/gc/lex.c`)**:
    *   `setfilename`関数は、ソースファイル名をコンパイラ内部で使用する形式に正規化する役割を担っています。
    *   **拡張子処理の変更**:
        *   変更前: `p = strchr(namebuf, '.'); if(p != nil) *p = 0;`
            *   これは、ファイル名に含まれる最初の`.`を見つけて、その位置で文字列を終端させる(つまり、`.`以降をすべて削除する)ロジックでした。
            *   このため、`foo.pb.go`のようなファイル名が`foo`に短縮され、`foo.go`と衝突する問題が発生していました。
        *   変更後: `p = strrchr(namebuf, '.'); if(p != nil && strcmp(p, ".go") == 0) *p = 0;`
            *   新しいロジックでは、ファイル名の**最後の**`.`を見つけ、さらにその`.`以降が厳密に`.go`である場合にのみ、その部分を削除するように変更されました。
            *   これにより、`foo.pb.go`は`.pb.go`の部分が削除されず、`foo.pb`として適切に扱われるようになります(後述の識別子変換により`foo_pb`となる)。`foo.go`は引き続き`foo`となります。これにより、`foo.pb.go`と`foo.go`が同じパッケージに存在しても、コンパイラ内部で異なるファイル名として認識され、名前衝突が回避されます。
    *   **不正な識別子文字の変換**:
        *   `for(p=filename; *p; p++) { c = *p & 0xFF; if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_') *p = '_'; }`
            *   このループは、正規化されたファイル名(`filename`変数)を走査し、Goの識別子として不正な文字(英数字、アンダースコア以外のASCII文字)をすべてアンダースコア(`_`)に変換します。
            *   例えば、`a-b.pb.go`は、拡張子処理後に`a-b.pb`となり、この処理によって`a_b_pb`に変換されます。これにより、内部的に生成される識別子が常に有効なGoの識別子規則に準拠するようになります。

これらの変更により、コンパイラはより堅牢になり、Go言語の柔軟なパッケージ構造とクロージャの使用をサポートできるようになりました。

## コアとなるコードの変更箇所

```diff
diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 2e467249bb..51c76be752 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -583,7 +583,7 @@ funclit1(Type *type, Node *body)
 
 	// declare function.
 	vargen++;
-	snprint(namebuf, sizeof(namebuf), "_f%.3ld", vargen);
+	snprint(namebuf, sizeof(namebuf), "_f%.3ld·%s", vargen, filename);
 	f = newname(lookup(namebuf));
 	addvar(f, ft, PFUNC);
 	f->funcdepth = 0;
diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 3477a2cffc..4e577a63c8 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -138,15 +138,23 @@ void
 setfilename(char *file)
 {
 	char *p;
+	int c;
 
 	p = strrchr(file, '/');
 	if(p != nil)
 		file = p+1;
 	strncpy(namebuf, file, sizeof(namebuf));
-	p = strchr(namebuf, '.');
-	if(p != nil)
+	p = strrchr(namebuf, '.');
+	if(p != nil && strcmp(p, ".go") == 0)
 		*p = 0;
 	filename = strdup(namebuf);
+	
+	// turn invalid identifier chars into _
+	for(p=filename; *p; p++) {
+		c = *p & 0xFF;
+		if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_')
+			*p = '_';
+	}
 }
 
 int

コアとなるコードの解説

src/cmd/gc/dcl.c の変更

-	snprint(namebuf, sizeof(namebuf), "_f%.3ld", vargen);
+	snprint(namebuf, sizeof(namebuf), "_f%.3ld·%s", vargen, filename);

この変更は、関数リテラルの内部名を生成する部分です。

  • - で始まる行は削除された元のコードです。_fというプレフィックスに、vargen(変数生成カウンター)の値を3桁のゼロ埋め整数として結合していました。
  • + で始まる行は追加された新しいコードです。_fプレフィックスとvargenの値に加えて、·(ミドルドット)と、現在のソースファイル名を表すfilename変数の内容が追加されています。
    • vargen: コンパイラが内部的に使用する、生成された変数やエンティティの一意性を保証するためのカウンターです。
    • filename: lex.csetfilename関数によって正規化された、現在のソースファイル名です。 この変更により、生成される内部名は _fXXX·<filename> の形式となり、ファイル名が内部名の一部に含まれることで、異なるファイルに存在するクロージャ間の名前衝突が回避されます。

src/cmd/gc/lex.c の変更

 	char *p;
+	int c; // 新しく追加された変数。文字コードを一時的に保持するために使用。
 
 	p = strrchr(file, '/');
 	if(p != nil)
 		file = p+1;
 	strncpy(namebuf, file, sizeof(namebuf));
-	p = strchr(namebuf, '.');
-	if(p != nil)
+	p = strrchr(namebuf, '.'); // ファイル名の最後の '.' を検索
+	if(p != nil && strcmp(p, ".go") == 0) // '.' が見つかり、かつその後の文字列が ".go" と完全に一致する場合のみ
 		*p = 0; // その '.' の位置で文字列を終端させる(".go" を削除する)
 	filename = strdup(namebuf);
+	
+	// turn invalid identifier chars into _
+	for(p=filename; *p; p++) { // filename文字列を先頭から走査
+		c = *p & 0xFF; // 現在の文字をASCII値として取得
+		if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_') // ASCII文字で、かつ英字、数字、アンダースコアのいずれでもない場合
+			*p = '_'; // その文字をアンダースコアに置換
+	}

この変更は、ソースファイル名をコンパイラ内部で使用するfilename変数に設定するロジックを改善しています。

  1. int c; の追加: 文字を処理するための一時変数cが導入されました。
  2. 拡張子処理の変更:
    • 元のコード (p = strchr(namebuf, '.'); if(p != nil) *p = 0;) は、ファイル名中の最初の.以降を無条件に削除していました。これにより、foo.pb.gofooになってしまう問題がありました。
    • 新しいコード (p = strrchr(namebuf, '.'); if(p != nil && strcmp(p, ".go") == 0) *p = 0;) では、strchr(最初の出現)からstrrchr(最後の出現)に変更され、さらにstrcmp(p, ".go") == 0という条件が追加されました。これは、「ファイル名の最後の.を見つけ、その.以降が厳密に.goである場合にのみ、その.go部分を削除する」ことを意味します。これにより、foo.pb.go.pb.goが削除されず、foo.pbとして扱われるようになります。
  3. 不正な識別子文字の変換ロジックの追加:
    • 新しく追加されたforループは、filename変数に格納された文字列を文字ごとに走査します。
    • c = *p & 0xFF; は、現在の文字のASCII値を取得します。
    • if(c < 0x80 && !isalpha(c) && !isdigit(c) && c != '_') は、文字がASCII範囲内であり(c < 0x80)、かつ英字(isalpha(c))、数字(isdigit(c))、アンダースコア(c != '_')のいずれでもない場合に真となります。
    • 条件が真の場合、*p = '_'; によってその文字はアンダースコアに置換されます。 このロジックにより、ファイル名に含まれるハイフン(-)やその他の特殊文字がアンダースコアに変換され、filename変数が常に有効なGoの識別子の一部として使用できる形式に正規化されます。例えば、a-b.pb.goa_b_pbに変換されます。

これらの変更は、Goコンパイラの内部的な堅牢性を高め、特にコード生成ツールとの連携や、複雑なファイル名を持つソースコードの処理において、より予測可能で安定した動作を保証するために不可欠でした。

関連リンク

参考にした情報源リンク

  • Go func literals
  • Go closure naming conventions
  • Go compiler filename handling
  • Go foo.pb.go foo.go same package
  • Go gc compiler (一般的な情報収集のため)
  • Go言語の識別子に関する一般的な情報