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

[インデックス 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.Printlnfmt.Println は修飾名です。これは、異なるパッケージで同じ名前の識別子が宣言されている場合に、どちらの識別子を参照しているかを明確にするために使用されます。
  • Exported vs. Unexported Identifiers: Go言語では、識別子(変数名、関数名、型名など)の最初の文字が大文字である場合、その識別子はエクスポートされ、他のパッケージから参照可能です。小文字である場合はエクスポートされず、宣言されたパッケージ内でのみ参照可能です。このルールは、識別子の比較において重要な意味を持ちます。
  • nil ポインタ: Goにおける nil は、ポインタ、スライス、マップ、チャネル、インターフェースなどのゼロ値を表します。このコミットでは、QualifiedNamePkg フィールドが nil になるケースが導入されており、これは現在のパッケージ内の識別子であることを示します。

技術的詳細

このコミットの技術的な核心は、go/types パッケージにおける識別子の比較ロジックの再構築と、名前付き型とASTオブジェクトの関連付けの明確化にあります。

  1. QualifiedName.IsSame メソッドの導入:

    • 以前は identicalNames というグローバル関数で処理されていた識別子比較ロジックが、QualifiedName 型のメソッド IsSame としてカプセル化されました。
    • このメソッドは、Go言語の識別子比較ルール(名前が同じであること、およびエクスポートされているか否か、パッケージパスが同じか否か)を正確に実装しています。
    • 特に、エクスポートされていない識別子の場合、それらが同じパッケージに属している場合にのみ「同じ」と判断されます。エクスポートされた識別子は、異なるパッケージに属していても名前が同じであれば「同じ」と見なされます(ただし、これは型システム内部での比較であり、実際のGo言語のリンケージルールとは異なる場合があります)。
    • QualifiedNamePkg フィールドが nil の場合、それは現在の(インポートされていない)パッケージ内の識別子であることを意味するように変更されました。IsSame メソッドはこの nil のケースも適切に処理します。
  2. NamedType.AstObj フィールドの導入:

    • NamedType 構造体に AstObj *ast.Object という新しいフィールドが追加されました。これは、名前付き型が宣言された際の元の ast.Object を指します。
    • 既存の obj *ast.Object フィールドはコメントアウトされ、最終的には削除される予定であることが示唆されています。これは、NamedType がASTオブジェクトを参照する方法を統一し、明確にするためのリファクタリングの一環です。
    • これにより、型チェックの過程で、特定の名前付き型がソースコード上のどの宣言に対応しているかを容易に追跡できるようになります。
  3. identicalNames 関数の削除と置き換え:

    • src/pkg/go/types/predicates.go から identicalNames 関数が完全に削除されました。
    • この関数への全ての呼び出しは、新しく導入された QualifiedName.IsSame メソッドに置き換えられました。これにより、識別子比較ロジックが一箇所に集約され、コードの可読性と保守性が向上しました。
  4. QualifiedNamePkg フィールドの変更:

    • QualifiedName 構造体の Pkg フィールドのコメントが「Pkg *Package // nil for current (non-imported) package」に変更されました。これは、現在のパッケージ内の識別子の場合、Pkgnil になることを明示しています。これにより、QualifiedName のインスタンス化が簡素化され、check.pkg を常に渡す必要がなくなりました。

これらの変更は、go/types パッケージの内部的な整合性を高め、将来的な機能拡張やバグ修正を容易にするための基盤を強化するものです。

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

このコミットにおける主要なコード変更は、以下のファイルに集中しています。

  • src/pkg/go/types/types.go:

    • QualifiedName 構造体に IsSame メソッドが追加されました。
    • NamedType 構造体に AstObj *ast.Object フィールドが追加され、既存の obj フィールドはコメントアウトされました。
    • QualifiedNamePkg フィールドのコメントが更新されました。
  • 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:

    • NamedTypeobj フィールドへの参照が 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 を返します。Pkgnil の場合は空文字列として扱われ、現在のパッケージ内の識別子であることを示します。
    • エクスポートされた識別子の場合、名前が同じであれば常に 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言語の識別子と型のセマンティクスをより正確かつ効率的に処理できるようになります。

関連リンク

参考にした情報源リンク

  • このコミットの変更リスト (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 パッケージとの連携を理解するために直接参照。