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

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

このコミットは、Goコンパイラ(cmd/gc)における埋め込みフィールドの扱いに関する修正です。具体的には、埋め込みフィールドがその所有パッケージによって適切に修飾されるように変更されています。これにより、異なるパッケージ間で埋め込みフィールドが使用される際の曖昧さや、それに起因するコンパイル時の問題が解消されます。

コミット

commit 8d6bc666fb86a45f2f426009b05fc07d3fb1cefc
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Wed Oct 2 12:27:33 2013 -0400

    cmd/gc: qualified embedded fields with owner package.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/14188044

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

https://github.com/golang/go/commit/8d6bc666fb86a45f2f426009b05fc07d3fb1cefc

元コミット内容

cmd/gc: qualified embedded fields with owner package.

R=rsc
CC=golang-dev
https://golang.org/cl/14188044

変更の背景

Go言語の埋め込みフィールド(Embedded Fields)は、ある構造体の中に別の構造体をフィールド名なしで埋め込むことで、埋め込まれた構造体のフィールドやメソッドを、あたかも自身のフィールドやメソッドであるかのように「プロモート」する機能です。これはコードの再利用性を高め、インターフェースの実装を簡潔にする強力なメカニズムです。

しかし、このコミットが行われた時点では、Goコンパイラ(cmd/gc)が埋め込みフィールドを処理する方法に問題がありました。特に、異なるパッケージからインポートされた構造体が埋め込みフィールドとして使用される場合、コンパイラがその埋め込みフィールドの「所有パッケージ」を正しく識別できないケースが存在しました。これにより、シンボル解決の曖昧さや、予期せぬコンパイルエラーが発生する可能性がありました。

test/fixedbugs/issue6513.dir/というテストディレクトリの存在から、この問題はGoのIssue 6513として報告されたバグに関連していると推測されます。このバグは、埋め込みフィールドのパッケージ修飾が不適切であるために発生するコンパイル時の問題を修正することを目的としています。

前提知識の解説

Go言語の埋め込みフィールド

Go言語では、構造体の中にフィールド名なしで別の構造体を埋め込むことができます。これを「埋め込みフィールド」と呼びます。埋め込みフィールドの型が構造体である場合、その構造体のフィールドやメソッドは、外側の構造体のフィールドやメソッドとして「プロモート」されます。

例:

package main

import "fmt"

type Person struct {
    Name string
}

func (p Person) Greet() {
    fmt.Printf("Hello, my name is %s\n", p.Name)
}

type Employee struct {
    Person // Person構造体を埋め込みフィールドとして埋め込む
    ID     string
}

func main() {
    e := Employee{
        Person: Person{Name: "Alice"},
        ID:     "12345",
    }
    e.Greet() // Employee型からPersonのGreetメソッドを呼び出せる
    fmt.Println(e.Name) // Employee型からPersonのNameフィールドにアクセスできる
}

この機能は、継承に似た振る舞いを実現しますが、Goは継承ではなくコンポジション(合成)を推奨しています。埋め込みフィールドは、特にインターフェースの実装を簡潔にする際にも役立ちます。

Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラです。Goのソースコードをコンパイルして実行可能なバイナリを生成する役割を担っています。コンパイルプロセスには、字句解析、構文解析、型チェック、最適化、コード生成などが含まれます。

シンボル解決とパッケージ修飾

コンパイラは、ソースコード中の変数名、関数名、型名などの「シンボル」が、どの実体を指しているのかを特定する「シンボル解決」を行います。Go言語では、シンボルはパッケージによってスコープが区切られます。例えば、fmt.PrintlnfmtパッケージのPrintln関数を指します。

埋め込みフィールドの場合、プロモートされたフィールドやメソッドは、外側の構造体のスコープで直接アクセスできるようになりますが、コンパイラの内部では、そのシンボルが元々どのパッケージに属していたかを正確に追跡する必要があります。これが「所有パッケージによる修飾(qualified with owner package)」の概念です。

技術的詳細

このコミットの技術的詳細は、Goコンパイラの内部、特に構文解析(go.yy.tab.cy.tab.h)と宣言処理(dcl.cgo.h)における埋め込みフィールドのシンボル解決の改善にあります。

主な変更点は以下の通りです。

  1. embedded関数の変更:

    • src/cmd/gc/dcl.csrc/cmd/gc/go.hにおいて、embedded関数のシグネチャがNode* embedded(Sym *s)からNode* embedded(Sym *s, Pkg *pkg)に変更されました。
    • これにより、埋め込みフィールドのシンボル(Sym *s)だけでなく、そのシンボルが属するパッケージ情報(Pkg *pkg)もembedded関数に渡されるようになりました。
    • 特に、builtinpkg(Goの組み込み型が属するパッケージ)の埋め込みフィールドを処理する際に、importpkg(現在インポート中のパッケージ)ではなく、明示的に渡されたpkgを使用するように修正されました。これは、埋め込みフィールドの実際の所有パッケージを正確に反映させるためです。
  2. 構文解析器 (go.y, y.tab.c, y.tab.h) の変更:

    • src/cmd/gc/go.yは、Go言語の文法を定義するBisonの入力ファイルです。このファイルが変更され、埋め込みフィールドの構文解析時に、そのフィールドが属するパッケージ情報をより正確に取得・伝達するようになりました。
    • 具体的には、structdcl(構造体宣言)やembed(埋め込みフィールドの定義)のルールにおいて、embedded関数を呼び出す際にimportpkglocalpkgといった適切なパッケージコンテキストを渡すように修正されています。
    • また、hidden_importsymという新しいルールが追加され、@とパッケージパス、.?という形式で、埋め込みフィールドのシンボルがどのパッケージに属するかを明示的に指定できるようになりました。これは、コンパイラ内部で埋め込みフィールドのシンボルを識別するための新しいメカニズムです。
    • src/cmd/gc/y.tab.csrc/cmd/gc/y.tab.hは、go.yから生成されるC言語の構文解析器のソースファイルとヘッダーファイルです。go.yの変更に伴い、これらのファイルも更新され、構文解析器の内部状態やトークン定義が修正されています。特に、Bisonのバージョンが2.3から2.5に更新されたことによる変更も含まれています。
  3. フォーマット出力 (fmt.c) の変更:

    • src/cmd/gc/fmt.cは、Goの型情報を文字列としてフォーマットする関数群を含んでいます。
    • このファイルに、埋め込みフィールド(t->embeddedが真の場合)で、かつシンボルがパッケージ情報を持つ場合(s->pkg != nil && s->pkg->path->len > 0)に、@\"%Z\".?という形式でパッケージパスを明示的に出力するロジックが追加されました。これは、デバッグや内部表現の可視化のために、埋め込みフィールドの所有パッケージ情報を表示するためのものです。

これらの変更により、コンパイラは埋め込みフィールドのシンボルを解決する際に、そのシンボルがどのパッケージから来たのかを正確に識別できるようになり、異なるパッケージからの埋め込みフィールドが絡む複雑なケースでのコンパイルエラーが解消されました。

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

src/cmd/gc/dcl.c

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1021,9 +1021,9 @@ tointerface(NodeList *l)
 }
 
 Node*
-embedded(Sym *s)
+embedded(Sym *s, Pkg *pkg)
 {
 	Node *n;
 	char *name;
 
 	name = s->name;
 	if(exportname(name))
 		n = newname(lookup(name));
-	else if(s->pkg == builtinpkg && importpkg != nil)
-		// The name of embedded builtins during imports belongs to importpkg.
-		n = newname(pkglookup(name, importpkg));
+	else if(s->pkg == builtinpkg)
+		// The name of embedded builtins belongs to pkg.
+		n = newname(pkglookup(name, pkg));
 	else
 		n = newname(pkglookup(name, s->pkg));
 	n = nod(ODCLFIELD, n, oldname(s));

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -1080,7 +1080,7 @@ NodeList*	constiter(NodeList *vl, Node *t, NodeList *cl);
 Node*	dclname(Sym *s);
 void	declare(Node *n, int ctxt);
 void	dumpdcl(char *st);
-Node*	embedded(Sym *s);
+Node*	embedded(Sym *s, Pkg *pkg);
 Node*	fakethis(void);
 void	funcbody(Node *n);
 void	funccompile(Node *n, int isclosure);

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1126,6 +1126,19 @@ hidden_importsym:
 		}
 		$$ = pkglookup($4->name, p);
 	}
+|\t'@' LLITERAL '.' '?'
+\t{
+\t\tPkg *p;
+\n+\t\tif($2.u.sval->len == 0)
+\t\t\tp = importpkg;
+\t\telse {
+\t\t\tif(isbadimport($2.u.sval))
+\t\t\t\terrorexit();
+\t\t\tp = mkpkg($2.u.sval);
+\t\t}
+\t\t$$ = pkglookup("?", p);
+\t}
 
 name:
 	sym	%prec NotParen
@@ -1536,7 +1549,7 @@ structdcl:
 		tn = $2;
 		if(n->op == OIND)
 			n = n->left;
-		n = embedded(n->sym);
+		n = embedded(n->sym, importpkg);
 		n->right = $2;
 		n->val = $3;
 		$$ = list1(n);
@@ -1607,7 +1620,7 @@ packname:
 embed:
 	packname
 	{
-		$$ = embedded($1);
+		$$ = embedded($1, localpkg);
 	}
 
 interfacedcl:
@@ -2061,15 +2074,19 @@ hidden_structdcl:
 	sym hidden_type oliteral
 	{
 		Sym *s;
+		Pkg *p;
 
-		if($1 != S) {
+		if($1 != S && strcmp($1->name, "?") != 0) {
 			$$ = nod(ODCLFIELD, newname($1), typenod($2));
 			$$->val = $3;
 		} else {
 			s = $2->sym;
 			if(s == S && isptr[$2->etype])
 				s = $2->type->sym;
-			$$ = embedded(s);
+			p = importpkg;
+			if($1 != S)
+				p = $1->pkg;
+			$$ = embedded(s, p);
 			$$->right = typenod($2);
 			$$->val = $3;
 		}

src/cmd/gc/fmt.c

--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -754,6 +754,9 @@ typefmt(Fmt *fp, Type *t)
 				//if(t->funarg)
 				//	fmtstrcpy(fp, "_ ");
 				//else
+				if(t->embedded && s->pkg != nil && s->pkg->path->len > 0)
+					fmtprint(fp, "@\"%Z\".? ", s->pkg->path);
+				else
 					fmtstrcpy(fp, "? ");
 			}
 		}

コアとなるコードの解説

embedded関数の変更

embedded関数は、埋め込みフィールドのシンボルを処理し、それに対応するNode(抽象構文木のノード)を生成する役割を担っています。変更前はSym *s(シンボル)のみを受け取っていましたが、変更後はPkg *pkg(パッケージ)も受け取るようになりました。

特に重要なのは、s->pkg == builtinpkgの条件分岐です。builtinpkgはGoの組み込み型(int, stringなど)が属する仮想的なパッケージです。以前は、組み込み型の埋め込みフィールドをインポートする際に、常にimportpkg(現在インポート中のパッケージ)を使用してシンボルをルックアップしていました。しかし、これは埋め込みフィールドの実際の所有パッケージを正確に反映していませんでした。

修正後は、embedded関数に明示的に渡されたpkgを使用するように変更されました。これにより、埋め込みフィールドがどのパッケージに属しているかという情報が正確に保持され、シンボル解決の精度が向上しました。

構文解析器 (go.y) の変更

go.yの変更は、埋め込みフィールドの構文解析時に、そのフィールドのパッケージ情報を正確にembedded関数に渡すためのものです。

  • hidden_importsymルールの追加: @ LLITERAL '.' '?'という新しい構文が追加されました。これは、コンパイラ内部で埋め込みフィールドのシンボルを表現するための特殊な形式です。LLITERALはパッケージパスを表し、?は埋め込みフィールドのシンボル自体を表します。このルールにより、埋め込みフィールドのシンボルがどのパッケージに属しているかを明示的に指定できるようになりました。

  • structdclembedルールの変更: structdcl(構造体宣言)とembed(埋め込みフィールドの定義)のルールにおいて、embedded関数を呼び出す際に、importpkglocalpkgといった適切なパッケージコンテキストを渡すように修正されました。これにより、構文解析の段階で埋め込みフィールドの所有パッケージ情報が正確にembedded関数に伝達されるようになりました。

  • hidden_structdclルールの変更: hidden_structdclは、内部的な構造体宣言を処理するルールです。ここでも、埋め込みフィールドのシンボルを処理する際に、$1(シンボル)がS(nilシンボル)でない場合、または?でない場合に、そのシンボルが属するパッケージをpとして取得し、embedded(s, p)を呼び出すように変更されました。これにより、匿名フィールドがポインタ型である場合など、より複雑なケースでも正確なパッケージ情報が渡されるようになりました。

fmt.cの変更

fmt.cの変更は、主にデバッグや内部表現の可視化を目的としています。埋め込みフィールドの型情報をフォーマットする際に、そのフィールドがembeddedであり、かつパッケージ情報を持つ場合に、@\"%Z\".?という形式でパッケージパスを明示的に出力するようになりました。これにより、コンパイラの内部で埋め込みフィールドの所有パッケージがどのように認識されているかを確認できるようになります。

これらの変更は、Goコンパイラが埋め込みフィールドをより堅牢に、かつ正確に処理できるようにするための重要なステップであり、特に異なるパッケージ間で埋め込みフィールドが使用される際のコンパイル時の問題を解決するのに貢献しました。

関連リンク

参考にした情報源リンク

  • https://golang.org/cl/14188044 (元のGerrit変更リスト)
  • Go言語の埋め込みフィールドに関する一般的な情報源 (Stack Overflow, Go公式ブログなど)
  • Bison (GNU Parser Generator) のドキュメント (y.tab.c, y.tab.h の理解のため)