[インデックス 14868] ファイルの概要
このコミットは、Go言語のコンパイラにおける型チェックを担当する go/types
パッケージに対する重要な変更を含んでいます。具体的には、識別子の比較ロジックを改善し、抽象構文木(AST)のオブジェクトと型情報の関連付けをより明確にすることを目的としています。
コミット
commit 8b62f54eb7bca56514984839dd26ddb05f2b3ed8
Author: Robert Griesemer <gri@golang.org>
Date: Fri Jan 11 14:55:49 2013 -0800
go/types: export QualifiedName.IsSame and NamedType.AstObj
R=adonovan
CC=golang-dev
https://golang.org/cl/7103047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8b62f54eb7bca56514984839dd26ddb05f2b3ed8
元コミット内容
go/types: export QualifiedName.IsSame and NamedType.AstObj
このコミットは、go/types
パッケージにおいて、QualifiedName
型に IsSame
メソッドを追加し、NamedType
型に AstObj
フィールドを導入することを主眼としています。これにより、型チェックにおける識別子の比較処理がより堅牢になり、また、名前付き型とそれに対応するASTオブジェクトとの関連付けが明確化されます。
変更の背景
Go言語のコンパイラは、ソースコードを解析し、その意味的な正当性を検証するために型チェックを行います。このプロセスでは、変数、関数、型などの様々な識別子を比較し、それらが同じ実体を参照しているかどうかを判断する必要があります。従来の go/types
パッケージでは、この識別子比較ロジックが identicalNames
というヘルパー関数によって行われていました。しかし、このアプローチは、特にパッケージスコープやエクスポートされた識別子の扱いにおいて、複雑さや潜在的なバグの原因となる可能性がありました。
また、名前付き型(type MyType int
のように宣言される型)は、ソースコード上の特定の宣言(ASTノード)に対応しています。型チェックの過程で、これらの名前付き型がどのASTオブジェクトに由来するのかを追跡することは、エラー報告やデバッグの際に重要となります。従来の NamedType
構造体には obj
というフィールドがありましたが、これは ast.Object
との関連付けを意図していましたが、その役割が不明瞭であったり、将来的な変更を見越してリファクタリングの必要がありました。
このコミットは、これらの課題に対処し、go/types
パッケージの内部構造を改善することで、型チェックの正確性と保守性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラの概念に関する知識が必要です。
- Go言語の型システム: Goは静的型付け言語であり、全ての変数や式には型があります。型チェックは、プログラムが型の規則に違反していないことを保証するプロセスです。
go/ast
パッケージ: Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として表現するためのパッケージです。ASTは、プログラムの構造を木構造で表現したもので、コンパイラがソースコードを解析する際の主要なデータ構造となります。ast.Object
:go/ast
パッケージにおけるObject
は、Goプログラム内の宣言されたエンティティ(変数、関数、型など)を表す抽象的な概念です。各ast.Object
は、そのエンティティの名前、種類、および対応するASTノードへの参照を持ちます。go/types
パッケージ: Go言語のセマンティック分析(意味解析)と型チェックを行うためのパッケージです。このパッケージは、go/ast
が生成したASTを受け取り、Go言語の仕様に従って型の整合性を検証し、型情報やスコープ情報を構築します。- Qualified Name (修飾名): Go言語において、パッケージ名で修飾された識別子を指します。例えば、
fmt.Println
のfmt.Println
は修飾名です。これは、異なるパッケージで同じ名前の識別子が宣言されている場合に、どちらの識別子を参照しているかを明確にするために使用されます。 - Exported vs. Unexported Identifiers: Go言語では、識別子(変数名、関数名、型名など)の最初の文字が大文字である場合、その識別子はエクスポートされ、他のパッケージから参照可能です。小文字である場合はエクスポートされず、宣言されたパッケージ内でのみ参照可能です。このルールは、識別子の比較において重要な意味を持ちます。
nil
ポインタ: Goにおけるnil
は、ポインタ、スライス、マップ、チャネル、インターフェースなどのゼロ値を表します。このコミットでは、QualifiedName
のPkg
フィールドがnil
になるケースが導入されており、これは現在のパッケージ内の識別子であることを示します。
技術的詳細
このコミットの技術的な核心は、go/types
パッケージにおける識別子の比較ロジックの再構築と、名前付き型とASTオブジェクトの関連付けの明確化にあります。
-
QualifiedName.IsSame
メソッドの導入:- 以前は
identicalNames
というグローバル関数で処理されていた識別子比較ロジックが、QualifiedName
型のメソッドIsSame
としてカプセル化されました。 - このメソッドは、Go言語の識別子比較ルール(名前が同じであること、およびエクスポートされているか否か、パッケージパスが同じか否か)を正確に実装しています。
- 特に、エクスポートされていない識別子の場合、それらが同じパッケージに属している場合にのみ「同じ」と判断されます。エクスポートされた識別子は、異なるパッケージに属していても名前が同じであれば「同じ」と見なされます(ただし、これは型システム内部での比較であり、実際のGo言語のリンケージルールとは異なる場合があります)。
QualifiedName
のPkg
フィールドがnil
の場合、それは現在の(インポートされていない)パッケージ内の識別子であることを意味するように変更されました。IsSame
メソッドはこのnil
のケースも適切に処理します。
- 以前は
-
NamedType.AstObj
フィールドの導入:NamedType
構造体にAstObj *ast.Object
という新しいフィールドが追加されました。これは、名前付き型が宣言された際の元のast.Object
を指します。- 既存の
obj *ast.Object
フィールドはコメントアウトされ、最終的には削除される予定であることが示唆されています。これは、NamedType
がASTオブジェクトを参照する方法を統一し、明確にするためのリファクタリングの一環です。 - これにより、型チェックの過程で、特定の名前付き型がソースコード上のどの宣言に対応しているかを容易に追跡できるようになります。
-
identicalNames
関数の削除と置き換え:src/pkg/go/types/predicates.go
からidenticalNames
関数が完全に削除されました。- この関数への全ての呼び出しは、新しく導入された
QualifiedName.IsSame
メソッドに置き換えられました。これにより、識別子比較ロジックが一箇所に集約され、コードの可読性と保守性が向上しました。
-
QualifiedName
のPkg
フィールドの変更:QualifiedName
構造体のPkg
フィールドのコメントが「Pkg *Package // nil for current (non-imported) package
」に変更されました。これは、現在のパッケージ内の識別子の場合、Pkg
がnil
になることを明示しています。これにより、QualifiedName
のインスタンス化が簡素化され、check.pkg
を常に渡す必要がなくなりました。
これらの変更は、go/types
パッケージの内部的な整合性を高め、将来的な機能拡張やバグ修正を容易にするための基盤を強化するものです。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイルに集中しています。
-
src/pkg/go/types/types.go
:QualifiedName
構造体にIsSame
メソッドが追加されました。NamedType
構造体にAstObj *ast.Object
フィールドが追加され、既存のobj
フィールドはコメントアウトされました。QualifiedName
のPkg
フィールドのコメントが更新されました。
-
src/pkg/go/types/predicates.go
:identicalNames
関数が削除されました。isIdentical
関数内のidenticalNames
の呼び出しがf.QualifiedName.IsSame(g.QualifiedName)
に変更されました。
-
src/pkg/go/types/check.go
,src/pkg/go/types/errors.go
,src/pkg/go/types/expr.go
,src/pkg/go/types/operand.go
,src/pkg/go/types/universe.go
:NamedType
のobj
フィールドへの参照がAstObj
に変更されました。identicalNames
関数への呼び出しがQualifiedName.IsSame
メソッドへの呼び出しに置き換えられました。QualifiedName
の初期化において、現在のパッケージの識別子に対してPkg
フィールドにnil
を渡すように変更されました。
コアとなるコードの解説
src/pkg/go/types/types.go
の変更
// A QualifiedName is a name qualified with the package that declared the name.
type QualifiedName struct {
Pkg *Package // nil for current (non-imported) package
Name string // unqualified type name for anonymous fields
}
// IsSame reports whether p and q are the same.
func (p QualifiedName) IsSame(q QualifiedName) bool {
// spec:
// "Two identifiers are different if they are spelled differently,
// or if they appear in different packages and are not exported.
// Otherwise, they are the same."
if p.Name != q.Name {
return false
}
// p.Name == q.Name
if !ast.IsExported(p.Name) {
// TODO(gri) just compare packages once we guarantee that they are canonicalized
pp := ""
if p.Pkg != nil {
pp = p.Pkg.Path
}
qp := ""
if q.Pkg != nil {
qp = q.Pkg.Path
}
return pp == qp
}
return true
}
// A NamedType represents a named type as declared in a type declaration.
type NamedType struct {
implementsType
// TODO(gri) remove AstObj once we have moved away from ast.Objects
Obj Object // corresponding declared object (imported package)
AstObj *ast.Object // corresponding declared object (current package)
Underlying Type // nil if not fully declared yet; never a *NamedType
Methods []*Method // TODO(gri) consider keeping them in sorted order
}
QualifiedName.IsSame
メソッドは、Go言語の仕様に厳密に従って2つの識別子が「同じ」であるかを判断します。- まず、名前(
Name
フィールド)が異なる場合はfalse
を返します。 - 名前が同じ場合、その識別子がエクスポートされているかどうか(
ast.IsExported(p.Name)
)をチェックします。 - エクスポートされていない識別子の場合、それらが同じパッケージパス(
Pkg.Path
)を持つ場合にのみtrue
を返します。Pkg
がnil
の場合は空文字列として扱われ、現在のパッケージ内の識別子であることを示します。 - エクスポートされた識別子の場合、名前が同じであれば常に
true
を返します。これは、エクスポートされた識別子はパッケージ境界を越えて参照可能であるためです。
- まず、名前(
NamedType
に追加されたAstObj
フィールドは、この名前付き型がソースコード上のどのast.Object
に対応するかを直接参照できるようにします。これにより、型チェック中に元の宣言情報を簡単に取得できるようになります。
src/pkg/go/types/predicates.go
の変更
--- a/src/pkg/go/types/predicates.go
+++ b/src/pkg/go/types/predicates.go
@@ -6,8 +6,6 @@
package types
-import "go/ast"
-
func isNamed(typ Type) bool {
if _, ok := typ.(*Basic); ok {
return ok
@@ -131,7 +129,7 @@ func isIdentical(x, y Type) bool {
if len(x.Fields) == len(y.Fields) {
for i, f := range x.Fields {
g := y.Fields[i]
- if !identicalNames(f.QualifiedName, g.QualifiedName) ||
+ if !f.QualifiedName.IsSame(g.QualifiedName) ||
!isIdentical(f.Type, g.Type) ||
f.Tag != g.Tag ||
f.IsAnonymous != g.IsAnonymous {
@@ -198,17 +196,6 @@ func isIdentical(x, y Type) bool {
return false
}
-// identicalNames returns true if the names a and b are equal.
-func identicalNames(a, b QualifiedName) bool {
- if a.Name != b.Name {
- return false
- }
- // a.Name == b.Name
- // TODO(gri) Guarantee that packages are canonicalized
- // and then we can compare p == q directly.
- return ast.IsExported(a.Name) || a.Pkg.Path == b.Pkg.Path
-}
-
// identicalTypes returns true if both lists a and b have the
// same length and corresponding objects have identical types.
func identicalTypes(a, b []*Var) bool {
identicalNames
関数が削除され、そのロジックはQualifiedName.IsSame
に移されました。isIdentical
関数内で、構造体のフィールド比較を行う際にf.QualifiedName.IsSame(g.QualifiedName)
が使用されるようになりました。これにより、型比較のロジックがよりクリーンで一貫性のあるものになります。
これらの変更により、go/types
パッケージは、Go言語の識別子と型のセマンティクスをより正確かつ効率的に処理できるようになります。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
go/types
パッケージのドキュメント: https://pkg.go.dev/go/typesgo/ast
パッケージのドキュメント: https://pkg.go.dev/go/ast- Go言語の仕様: https://golang.org/ref/spec
参考にした情報源リンク
- このコミットの変更リスト (Gerrit): https://golang.org/cl/7103047
- Go言語のコンパイラに関する一般的な情報源 (例: Goコンパイラの内部構造に関するブログ記事や論文など)
- "The Go Programming Language Specification" - Go言語の識別子とパッケージのルールに関する詳細。
- "Go compiler internals" (Goコンパイラの内部に関する非公式な解説やブログ記事) -
go/types
パッケージの役割と機能について理解を深めるために参照。 - Go言語のソースコード自体 -
go/types
パッケージの他の部分や、go/ast
パッケージとの連携を理解するために直接参照。