[インデックス 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言語およびコンパイラの基本的な概念を理解しておく必要があります。
-
go/types
パッケージ:- Go言語の標準ライブラリの一部であり、Goプログラムの型チェックを行うためのパッケージです。
- 主な機能は、識別子の解決、型の推論、定数式の評価、そしてGoの型システム全体の表現と管理です。
go/ast
パッケージが提供する抽象構文木(AST)を入力として受け取り、それに意味論的な情報を付与します。
-
型チェック (Type Checking):
- プログラムがGo言語の型規則に準拠しているかを確認するプロセスです。
- コンパイル時に行われ、型互換性の問題や不正な操作を検出することで、実行時エラーを未然に防ぎます。
-
抽象構文木 (AST - Abstract Syntax Tree):
go/ast
パッケージによって生成される、Goソースコードの構造を木構造で表現したものです。- ソースコードの構文的な構造を表現しますが、型情報や識別子の解決といった意味論的な情報は直接持ちません。
-
ast.Object
とtypes.Object
:ast.Object
:go/ast
パッケージにおける、識別子(変数、関数、型など)の宣言を表す汎用的な構造体です。構文解析の段階で作成されます。types.Object
:go/types
パッケージにおける、型チェックされた後の識別子を表すインターフェースです。Const
、TypeName
、Var
、Func
などの具体的な型がこのインターフェースを実装します。ast.Object
からtypes.Object
への変換は、型チェックの過程で行われ、このtypes.Object
には型情報やスコープ情報などの意味論的な情報が付与されます。
-
Universe Block (ユニバースブロック):
- Go言語における最も外側のスコープであり、すべてのGoソースコードを包含します。
true
,false
,iota
,nil
などの事前宣言された定数、int
,string
,bool
などの組み込み型、len
,cap
,make
などの組み込み関数がこのユニバースブロックで定義されています。- これらのオブジェクトは、どのパッケージにも属さない「グローバル」な存在として扱われます。
-
Package
(パッケージ):- Go言語におけるコードの組織単位です。関連する機能や型をまとめるために使用されます。
go/types
パッケージでは、Package
型がGoのパッケージ自体を表し、そのパッケージ内で宣言されたオブジェクトやインポートされたパッケージの情報を管理します。
技術的詳細
このコミットの核心は、go/types
パッケージ内の主要なオブジェクト型 (Const
, TypeName
, Var
, Func
) に Pkg *Package
フィールドを追加し、それらのオブジェクトがどの Package
に属しているかを明示的に保持するようにした点です。
具体的には、以下の変更が行われました。
-
Object
インターフェースの拡張:go/types/objects.go
において、Object
インターフェースにGetPkg() *Package
メソッドが追加されました。これにより、すべてのObject
は自身が属するパッケージを取得できるようになります。
-
オブジェクト型への
Pkg
フィールドの追加:Const
,TypeName
,Var
,Func
の各構造体にPkg *Package
フィールドが追加されました。Var
型のPkg
フィールドには、コメントで「// nil for parameters
」と明記されており、関数パラメータや結果変数についてはnil
となることが示されています。これは、これらの変数が特定のパッケージのトップレベルで宣言されるわけではないためです。
-
GetPkg()
メソッドの実装:- 追加された
Pkg
フィールドに対応するGetPkg()
メソッドが、Const
,TypeName
,Var
,Func
の各型に実装されました。Package
型自体もObject
インターフェースを実装し、GetPkg()
メソッドは自身を返します。
- 追加された
-
オブジェクト生成時の
Pkg
フィールドの設定:newObj
関数(go/types/objects.go
)がpkg *Package
引数を受け取るように変更され、生成されるConst
,TypeName
,Var
,Func
オブジェクトのPkg
フィールドがこの引数で初期化されるようになりました。newObj
関数内ではassert(pkg != nil)
が追加され、Pkg
がnil
でないことが保証されます。これは、newObj
が通常、特定のパッケージのコンテキストで呼び出されることを意味します。
-
宣言ヘルパー関数の変更:
go/types/gcimporter.go
にあるdeclConst
,declTypeName
,declVar
,declFunc
といった宣言ヘルパー関数が、scope *Scope
ではなくpkg *Package
を引数として受け取るように変更されました。これにより、これらの関数がオブジェクトを生成する際に、そのオブジェクトが属するパッケージ情報を直接設定できるようになりました。- これらの関数内では、引数として受け取った
pkg
のScope
を使用してオブジェクトをスコープに挿入します。
-
型チェックロジックの調整:
go/types/check.go
では、newObj
の呼び出し箇所がcheck.pkg
を引数として渡すように変更されました。check.pkg
は、現在型チェック中のパッケージを表します。assocMethod
やdecl
関数内でのFunc
オブジェクトの生成時にもPkg: check.pkg
が明示的に設定されるようになりました。check
関数のdefer
句でpkg = check.pkg
が設定されるようになり、型チェックの結果として得られるパッケージが正しく返されるようになりました。resolve
関数のシグネチャが変更され、pkg *Package
を返さなくなり、代わりにcheck.pkg
フィールドに設定されるようになりました。
-
ユニバースオブジェクトの特殊な扱い:
go/types/universe.go
では、predeclaredConstants
の初期化において、Pkg
フィールドが明示的にnil
に設定されました。これは、true
,false
,iota
,nil
といったユニバースブロックの定数がどの特定のパッケージにも属さないためです。def
関数(ユニバースオブジェクトを定義する関数)では、TypeName
やFunc
のオブジェクトに対して、Unsafe
パッケージに属する場合にPkg
フィールドを設定するロジックが追加されました。これは、unsafe
パッケージのオブジェクトがユニバーススコープの一部として扱われるものの、特定のパッケージに属するという特性を持つためです。
-
テストコードの更新:
go/types/check_test.go
では、ユニバースオブジェクトのPkg
がnil
であることを示すコメントが追加されました。go/types/resolver_test.go
では、テスト内で生成されるVar
やFunc
オブジェクトに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
フィールドが追加されました。これにより、これらのオブジェクトインスタンスが生成される際に、そのオブジェクトがどのパッケージに属しているかという情報が直接埋め込まれるようになります。Var
のPkg
フィールドがパラメータに対して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
オブジェクトの生成:assocMethod
やdecl
関数内で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
パッケージのオブジェクトを定義するために使用されます。この関数内で、TypeName
やFunc
のオブジェクトがunsafe
パッケージに属する場合にPkg
フィールドを設定するロジックが追加されました。これは、unsafe
パッケージのオブジェクトがユニバーススコープの一部として扱われるものの、特定のパッケージに属するという特殊な性質を持つためです。
これらの変更は、go/types
パッケージがGoプログラムのセマンティックな構造をより正確に表現し、型チェックの堅牢性を高めるための重要なステップです。
関連リンク
参考にした情報源リンク
go/types
package role: https://go.dev/blog/go1.5-type-checker- Go type checker design: https://go.googlesource.com/go/+/refs/heads/master/src/go/types/doc.go
- Go universe block predeclared identifiers: https://go.dev/ref/spec#Predeclared_identifiers
- Go AST object relation to go/types: https://pkg.go.dev/go/types#Info
- Go Pkg *Package field in objects go/types: https://pkg.go.dev/go/types#Object.Pkg