[インデックス 15316] ファイルの概要
このコミットは、Go言語の型システムを扱う go/types パッケージと、SSA (Static Single Assignment) 形式のコード生成を扱う実験的な exp/ssa パッケージにおける変更を記述しています。主な目的は、NamedType.String() メソッドの出力にパッケージのインポートパスを含めることで、診断の曖昧さを解消し、gc コンパイラ(Goの公式コンパイラ)の出力に近づけることです。また、exp/ssa パッケージ内のいくつかのコスメティックな変更も含まれています。
コミット
commit a17c46169f59579788d0ffb82eab875e4f15fc00
Author: Alan Donovan <adonovan@google.com>
Date: Tue Feb 19 14:42:05 2013 -0500
go/types: include package import path in NamedType.String().
This avoids ambiguity and makes the diagnostics closer to
those issued by gc, but it is more verbose since it qualifies
intra-package references.
Without extra context---e.g. a 'from *Package' parameter to
Type.String()---we are forced to err on one side or the other.
Also, cosmetic changes to exp/ssa:
- Remove package-qualification workaround in Function.FullName.
- Always set go/types.Package.Path field to the import path,
since we know the correct path at this point.
- In Function.DumpTo, show variadic '...' and result type info,
and delete now-redundant "# Type: " line.
R=gri
CC=golang-dev
https://golang.org/cl/7325051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a17c46169f59579788d0ffb82eab875e4f15fc00
元コミット内容
このコミットの元の内容は以下の通りです。
- go/types:
NamedType.String()にパッケージのインポートパスを含める。- これにより曖昧さが解消され、
gcコンパイラが出力する診断に近くなるが、パッケージ内の参照も修飾されるため冗長になる。 Type.String()にfrom *Packageのような追加のコンテキストがない場合、どちらかの側でエラーを出すしかない。
- これにより曖昧さが解消され、
- exp/ssa のコスメティックな変更:
Function.FullNameからパッケージ修飾の回避策を削除。go/types.Package.Pathフィールドを常にインポートパスに設定する。これは、この時点で正しいパスが分かっているため。Function.DumpToで可変長引数...と結果の型情報を表示し、冗長になった# Type:行を削除。
変更の背景
この変更の背景には、Go言語のコンパイラやツールチェーンにおける型情報の表現と診断の改善という目的があります。
Go言語では、異なるパッケージで同じ名前の型が定義されることがあります。例えば、package A に type MyType int があり、package B にも type MyType string がある場合、単に MyType とだけ表示されると、どちらのパッケージの MyType を指しているのかが曖昧になります。特に、コンパイラがエラーメッセージやデバッグ情報を出力する際に、この曖昧さがユーザーの混乱を招く可能性がありました。
gc コンパイラは、型名を表示する際にその型が属するパッケージのインポートパスを修飾することが一般的です(例: fmt.Stringer)。go/types パッケージは、Goのソースコードを解析して型情報を構築するためのライブラリであり、コンパイラやリンター、IDEなどのツールで利用されます。この go/types パッケージが生成する型情報の文字列表現が gc コンパイラの出力と異なる場合、ツールの出力と公式コンパイラの出力との間に乖離が生じ、ユーザー体験を損なう可能性があります。
このコミットの主な動機は、go/types パッケージが提供する NamedType.String() メソッドの出力を gc コンパイラの診断出力に近づけることで、ツールの診断メッセージの一貫性と明確性を向上させることにあります。これにより、ユーザーはより正確で理解しやすい型情報を受け取ることができるようになります。
また、exp/ssa パッケージは、GoコンパイラのSSAバックエンドの実験的な実装であり、コンパイラの最適化やコード生成の過程を可視化・デバッグするために使用されます。このパッケージ内の変更は、SSA形式の関数表現のデバッグ出力(Function.DumpTo)をより情報豊富にし、かつ冗長な情報を削除することで、開発者がSSAコードを分析しやすくすることを目的としています。特に、Function.FullName からパッケージ修飾の回避策を削除したことは、go/types 側の変更によって型名の修飾が適切に行われるようになったため、exp/ssa 側での特別な処理が不要になったことを示唆しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と関連パッケージについての知識が必要です。
-
Go言語のパッケージとインポートパス: Go言語のコードはパッケージに分割されます。各パッケージは、そのパッケージを一意に識別するための「インポートパス」を持ちます。例えば、標準ライブラリの
fmtパッケージはfmtというインポートパスを持ち、GitHub上のリポジトリであればgithub.com/user/repo/package_nameのような形式になります。型や関数を参照する際には、通常、package_name.TypeNameのようにパッケージ名を修飾して参照します。 -
go/typesパッケージ:go/typesはGoの標準ライブラリの一部であり、Goのソースコードを解析して型情報を構築するためのパッケージです。これは、Goのコンパイラ、リンター、静的解析ツール、IDEなどが、Goプログラムのセマンティクス(意味)を理解するために利用します。types.Package: Goのパッケージを表す構造体です。インポートパスやパッケージ名などの情報を含みます。types.Typeインターフェース: Goのあらゆる型(基本型、構造体、インターフェース、関数など)を表すインターフェースです。types.NamedType:type MyType intのように、名前が付けられた型(定義型)を表す構造体です。これは、基底型(int)と、その型が定義されたオブジェクト(MyType)への参照を持ちます。String()メソッド: Goの多くの型はString()メソッドを実装しており、その型の文字列表現を返します。これはデバッグ出力やエラーメッセージで頻繁に利用されます。
-
exp/ssaパッケージ:exp/ssaは、GoコンパイラのSSA(Static Single Assignment)バックエンドの実験的な実装を含むパッケージです。SSAは、コンパイラの最適化フェーズで広く使用される中間表現(IR)の一種です。各変数が一度だけ代入されるという特性を持ち、データフロー解析や最適化を容易にします。ssa.Function: SSA形式で表現された関数を表す構造体です。Function.FullName(): 関数の完全な名前(パッケージ名、レシーバ型などを含む)を返すメソッドです。Function.DumpTo(): SSA形式の関数の内容を人間が読める形式で出力するメソッドです。これはデバッグや分析に役立ちます。
-
gcコンパイラ:gcは、Go言語の公式なコンパイラです。Goのソースコードを機械語に変換する役割を担います。go/typesパッケージは、gcコンパイラが内部的に使用する型システムと密接に関連しており、両者の出力の一貫性はGo開発者にとって重要です。
技術的詳細
このコミットは、主に go/types パッケージにおける型名の文字列表現の改善と、それに伴う exp/ssa パッケージの調整に焦点を当てています。
go/types における NamedType.String() の変更
以前の NamedType.String() の実装では、NamedType の文字列表現を生成する際に、その型が属するパッケージのインポートパスを含めていませんでした。これは、同じ名前の型が異なるパッケージに存在する場合に、その型がどのパッケージに属するのかを明確に識別できないという問題を引き起こしていました。
このコミットでは、src/pkg/go/types/errors.go 内の writeType 関数(NamedType の文字列表現を生成する部分)が変更され、NamedType が持つ Obj (オブジェクト) の Pkg (パッケージ) が存在し、かつその Pkg.Path (インポートパス) が空でない場合に、obj.Pkg.Path + "." を型名の前に付加するように修正されました。
変更前:
MyType (曖昧な場合がある)
変更後:
package/path.MyType (常に完全修飾される)
この変更により、NamedType.String() の出力は常にパッケージのインポートパスで修飾されるようになります。これにより、型名の曖昧さが解消され、特にエラーメッセージやデバッグ出力において、どのパッケージの型が参照されているのかが明確になります。コミットメッセージにあるように、これは gc コンパイラの診断出力に近づけるための変更です。
ただし、この変更にはトレードオフがあります。パッケージ内の参照(例: main パッケージ内で定義された MyType を main パッケージ内で参照する場合)も main.MyType のように修飾されるため、以前よりも冗長な出力になる可能性があります。コミットメッセージでは、Type.String() に from *Package のような追加のコンテキスト(どのパッケージから参照されているか)がない場合、この冗長性を避けることが難しいと説明されています。つまり、go/types パッケージは汎用的な型システムであり、特定の参照コンテキストを持たないため、常に完全修飾することが最も安全で一貫性のあるアプローチと判断されたわけです。
exp/ssa における変更
exp/ssa パッケージに対する変更は、go/types の変更と密接に関連しており、主にSSAコードのデバッグ出力の改善と、型情報の取り扱いの一貫性向上を目的としています。
-
src/pkg/exp/ssa/builder.goの変更:createPackageImpl関数内で、types.Package.Pathフィールドが常に正しいインポートパスに設定されるように修正されました。以前は、型チェッカーがGcImportedパッケージに対してのみtypes.Package.Pathを設定していましたが、SSAビルダーの段階では常に正しいインポートパスが分かっているため、これを保証するように変更されました。これにより、ssa.Package.ImportPathフィールドの冗長性が将来的に解消される可能性が示唆されています。 -
src/pkg/exp/ssa/func.goのFunction.FullNameの変更:Function.FullNameメソッドは、関数の完全な名前を生成します。以前は、クロスパッケージ参照の場合にのみパッケージ名を修飾するロジックが含まれていましたが、go/typesのNamedType.String()が常にパッケージパスを修飾するようになったため、このロジックが簡素化されました。- 変更前は、
pkgQualという変数を使ってクロスパッケージ参照の場合にのみパッケージ名を付加していました。 - 変更後は、レシーバを持つメソッドの場合、
recv.TypeのString()メソッドが既にパッケージ修飾された名前を返すため、fmt.Sprintf("(%s).%s", recv.Type, f.Name_)のように直接利用できるようになりました。 - パッケージレベルの関数の場合も、
from != f.Pkg(クロスパッケージ参照) の場合にのみf.Pkg.ImportPathを付加するようにロジックが変更され、より簡潔になりました。これにより、Function.FullNameが生成する名前も、go/typesの出力と一貫性を持つようになります。
- 変更前は、
-
src/pkg/exp/ssa/func.goのFunction.DumpToの変更:Function.DumpToメソッドは、SSA形式の関数の詳細な情報を出力します。このコミットでは、デバッグ出力の視認性と情報量を向上させるためのコスメティックな変更が行われました。- 冗長な
# Type:行が削除されました。関数のシグネチャ情報は、後続のコードでより詳細に表示されるため、この行は不要と判断されました。 - 関数のシグネチャ表示において、可変長引数(
...)と結果の型情報が追加されました。これにより、DumpToの出力から関数のシグネチャがより正確に読み取れるようになります。特に、結果の型が匿名の場合や複数の戻り値がある場合にも対応できるよう、types.Result型を使用して表現されています。
- 冗長な
これらの変更は、Goコンパイラの内部ツールが生成する型情報の表現をより一貫性のあるものにし、開発者にとってのデバッグや分析を容易にすることを目的としています。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードブロックは以下の通りです。
-
src/pkg/exp/ssa/builder.go:func (b *Builder) createPackageImpl(...) *Package関数内。typkg.Path == ""の場合にtypkg.Path = importPathを設定するロジックが追加されました。
--- a/src/pkg/exp/ssa/builder.go +++ b/src/pkg/exp/ssa/builder.go @@ -2570,11 +2570,13 @@ func (b *Builder) CreatePackage(importPath string, files []*ast.File) (*Package,\ // from the gc compiler's object files; no code will be available.\ //\ func (b *Builder) createPackageImpl(typkg *types.Package, importPath string, files []*ast.File) *Package {\ - // TODO(gri): make this an invariant and eliminate importPath - // param and Package field.\ - // if importPath != p.Types.Path {\ - // panic(importPath + " != " + p.Types.Path)\ - // }\ + // The typechecker sets types.Package.Path only for GcImported + // packages, since it doesn't know import path until after typechecking is done.\ + // Here we ensure it is always set, since we know the correct path.\ + // TODO(adonovan): eliminate redundant ssa.Package.ImportPath field.\ + if typkg.Path == "" { + typkg.Path = importPath + } p := &Package{ Prog: b.Prog, -
src/pkg/exp/ssa/func.go:func (f *Function) fullName(from *Package) string関数内。- パッケージ修飾のロジックが簡素化され、
pkgQual変数とその関連ロジックが削除されました。 - レシーバを持つメソッドとパッケージレベルの関数の名前生成ロジックが変更されました。
--- a/src/pkg/exp/ssa/func.go +++ b/src/pkg/exp/ssa/func.go @@ -379,27 +383,20 @@ func (f *Function) fullName(from *Package) string {\ } else {\ recvType = f.Params[0].Type() // interface method thunk }\ - // TODO(adonovan): print type package-qualified, if NamedType.\ return fmt.Sprintf("(%s).%s", recvType, f.Name_)\ }\ - // "pkg." prefix for cross-package references only.\ - var pkgQual string\ - if from != f.Pkg {\ - pkgQual = f.Pkg.ImportPath + ".\"\ - }\ - // Declared method?\ if recv != nil {\ - star := ""\ - if isPointer(recv.Type) {\ - star = "*"\ - }\ - return fmt.Sprintf("(%s%s%s).%s", star, pkgQual, deref(recv.Type), f.Name_)\ + return fmt.Sprintf("(%s).%s", recv.Type, f.Name_)\ }\ // Package-level function.\ - return pkgQual + f.Name_\ + // Prefix with package name for cross-package references only.\ + if from != f.Pkg {\ + return fmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)\ + }\ + return f.Name_\ }\ // DumpTo prints to w a human readable "disassembly" of the SSA code of @@ -408,7 +405,6 @@ func (f *Function) fullName(from *Package) string {\ func (f *Function) DumpTo(w io.Writer) {\ fmt.Fprintf(w, "# Name: %s\\n", f.FullName())\ fmt.Fprintf(w, "# Declared at %s\\n", f.Prog.Files.Position(f.Pos))\ - fmt.Fprintf(w, "# Type: %s\\n", f.Signature)\ if f.Enclosing != nil {\ fmt.Fprintf(w, "# Parent: %s\\n", f.Enclosing.Name())\ @@ -421,6 +417,7 @@ func (f *Function) DumpTo(w io.Writer) {\ }\ }\ + // Function Signature in declaration syntax; derived from types.Signature.String().\ io.WriteString(w, "func ")\ params := f.Params\ if f.Signature.Recv != nil {\ @@ -435,9 +432,23 @@ func (f *Function) DumpTo(w io.Writer) {\ \t\tio.WriteString(w, v.Name())\ \t\tio.WriteString(w, " ")\ +\t\tif f.Signature.IsVariadic && i == len(params)-1 {\ +\t\t\tio.WriteString(w, "...")\ +\t\t}\ \t\tio.WriteString(w, v.Type().String())\ }\ -\tio.WriteString(w, "):\\n")\ +\tio.WriteString(w, ")")\ +\tif res := f.Signature.Results; res != nil {\ +\t\tio.WriteString(w, " ")\ +\t\tvar t types.Type\ +\t\tif len(res) == 1 && res[0].Name == "" {\ +\t\t\tt = res[0].Type\ +\t\t} else {\ +\t\t\tt = &types.Result{Values: res}\ +\t\t}\ +\t\tio.WriteString(w, t.String())\ +\t}\ +\tio.WriteString(w, ":\\n")\ for _, b := range f.Blocks {\ \tif b == nil {\ -
src/pkg/go/types/errors.go:func writeType(buf *bytes.Buffer, typ Type)関数内。case *NamedType:ブロックで、obj.Pkg.Pathを利用してパッケージ名を型名の前に付加するロジックが追加されました。
--- a/src/pkg/go/types/errors.go +++ b/src/pkg/go/types/errors.go @@ -307,7 +307,11 @@ func writeType(buf *bytes.Buffer, typ Type) {\ case *NamedType:\ \ts := "<NamedType w/o object>"\ -\t\tif t.Obj != nil {\ +\t\tif obj := t.Obj; obj != nil {\ +\t\t\tif obj.Pkg != nil && obj.Pkg.Path != "" {\ +\t\t\t\tbuf.WriteString(obj.Pkg.Path)\ +\t\t\t\tbuf.WriteString(".")\ +\t\t\t}\ \t\ts = t.Obj.GetName()\ \t}\ \tbuf.WriteString(s)\
コアとなるコードの解説
src/pkg/go/types/errors.go の変更
このファイルは、go/types パッケージが型関連のエラーメッセージや診断情報を生成する際に使用するユーティリティ関数を含んでいます。writeType 関数は、types.Type インターフェースを実装する任意の型の文字列表現を bytes.Buffer に書き込む役割を担っています。
変更の核心は、case *NamedType: ブロックにあります。NamedType は type MyType int のようにユーザーが定義した名前付きの型を表します。
- 変更前:
t.Obj != nilの場合にt.Obj.GetName()を取得し、その名前だけをバッファに書き込んでいました。これにより、同じ名前の型が異なるパッケージに存在する場合に曖昧さが生じていました。 - 変更後:
obj := t.Obj; obj != nilのチェックに加え、obj.Pkg != nil && obj.Pkg.Path != ""の条件が追加されました。これは、NamedTypeが属するオブジェクト(obj)がパッケージ(obj.Pkg)を持ち、そのパッケージにインポートパス(obj.Pkg.Path)が設定されている場合に、そのインポートパスとピリオド(.)を型名の前に書き込むようにしています。buf.WriteString(obj.Pkg.Path): パッケージのインポートパスを書き込みます。buf.WriteString("."): パッケージ名と型名を区切るピリオドを書き込みます。s = t.Obj.GetName(): 型名自体を取得します。buf.WriteString(s): 最終的に型名を書き込みます。
この変更により、NamedType の文字列表現は package/path.TypeName の形式となり、常に完全修飾されることで、型名の曖昧さが解消されます。
src/pkg/exp/ssa/builder.go の変更
このファイルは、GoのAST(抽象構文木)からSSA形式のコードを構築するビルダーのロジックを含んでいます。createPackageImpl 関数は、SSAパッケージを作成する際に呼び出されます。
- 変更前:
typkg.Pathが設定されていない場合のコメントアウトされたpanicやTODOコメントがありました。これは、型チェッカーがtypes.Package.Pathを設定するタイミングに依存しており、SSAビルダーの段階で常に正しいパスが利用可能であるという保証がなかったことを示唆しています。 - 変更後:
if typkg.Path == ""という条件が追加され、typkg.Pathが空の場合に引数として渡されたimportPathを設定するように修正されました。typkgはgo/types.Package型のインスタンスであり、SSAパッケージが構築される基となる型情報パッケージです。importPathは、SSAパッケージが作成される際の正しいインポートパスです。- この変更により、
types.Package.PathがSSAビルダーの段階で常に正しい値を持つことが保証され、SSAコード生成の過程での型情報の整合性が向上します。また、コミットメッセージにあるように、これによりssa.Package.ImportPathフィールドの冗長性が将来的に解消される可能性が生まれます。
src/pkg/exp/ssa/func.go の変更
このファイルは、SSA形式の関数(ssa.Function)の定義と、その操作に関するメソッドを含んでいます。
Function.FullName() の変更
このメソッドは、関数の完全な名前(例: math.IsNaN, (*sync.WaitGroup).Add)を生成します。
- 変更前:
- レシーバを持つメソッドの場合、レシーバの型名に
*を付加するかどうかのロジックと、pkgQual変数を使ってクロスパッケージ参照の場合にのみパッケージ名を付加するロジックが含まれていました。 - パッケージレベルの関数の場合も、
pkgQualを使ってパッケージ名を付加していました。 - この
pkgQualを使ったパッケージ修飾のロジックは、go/typesのNamedType.String()がパッケージパスを修飾しないことを前提としていました。
- レシーバを持つメソッドの場合、レシーバの型名に
- 変更後:
pkgQual変数とその関連ロジックが完全に削除されました。- レシーバを持つメソッドの場合、
fmt.Sprintf("(%s).%s", recv.Type, f.Name_)のように、レシーバの型を直接recv.Typeとして渡すようになりました。これは、recv.Type.String()がgo/typesの変更によって既にパッケージ修飾された名前を返すようになったため、Function.FullName側で追加の修飾が不要になったためです。 - パッケージレベルの関数の場合も、
if from != f.Pkg(クロスパッケージ参照) の場合にのみfmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)のようにパッケージ名を付加するように変更されました。これにより、パッケージ内の参照は修飾されず、クロスパッケージ参照のみが修飾されるという、より自然な形式になります。
この変更は、go/types の NamedType.String() の変更と連携し、SSA関数の名前表現の一貫性と正確性を向上させています。
Function.DumpTo() の変更
このメソッドは、SSA形式の関数の詳細なデバッグ情報を出力します。
-
冗長な
# Type:行の削除:- 以前は
# Type: %sという行で関数のシグネチャを出力していましたが、これは後続のコードでより詳細なシグネチャ情報が出力されるため、冗長と判断され削除されました。
- 以前は
-
可変長引数(
...)と結果の型情報の追加:- 関数のパラメータリストの出力において、
if f.Signature.IsVariadic && i == len(params)-1の条件が追加され、可変長引数を持つ関数の最後のパラメータに対して...が表示されるようになりました。 - 関数の戻り値の型情報の出力が大幅に強化されました。
if res := f.Signature.Results; res != nilで戻り値が存在するかをチェックします。- 戻り値が1つで名前がない場合(例:
func() int)、その型を直接t = res[0].Typeで取得します。 - それ以外の場合(複数の戻り値や名前付き戻り値)、
t = &types.Result{Values: res}を使用してtypes.Result型として表現し、そのString()メソッドを利用して適切な文字列表現を得ます。 - これにより、
DumpToの出力が関数のシグネチャをより正確かつ詳細に反映するようになり、SSAコードの分析が容易になります。
- 関数のパラメータリストの出力において、
これらの変更は、exp/ssa パッケージが生成するデバッグ出力の品質を向上させ、Goコンパイラの開発者がSSA中間表現をより効果的に理解・デバッグできるようにすることを目的としています。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
go/typesパッケージのドキュメント: https://pkg.go.dev/go/types- SSA (Static Single Assignment) について: https://en.wikipedia.org/wiki/Static_single_assignment_form
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージの
https://golang.org/cl/7325051はGerritの変更リストへのリンクです)
参考にした情報源リンク
- コミットハッシュ:
a17c46169f59579788d0ffb82eab875e4f15fc00 - GitHub上のコミットページ: https://github.com/golang/go/commit/a17c46169f59579788d0ffb82eab875e4f15fc00
- Go言語のソースコード: https://github.com/golang/go
go/typesパッケージのソースコード: https://github.com/golang/go/tree/master/src/go/typesexp/ssaパッケージのソースコード: https://github.com/golang/go/tree/master/src/exp/ssa- Go言語のパッケージとインポートパスに関する一般的な情報源 (Go公式ドキュメントなど)
- SSA形式に関する一般的な情報源 (コンパイラ理論の教科書やオンラインリソース)
- Go言語のコンパイラ設計に関する情報源 (Goのブログ記事やカンファレンス発表など)
gcコンパイラの動作に関する情報源 (Goの内部実装に関するドキュメントやブログ記事)- Goの型システムに関する情報源 (Goの言語仕様や関連する論文)