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

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

このコミットは、Go言語の型システムを扱う go/types パッケージにおいて、すべてのオブジェクトに Pkg *Package フィールドを追加する変更です。この変更により、型チェック時に各オブジェクトがどのパッケージに属しているかをより正確に追跡できるようになります。ただし、事前に宣言された(universe)オブジェクトや、関数パラメータ/結果変数については、この Pkg フィールドは nil となります。

コミット

commit 95aaca6708f719e03ab71f553981da2451142ec7
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Feb 18 14:40:47 2013 -0800

    go/types: Pkg *Package field for all objects
    
    The field is nil for predeclared (universe)
    objects and parameter/result variables.
    
    R=adonovan
    CC=golang-dev
    https://golang.org/cl/7312093

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

https://github.com/golang/go/commit/95aaca6708f719e03ab71f553981da2451142ec7

元コミット内容

go/types: Pkg *Package field for all objects

The field is nil for predeclared (universe)
objects and parameter/result variables.

R=adonovan
CC=golang-dev
https://golang.org/cl/7312093

変更の背景

Go言語のコンパイラやツールチェーンにおいて、型チェックは非常に重要なプロセスです。go/types パッケージは、Goプログラムの型チェックを行うための中心的な役割を担っています。これまでの実装では、型システム内の各種オブジェクト(定数、型名、変数、関数など)がどのパッケージに属しているかという情報が、一貫した形でオブジェクト自体に保持されていませんでした。

このコミットの背景には、型チェックの精度向上と、より堅牢なツール開発の必要性があります。特に、異なるパッケージ間でオブジェクトが参照される場合、そのオブジェクトがどのパッケージで宣言されたものかを正確に識別できることは、名前解決、型互換性のチェック、そしてエラー報告の正確性を高める上で不可欠です。

例えば、ある型が別のパッケージで定義されている場合、その型の完全な識別子(例: fmt.Stringer)にはパッケージ情報が含まれます。型チェッカーがこのような参照を正しく解決し、そのオブジェクトが実際に fmt パッケージに属していることを確認するためには、オブジェクト自体がそのパッケージへの参照を持つことが望ましいです。

この変更は、go/types パッケージがGo 1.5以降で標準ライブラリの一部となる以前の段階で行われており、Goの型システム設計の進化の一環として、より洗練された内部表現を目指したものです。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。

  1. go/types パッケージ:

    • Go言語の標準ライブラリの一部であり、Goプログラムの型チェックを行うためのパッケージです。
    • 主な機能は、識別子の解決、型の推論、定数式の評価、そしてGoの型システム全体の表現と管理です。
    • go/ast パッケージが提供する抽象構文木(AST)を入力として受け取り、それに意味論的な情報を付与します。
  2. 型チェック (Type Checking):

    • プログラムがGo言語の型規則に準拠しているかを確認するプロセスです。
    • コンパイル時に行われ、型互換性の問題や不正な操作を検出することで、実行時エラーを未然に防ぎます。
  3. 抽象構文木 (AST - Abstract Syntax Tree):

    • go/ast パッケージによって生成される、Goソースコードの構造を木構造で表現したものです。
    • ソースコードの構文的な構造を表現しますが、型情報や識別子の解決といった意味論的な情報は直接持ちません。
  4. ast.Objecttypes.Object:

    • ast.Object: go/ast パッケージにおける、識別子(変数、関数、型など)の宣言を表す汎用的な構造体です。構文解析の段階で作成されます。
    • types.Object: go/types パッケージにおける、型チェックされた後の識別子を表すインターフェースです。ConstTypeNameVarFunc などの具体的な型がこのインターフェースを実装します。ast.Object から types.Object への変換は、型チェックの過程で行われ、この types.Object には型情報やスコープ情報などの意味論的な情報が付与されます。
  5. Universe Block (ユニバースブロック):

    • Go言語における最も外側のスコープであり、すべてのGoソースコードを包含します。
    • true, false, iota, nil などの事前宣言された定数、int, string, bool などの組み込み型、len, cap, make などの組み込み関数がこのユニバースブロックで定義されています。
    • これらのオブジェクトは、どのパッケージにも属さない「グローバル」な存在として扱われます。
  6. Package (パッケージ):

    • Go言語におけるコードの組織単位です。関連する機能や型をまとめるために使用されます。
    • go/types パッケージでは、Package 型がGoのパッケージ自体を表し、そのパッケージ内で宣言されたオブジェクトやインポートされたパッケージの情報を管理します。

技術的詳細

このコミットの核心は、go/types パッケージ内の主要なオブジェクト型 (Const, TypeName, Var, Func) に Pkg *Package フィールドを追加し、それらのオブジェクトがどの Package に属しているかを明示的に保持するようにした点です。

具体的には、以下の変更が行われました。

  1. Object インターフェースの拡張:

    • go/types/objects.go において、Object インターフェースに GetPkg() *Package メソッドが追加されました。これにより、すべての Object は自身が属するパッケージを取得できるようになります。
  2. オブジェクト型への Pkg フィールドの追加:

    • Const, TypeName, Var, Func の各構造体に Pkg *Package フィールドが追加されました。
    • Var 型の Pkg フィールドには、コメントで「// nil for parameters」と明記されており、関数パラメータや結果変数については nil となることが示されています。これは、これらの変数が特定のパッケージのトップレベルで宣言されるわけではないためです。
  3. GetPkg() メソッドの実装:

    • 追加された Pkg フィールドに対応する GetPkg() メソッドが、Const, TypeName, Var, Func の各型に実装されました。Package 型自体も Object インターフェースを実装し、GetPkg() メソッドは自身を返します。
  4. オブジェクト生成時の Pkg フィールドの設定:

    • newObj 関数(go/types/objects.go)が pkg *Package 引数を受け取るように変更され、生成される Const, TypeName, Var, Func オブジェクトの Pkg フィールドがこの引数で初期化されるようになりました。
    • newObj 関数内では assert(pkg != nil) が追加され、Pkgnil でないことが保証されます。これは、newObj が通常、特定のパッケージのコンテキストで呼び出されることを意味します。
  5. 宣言ヘルパー関数の変更:

    • go/types/gcimporter.go にある declConst, declTypeName, declVar, declFunc といった宣言ヘルパー関数が、scope *Scope ではなく pkg *Package を引数として受け取るように変更されました。これにより、これらの関数がオブジェクトを生成する際に、そのオブジェクトが属するパッケージ情報を直接設定できるようになりました。
    • これらの関数内では、引数として受け取った pkgScope を使用してオブジェクトをスコープに挿入します。
  6. 型チェックロジックの調整:

    • go/types/check.go では、newObj の呼び出し箇所が check.pkg を引数として渡すように変更されました。check.pkg は、現在型チェック中のパッケージを表します。
    • assocMethoddecl 関数内での Func オブジェクトの生成時にも Pkg: check.pkg が明示的に設定されるようになりました。
    • check 関数の defer 句で pkg = check.pkg が設定されるようになり、型チェックの結果として得られるパッケージが正しく返されるようになりました。
    • resolve 関数のシグネチャが変更され、pkg *Package を返さなくなり、代わりに check.pkg フィールドに設定されるようになりました。
  7. ユニバースオブジェクトの特殊な扱い:

    • go/types/universe.go では、predeclaredConstants の初期化において、Pkg フィールドが明示的に nil に設定されました。これは、true, false, iota, nil といったユニバースブロックの定数がどの特定のパッケージにも属さないためです。
    • def 関数(ユニバースオブジェクトを定義する関数)では、TypeNameFunc のオブジェクトに対して、Unsafe パッケージに属する場合に Pkg フィールドを設定するロジックが追加されました。これは、unsafe パッケージのオブジェクトがユニバーススコープの一部として扱われるものの、特定のパッケージに属するという特性を持つためです。
  8. テストコードの更新:

    • go/types/check_test.go では、ユニバースオブジェクトの Pkgnil であることを示すコメントが追加されました。
    • go/types/resolver_test.go では、テスト内で生成される VarFunc オブジェクトに Pkg: pkg が設定されるようになりました。

この変更により、go/types パッケージは、型システム内のすべての名前付きオブジェクトについて、それがどのパッケージで宣言されたものかという情報を一貫して保持できるようになりました。これにより、型チェックの正確性が向上し、Goのツールチェーンがより堅牢なセマンティック分析を実行するための基盤が強化されます。

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

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

  • src/pkg/go/types/objects.go: Object インターフェースの拡張と、各オブジェクト型への Pkg フィールドの追加、および newObj 関数の変更。
  • src/pkg/go/types/gcimporter.go: オブジェクト宣言ヘルパー関数 (declConst, declTypeName, declVar, declFunc) のシグネチャ変更と Pkg フィールドの設定。
  • src/pkg/go/types/check.go: newObj の呼び出し箇所やオブジェクト生成時の Pkg フィールドの設定、および check 関数の戻り値の変更。
  • src/pkg/go/types/universe.go: 事前宣言された定数(ユニバースオブジェクト)の Pkg フィールドの初期化と、def 関数の変更。

src/pkg/go/types/objects.go

--- a/src/pkg/go/types/objects.go
+++ b/src/pkg/go/types/objects.go
@@ -14,6 +14,7 @@ import (
 // All objects implement the Object interface.
 //
 type Object interface {
+	GetPkg() *Package
 	GetName() string
 	GetType() Type
 	GetPos() token.Pos
@@ -34,6 +35,7 @@ type Package struct {
 
 // A Const represents a declared constant.
 type Const struct {
+	Pkg  *Package
 	Name string
 	Type Type
 	Val  interface{}
@@ -43,6 +45,7 @@ type Const struct {
 
 // A TypeName represents a declared type.
 type TypeName struct {
+	Pkg  *Package
 	Name string
 	Type Type // *NamedType or *Basic
 
@@ -51,6 +54,7 @@ type TypeName struct {
 
 // A Variable represents a declared variable (including function parameters and results).
 type Var struct {
+	Pkg  *Package // nil for parameters
 	Name string
 	Type Type
 
@@ -60,12 +64,19 @@ type Var struct {
 
 // A Func represents a declared function.
 type Func struct {
+	Pkg  *Package
 	Name string
 	Type Type // *Signature or *Builtin
 
 	decl *ast.FuncDecl
 }
 
+func (obj *Package) GetPkg() *Package  { return obj }
+func (obj *Const) GetPkg() *Package    { return obj.Pkg }
+func (obj *TypeName) GetPkg() *Package { return obj.Pkg }
+func (obj *Var) GetPkg() *Package      { return obj.Pkg }
+func (obj *Func) GetPkg() *Package     { return obj.Pkg }
+
 func (obj *Package) GetName() string  { return obj.Name }
 func (obj *Const) GetName() string    { return obj.Name }
 func (obj *TypeName) GetName() string { return obj.Name }
@@ -126,18 +137,18 @@ func (*Func) anObject()     {}
 // TODO(gri) Once we do identifier resolution completely in
 //           in the typechecker, this functionality can go.
 //
-func newObj(astObj *ast.Object) Object {
+func newObj(pkg *Package, astObj *ast.Object) Object {
+	assert(pkg != nil)
 	name := astObj.Name
 	typ, _ := astObj.Type.(Type)
 	switch astObj.Kind {
 	case ast.Pkg:
 		unreachable()
 	case ast.Con:
-		return &Const{Name: name, Type: typ, Val: astObj.Data, spec: astObj.Decl.(*ast.ValueSpec)}
+		return &Const{Pkg: pkg, Name: name, Type: typ, Val: astObj.Data, spec: astObj.Decl.(*ast.ValueSpec)}
 	case ast.Typ:
-		return &TypeName{Name: name, Type: typ, spec: astObj.Decl.(*ast.TypeSpec)}
+		return &TypeName{Pkg: pkg, Name: name, Type: typ, spec: astObj.Decl.(*ast.TypeSpec)}
 	case ast.Var:
 		switch astObj.Decl.(type) {
 		case *ast.Field, *ast.ValueSpec, *ast.AssignStmt: // these are ok
 		default:
 			unreachable()
 		}
-		return &Var{Name: name, Type: typ, decl: astObj.Decl}
+		return &Var{Pkg: pkg, Name: name, Type: typ, decl: astObj.Decl}
 	case ast.Fun:
-		return &Func{Name: name, Type: typ, decl: astObj.Decl.(*ast.FuncDecl)}
+		return &Func{Pkg: pkg, Name: name, Type: typ, decl: astObj.Decl.(*ast.FuncDecl)}
 	case ast.Lbl:
 		unreachable() // for now
 	}

src/pkg/go/types/gcimporter.go

--- a/src/pkg/go/types/gcimporter.go
+++ b/src/pkg/go/types/gcimporter.go
@@ -197,23 +197,25 @@ func (p *gcParser) next() {
 	}
 }
 
-func declConst(scope *Scope, name string) *Const {
+func declConst(pkg *Package, name string) *Const {
 	// the constant may have been imported before - if it exists
 	// already in the respective scope, return that constant
+\tscope := pkg.Scope
 	if obj := scope.Lookup(name); obj != nil {
 		return obj.(*Const)
 	}
 	// otherwise create a new constant and insert it into the scope
-	obj := &Const{Name: name}
+	obj := &Const{Pkg: pkg, Name: name}
 	scope.Insert(obj)
 	return obj
 }
 
-func declTypeName(scope *Scope, name string) *TypeName {
+func declTypeName(pkg *Package, name string) *TypeName {
+\tscope := pkg.Scope
 	if obj := scope.Lookup(name); obj != nil {
 		return obj.(*TypeName)
 	}
-	obj := &TypeName{Name: name}
+	obj := &TypeName{Pkg: pkg, Name: name}
 	// a named type may be referred to before the underlying type
 	// is known - set it up
 	obj.Type = &NamedType{Obj: obj}
@@ -221,20 +223,22 @@ func declTypeName(scope *Scope, name string) *TypeName {
 	return obj
 }
 
-func declVar(scope *Scope, name string) *Var {
+func declVar(pkg *Package, name string) *Var {
+\tscope := pkg.Scope
 	if obj := scope.Lookup(name); obj != nil {
 		return obj.(*Var)
 	}
-	obj := &Var{Name: name}
+	obj := &Var{Pkg: pkg, Name: name}
 	scope.Insert(obj)
 	return obj
 }
 
-func declFunc(scope *Scope, name string) *Func {
+func declFunc(pkg *Package, name string) *Func {
+\tscope := pkg.Scope
 	if obj := scope.Lookup(name); obj != nil {
 		return obj.(*Func)
 	}
-	obj := &Func{Name: name}
+	obj := &Func{Pkg: pkg, Name: name}
 	scope.Insert(obj)
 	return obj
 }
@@ -507,7 +511,7 @@ func (p *gcParser) parseParameter() (par *Var, isVariadic bool) {
 	if p.tok == scanner.String {
 		p.next()
 	}
-	par = &Var{Name: name, Type: typ} // Pkg == nil
+	par = &Var{Name: name, Type: typ} // Pkg == nil
 	return
 }
 
@@ -637,7 +641,7 @@ func (p *gcParser) parseType() Type {
 	case '@':
 		// TypeName
 		pkg, name := p.parseExportedName()
-		return declTypeName(pkg.Scope, name).Type
+		return declTypeName(pkg, name).Type
 	case '[':
 		p.next() // look ahead
 		if p.tok == ']' {
@@ -740,7 +744,7 @@ func (p *gcParser) parseNumber() (x operand) {
 func (p *gcParser) parseConstDecl() {
 	p.expectKeyword("const")
 	pkg, name := p.parseExportedName()
-	obj := declConst(pkg.Scope, name)
+	obj := declConst(pkg, name)
 	var x operand
 	if p.tok != '=' {
 		obj.Type = p.parseType()
@@ -806,7 +810,7 @@ func (p *gcParser) parseConstDecl() {
 func (p *gcParser) parseTypeDecl() {
 	p.expectKeyword("type")
 	pkg, name := p.parseExportedName()
-	obj := declTypeName(pkg.Scope, name)
+	obj := declTypeName(pkg, name)
 
 	// The type object may have been imported before and thus already
 	// have a type associated with it. We still need to parse the type
@@ -825,7 +829,7 @@ func (p *gcParser) parseTypeDecl() {
 func (p *gcParser) parseVarDecl() {
 	p.expectKeyword("var")
 	pkg, name := p.parseExportedName()
-	obj := declVar(pkg.Scope, name)
+	obj := declVar(pkg, name)
 	obj.Type = p.parseType()
 }
 
@@ -886,7 +890,7 @@ func (p *gcParser) parseFuncDecl() {
 	// "func" already consumed
 	pkg, name := p.parseExportedName()
 	typ := p.parseFunc()
-	declFunc(pkg.Scope, name).Type = typ
+	declFunc(pkg, name).Type = typ
 }
 
 // Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" .

src/pkg/go/types/check.go

--- a/src/pkg/go/types/check.go
+++ b/src/pkg/go/types/check.go
@@ -70,7 +70,7 @@ func (check *checker) lookup(ident *ast.Ident) Object {
 	}
 
 	if obj = check.objects[astObj]; obj == nil {
-		obj = newObj(astObj)
+		obj = newObj(check.pkg, astObj)
 		check.objects[astObj] = obj
 	}
 	check.register(ident, obj)
@@ -346,7 +346,7 @@ func (check *checker) assocMethod(meth *ast.FuncDecl) {
 		scope = new(Scope)
 		check.methods[tname] = scope
 	}
-	check.declareIdent(scope, meth.Name, &Func{Name: meth.Name.Name, decl: meth})
+	check.declareIdent(scope, meth.Name, &Func{Pkg: check.pkg, Name: meth.Name.Name, decl: meth})
 }
 
 func (check *checker) decl(decl ast.Decl) {
@@ -378,7 +378,7 @@ func (check *checker) decl(decl ast.Decl) {
 		// since they are not in any scope. Create a dummy object for them.
 		if d.Name.Name == "init" {
 			assert(obj == nil) // all other functions should have an object
-			obj = &Func{Name: d.Name.Name, decl: d}
+			obj = &Func{Pkg: check.pkg, Name: d.Name.Name, decl: d}
 			check.register(d.Name, obj)
 		}
 		check.object(obj, false)
@@ -403,8 +403,9 @@ func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package,
 		conversions: make(map[*ast.CallExpr]bool),
 	}
 
-	// handle panics
+	// set results and handle panics
 	defer func() {
+		pkg = check.pkg
 		switch p := recover().(type) {
 		case nil, bailout:
 			// normal return or early exit
@@ -422,8 +423,7 @@ func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package,
 	if imp == nil {
 		imp = GcImport
 	}
-	pkg, methods := check.resolve(imp)
-	check.pkg = pkg
+	methods := check.resolve(imp)
 
 	// associate methods with types
 	for _, m := range methods {

src/pkg/go/types/universe.go

--- a/src/pkg/go/types/universe.go
+++ b/src/pkg/go/types/universe.go
@@ -55,10 +55,10 @@ var aliases = [...]*Basic{
 }
 
 var predeclaredConstants = [...]*Const{
-\t{\"true\", Typ[UntypedBool], true, nil},\n-\t{\"false\", Typ[UntypedBool], false, nil},\n-\t{\"iota\", Typ[UntypedInt], zeroConst, nil},\n-\t{\"nil\", Typ[UntypedNil], nilConst, nil},\n+\t{nil, \"true\", Typ[UntypedBool], true, nil},\n+\t{nil, \"false\", Typ[UntypedBool], false, nil},\n+\t{nil, \"iota\", Typ[UntypedInt], zeroConst, nil},\n+\t{nil, \"nil\", Typ[UntypedNil], nilConst, nil},\n }
 
 var predeclaredFunctions = [...]*builtin{
@@ -130,6 +130,15 @@ func def(obj Object) {
 	scope := Universe
 	if ast.IsExported(name) {
 		scope = Unsafe.Scope
+		// set Pkg field
+		switch obj := obj.(type) {
+		case *TypeName:
+			obj.Pkg = Unsafe
+		case *Func:
+			obj.Pkg = Unsafe
+		default:
+			unreachable()
+		}
 	}
 	if scope.Insert(obj) != nil {
 		panic("internal error: double declaration")

コアとなるコードの解説

src/pkg/go/types/objects.go

  • Object インターフェースの変更: GetPkg() *Package メソッドが追加されたことで、go/types パッケージ内のすべての名前付きオブジェクトが、自身が属するパッケージ情報を統一的に提供できるようになりました。これは、型チェックやセマンティック分析を行うツールにとって非常に有用な情報となります。
  • 構造体への Pkg フィールド追加: Const, TypeName, Var, Func の各構造体に Pkg *Package フィールドが追加されました。これにより、これらのオブジェクトインスタンスが生成される際に、そのオブジェクトがどのパッケージに属しているかという情報が直接埋め込まれるようになります。VarPkg フィールドがパラメータに対して nil となるというコメントは、パラメータが特定のパッケージのトップレベルで宣言されるエンティティではないというGoのセマンティクスを反映しています。
  • newObj 関数の変更: newObj 関数は、ast.Object から types.Object を生成するファクトリ関数です。この関数が pkg *Package 引数を受け取るようになったことで、生成される types.Object に正しいパッケージ情報を付与できるようになりました。assert(pkg != nil) は、この関数が常に有効なパッケージコンテキストで呼び出されることを保証します。

src/pkg/go/types/gcimporter.go

  • 宣言ヘルパー関数の変更: declConst, declTypeName, declVar, declFunc といった関数は、Goのソースコードから定数、型名、変数、関数を宣言する際に使用されます。これらの関数が scope *Scope ではなく pkg *Package を引数として受け取るようになったことで、オブジェクト生成時にそのオブジェクトが属するパッケージを直接指定できるようになりました。これにより、インポートされたパッケージのオブジェクトも正確にそのパッケージに紐付けられるようになります。

src/pkg/go/types/check.go

  • newObj の呼び出し箇所の更新: check.go は型チェッカーの主要なロジックを含むファイルです。ここで newObj が呼び出される際に、現在型チェック中のパッケージを表す check.pkg が引数として渡されるようになりました。これにより、ソースコード内で宣言されるすべてのオブジェクトが、正しく現在のパッケージに紐付けられます。
  • Func オブジェクトの生成: assocMethoddecl 関数内で Func オブジェクトを生成する際にも、明示的に Pkg: check.pkg が設定されるようになりました。これは、メソッドやトップレベル関数が現在のパッケージに属することを保証します。
  • check 関数の戻り値の変更: check 関数の defer 句で pkg = check.pkg が設定されるようになったことで、型チェックの最終結果として返される Package オブジェクトが、check 構造体の pkg フィールドに保持されているものと一致することが保証されます。

src/pkg/go/types/universe.go

  • 事前宣言された定数の初期化: predeclaredConstants の初期化において、Pkg フィールドが明示的に nil に設定されました。これは、true, false, iota, nil といったGoの組み込み定数が、どの特定のパッケージにも属さない「ユニバーススコープ」のオブジェクトであるというGo言語の仕様を反映しています。
  • def 関数の変更: def 関数は、ユニバーススコープや unsafe パッケージのオブジェクトを定義するために使用されます。この関数内で、TypeNameFunc のオブジェクトが unsafe パッケージに属する場合に Pkg フィールドを設定するロジックが追加されました。これは、unsafe パッケージのオブジェクトがユニバーススコープの一部として扱われるものの、特定のパッケージに属するという特殊な性質を持つためです。

これらの変更は、go/types パッケージがGoプログラムのセマンティックな構造をより正確に表現し、型チェックの堅牢性を高めるための重要なステップです。

関連リンク

参考にした情報源リンク