[インデックス 14888] ファイルの概要
このコミットは、Go言語の型チェックを行う go/types
パッケージにおける複数のマイナーな修正と改善を目的としています。主な変更点は、QualifiedName
構造体におけるパッケージフィールドの扱いの一貫性向上、および型チェッカーの Context.Ident
コールバックがAST内のすべての関連する識別子に対して適切に呼び出されるようにするバグ修正です。これらの変更は、Goの静的解析ツールやIDEの機能(例えば、「定義へ移動」など)の正確性と信頼性を向上させる上で重要です。
コミット
commit 5e5c0a9fbb3adf832fe5eb7d47883318a73c8400
Author: Robert Griesemer <gri@golang.org>
Date: Mon Jan 14 15:19:32 2013 -0800
go/types: various minor fixes
- always set the Pkg field in QualifiedIdents
- call Context.Ident for all identifiers in the AST that denote
a types.Object (bug fix)
- added test that Context.Ident is called for all such identifiers
R=adonovan
CC=golang-dev
https://golang.org/cl/7101054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5e5c0a9fbb3adf832fe5eb7d47883318a73c8400
元コミット内容
このコミットは、go/types
パッケージに対して以下の修正と改善を行っています。
QualifiedName
構造体内のPkg
フィールドが常に設定されるように変更されました。- AST(抽象構文木)内で
types.Object
を示すすべての識別子に対してContext.Ident
コールバックが呼び出されるようにバグが修正されました。 Context.Ident
がそのようなすべての識別子に対して呼び出されることを検証するテストが追加されました。
変更の背景
go/types
パッケージは、Goプログラムの型チェックを行うためのGoツールチェーンの基盤となる部分です。これは go vet
、gofmt
、様々なIDE、およびその他の静的解析ツールによって、Goコードのセマンティックな意味を理解するために使用されます。
このコミットが行われる前には、主に2つの問題がありました。
-
QualifiedName
におけるPkg
フィールドの不整合: 現在のパッケージ内の識別子の場合、QualifiedName
のPkg
フィールドがnil
になることが頻繁にありました。これにより、QualifiedName.IsSame
メソッドのロジックが複雑になり、nil
パッケージを異なる方法で処理する必要があるため、エラーが発生しやすくなっていました。また、パッケージのコンテキストが常に修飾名と共に明示的に伝達されるわけではありませんでした。 -
Context.Ident
コールバックの不完全な呼び出し:Context.Ident
コールバックは、types.Object
を示すすべての識別子に対して呼び出されていませんでした。これは、IDEやリンターなど、型チェッカーと統合するツールがこのコールバックに依存している場合に、完全な情報を受け取れないというバグでした。具体的には、構造体フィールド、インターフェースメソッド、複合リテラルのキーなどの識別子が欠落していました。
これらの問題は、Goの静的解析の正確性に影響を与え、開発ツールの機能に制限をもたらしていました。このコミットは、これらの根本的な問題を解決し、go/types
パッケージの堅牢性と信頼性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語および go/types
パッケージに関する概念を理解しておく必要があります。
-
AST (Abstract Syntax Tree - 抽象構文木): Goのソースコードを解析して生成されるツリー構造の表現です。コードの構文的な構造を反映しており、各ノードはプログラムの要素(識別子、式、文など)を表します。
ast.Ident
はAST内の識別子(変数名、関数名など)を表すノードです。 -
go/types
パッケージ: Go言語の型システムをモデル化し、Goプログラムの型チェックを行うための標準ライブラリパッケージです。コンパイラ、リンター、IDEなどのツールがGoコードのセマンティックな意味を理解するために利用します。 -
types.Object
:go/types
パッケージにおいて、型チェッカーによって解決された名前付き言語エンティティ(変数、関数、型、パッケージなど)を表す構造体です。ast.Ident
が構文的な表現であるのに対し、types.Object
はその識別子のセマンティックな意味(型、スコープ、値など)をカプセル化します。 -
types.Context
:go/types
パッケージが提供する構造体で、外部ツールが型チェックプロセスにフックするためのメカニズムを提供します。Context
構造体には、型チェック中に特定のイベントが発生したときに呼び出されるコールバック関数(例:Ident
,Expr
)が含まれます。 -
Context.Ident
コールバック:types.Context
のフィールドの一つで、型チェッカーが識別子をtypes.Object
に解決したときに呼び出される関数です。このコールバックは、IDEが「定義へ移動」機能を提供したり、リンターが特定の識別子の使用状況を分析したりするために不可欠です。 -
types.QualifiedName
:go/types
パッケージで定義される構造体で、名前が宣言されたパッケージとその非修飾名を組み合わせて、名前を一意に識別するために使用されます。これは、異なるパッケージに属するが同じ名前を持つエンティティ(例:fmt.Print
とlog.Print
)を区別するために重要です。Pkg
フィールドはパッケージへのポインタを保持し、Name
フィールドは非修飾名を保持します。 -
スコープ (Scope): Goプログラムにおける名前の可視性(どこでその名前が参照可能か)を定義する領域です。パッケージスコープ、ファイルスコープ、関数スコープなどがあります。型チェッカーは、識別子を解決する際にスコープチェーンを辿ります。
-
インポート (Import): Goプログラムが他のパッケージの機能を利用するために、そのパッケージを現在のファイルに導入するメカニズムです。ドットインポート (
import . "pkg"
) は、インポートされたパッケージのトップレベルの識別子を現在のファイルスコープに直接マージする特殊な形式です。
技術的詳細
このコミットの技術的な詳細は、主に go/types
パッケージ内の識別子解決、スコープ管理、および QualifiedName
の一貫性に関する改善に焦点を当てています。
-
QualifiedName
の一貫性強化:- 以前は、現在のパッケージ内の識別子に対して
QualifiedName
のPkg
フィールドがnil
に設定されることがありました。このコミットでは、src/pkg/go/types/check.go
およびsrc/pkg/go/types/expr.go
内のQualifiedName{nil, name}
のような初期化をQualifiedName{check.pkg, name}
に変更することで、このフィールドが常に現在のパッケージ (check.pkg
) に明示的に設定されるようにしました。 - これにより、
src/pkg/go/types/types.go
のQualifiedName.IsSame
メソッドのロジックが簡素化されました。以前は非エクスポートされた名前に対して複雑なパス比較を行っていましたが、変更後はast.IsExported(p.Name) || p.Pkg == q.Pkg
という直接的なパッケージポインタの比較に依存できるようになりました。これは、Pkg
フィールドが信頼性高く設定されるようになったことを示唆しています。 - 唯一の例外は、特殊な事前宣言されたメソッドである
error.Error
です。このメソッドは特定のパッケージに属さないため、src/pkg/go/types/universe.go
でそのQualifiedName
のPkg
フィールドが明示的にnil
に設定されています。
- 以前は、現在のパッケージ内の識別子に対して
-
Context.Ident
コールバックのバグ修正と網羅性向上:- コミットメッセージには「AST内の
types.Object
を示すすべての識別子に対してContext.Ident
を呼び出す(バグ修正)」と明記されています。 src/pkg/go/types/check.go
のchecker.lookup
関数では、check.register(ident, obj)
の呼び出しが、obj
が新しく作成されcheck.objects
マップに登録された後に移動されました。check.register
関数は、ast.Ident
をtypes.Object
に関連付け、設定されていればcheck.Context.Ident
コールバックを呼び出す役割を担っています。この変更により、新しく作成されたすべてのオブジェクトに対してregister
が常に呼び出されるようになり、Ident
コールバックの網羅性が向上します。- このバグ修正の最も重要な側面は、
src/pkg/go/types/resolver_test.go
に追加された広範な新しいテストケースです。このテストは、構造体フィールド、インターフェースメソッド、複合リテラルのキーなど、以前はContext.Ident
が呼び出されなかった可能性のある様々な種類の識別子を含むGoコードスニペットを作成します。ast.Inspect
を使用してASTを走査し、すべてのast.Ident
(_
と.
を除く)がtypes.Object
に解決され、対応するIdent
コールバックが(idents
マップを介してシミュレートされる形で)呼び出されたことを検証します。これにより、バグ修正に対する強力な回帰保護が提供されます。
- コミットメッセージには「AST内の
-
resolve.go
のリファクタリング:src/pkg/go/types/resolve.go
では、パッケージ解決ロジックが大幅にリファクタリングされました。以前は一時的なpkgName
とpkgScope
変数を使用していた箇所が、Package
構造体のフィールド (pkg.Name
とpkg.Scope
) を直接使用するように変更されました。これにより、コードがよりクリーンになり、不整合が発生しにくくなります。check.register(file.Name, pkg)
が追加され、パッケージ識別子自体も登録されるようになりました。- インポートの処理、特にドットインポート (
import . "pkg"
) のロジックが洗練され、インポートされたスコープがファイルスコープとパッケージスコープに正しくマージされるようになりました。 pkg.Scope.Outer
の設定とリセットのロジックも改善され、インポートエラー処理中のスコープの整合性が保たれるようになりました。
これらの変更は、go/types
パッケージの内部的な堅牢性を高め、Goの型システムをより正確に反映し、その上に構築されるツールがより信頼性の高い情報を受け取れるようにすることに貢献しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに分散しています。
-
src/pkg/go/types/api.go
:Context.Ident
コールバックのドキュメントが更新され、いつ呼び出され、いつ呼び出されないかがより明確に記述されました。--- a/src/pkg/go/types/api.go +++ b/src/pkg/go/types/api.go @@ -25,9 +25,14 @@ type Context struct { // filename:line:column: message. Error func(err error) - // If Ident is not nil, it is called for each identifier - // id that is type-checked: obj is the object denoted by - // the identifier. + // If Ident is not nil, it is called for each identifier id + // denoting an Object in the files provided to Check, and + // obj is the denoted object. + // Ident is not called for fields and methods in struct or + // interface types or composite literals, or for blank (_) + // or dot (.) identifiers in dot-imports. + // TODO(gri) Consider making Fields and Methods ordinary + // Objects - than we could lift this restriction. Ident func(id *ast.Ident, obj Object) // If Expr is not nil, it is called for each expression x that is
-
src/pkg/go/types/check.go
:checker.lookup
関数内でcheck.register(ident, obj)
の呼び出し位置が変更され、obj
がマップに登録された後に常に呼び出されるようになりました。また、メソッドのQualifiedName
初期化でnil
ではなくcheck.pkg
が使用されるようになりました。--- a/src/pkg/go/types/check.go +++ b/src/pkg/go/types/check.go @@ -70,9 +70,9 @@ func (check *checker) lookup(ident *ast.Ident) Object { if obj = check.objects[astObj]; obj == nil { obj = newObj(astObj) - check.register(ident, obj) check.objects[astObj] = obj } + check.register(ident, obj) return obj } @@ -256,7 +256,7 @@ func (check *checker) object(obj Object, cycleOk bool) { params, _ := check.collectParams(m.decl.Recv, false) sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter m.Type = sig - methods = append(methods, &Method{QualifiedName{nil, m.Name}, sig}) + methods = append(methods, &Method{QualifiedName{check.pkg, m.Name}, sig}) check.later(m, sig, m.decl.Body) } typ.Methods = methods
-
src/pkg/go/types/expr.go
: メソッド、フィールド、匿名フィールドのQualifiedName
初期化において、nil
ではなくcheck.pkg
が使用されるようになりました。--- a/src/pkg/go/types/expr.go +++ b/src/pkg/go/types/expr.go @@ -84,7 +84,7 @@ func (check *checker) collectMethods(list *ast.FieldList) (methods []*Method) { continue } for _, name := range f.Names { - methods = append(methods, &Method{QualifiedName{nil, name.Name}, sig}) + methods = append(methods, &Method{QualifiedName{check.pkg, name.Name}, sig}) } } else { // embedded interface @@ -137,15 +137,15 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [ if len(f.Names) > 0 { // named fields for _, name := range f.Names { - fields = append(fields, &Field{QualifiedName{nil, name.Name}, typ, tag, false}) + fields = append(fields, &Field{QualifiedName{check.pkg, name.Name}, typ, tag, false}) } } else { // anonymous field switch t := deref(typ).(type) { case *Basic: - fields = append(fields, &Field{QualifiedName{nil, t.Name}, typ, tag, true}) + fields = append(fields, &Field{QualifiedName{check.pkg, t.Name}, typ, tag, true}) case *NamedType: - fields = append(fields, &Field{QualifiedName{nil, t.Obj.GetName()}, typ, tag, true}) + fields = append(fields, &Field{QualifiedName{check.pkg, t.Obj.GetName()}, typ, tag, true}) default: if typ != Typ[Invalid] { check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ) @@ -902,7 +902,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle if x.mode == invalid { goto Error } - mode, typ := lookupField(x.typ, QualifiedName{nil, sel}) + mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel}) if mode == invalid { check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel) goto Error
-
src/pkg/go/types/resolve.go
: パッケージスコープとインポート処理がリファクタリングされ、pkg.Scope
が直接使用されるようになりました。また、パッケージ識別子自体を登録するcheck.register(file.Name, pkg)
が追加されました。--- a/src/pkg/go/types/resolve.go +++ b/src/pkg/go/types/resolve.go @@ -37,18 +37,17 @@ func (check *checker) resolveIdent(scope *Scope, ident *ast.Ident) bool { } func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.FuncDecl) { - // complete package scope - pkgName := "" - pkgScope := &Scope{Outer: Universe} + pkg = &Package{Scope: &Scope{Outer: Universe}, Imports: make(map[string]*Package)} + // complete package scope i := 0 for _, file := range check.files { // package names must match switch name := file.Name.Name; { - case pkgName == "": - pkgName = name - case name != pkgName: - check.errorf(file.Package, "package %s; expected %s", name, pkgName) + case pkg.Name == "": + pkg.Name = name + case name != pkg.Name: + check.errorf(file.Package, "package %s; expected %s", name, pkg.Name) continue // ignore this file } @@ -56,6 +55,9 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F check.files[i] = file i++ + // the package identifier denotes the current package + check.register(file.Name, pkg) + // insert top-level file objects in package scope // (the parser took care of declaration errors) for _, decl := range file.Decls { @@ -75,13 +77,13 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F if name.Name == "_" { continue } - pkgScope.Insert(check.lookup(name)) + pkg.Scope.Insert(check.lookup(name)) } case *ast.TypeSpec: if s.Name.Name == "_" { continue } - pkgScope.Insert(check.lookup(s.Name)) + pkg.Scope.Insert(check.lookup(s.Name)) default: check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) } @@ -95,7 +97,7 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F if d.Name.Name == "_" || d.Name.Name == "init" { continue // blank (_) and init functions are inaccessible } - pkgScope.Insert(check.lookup(d.Name)) + pkg.Scope.Insert(check.lookup(d.Name)) default: check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) } @@ -103,21 +105,18 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F } check.files = check.files[0:i] - // package global mapping of imported package ids to package objects - imports := make(map[string]*Package) - // complete file scopes with imports and resolve identifiers for _, file := range check.files { // build file scope by processing all imports importErrors := false - fileScope := &Scope{Outer: pkgScope} + fileScope := &Scope{Outer: pkg.Scope} for _, spec := range file.Imports { if importer == nil { importErrors = true continue } path, _ := strconv.Unquote(spec.Path.Value) - pkg, err := importer(imports, path) + imp, err := importer(pkg.Imports, path) if err != nil { check.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) importErrors = true @@ -128,7 +127,7 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F // import failed. Consider adjusting the logic here a bit. // local name overrides imported package name - name := pkg.Name + name := imp.Name if spec.Name != nil { name = spec.Name.Name } @@ -136,16 +135,20 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F // add import to file scope if name == "." { // merge imported scope with file scope - for _, obj := range pkg.Scope.Entries { - check.declareObj(fileScope, pkgScope, obj) + for _, obj := range imp.Scope.Entries { + check.declareObj(fileScope, pkg.Scope, obj) } + // TODO(gri) consider registering the "." identifier + // if we have Context.Ident callbacks for say blank + // (_) identifiers + // check.register(spec.Name, pkg) } else if name != "_" { // declare imported package object in file scope - // (do not re-use pkg in the file scope but create + // (do not re-use imp in the file scope but create // a new object instead; the Decl field is different // for different files) - obj := &Package{Name: name, Scope: pkg.Scope, spec: spec} - check.declareObj(fileScope, pkgScope, obj) + obj := &Package{Name: name, Scope: imp.Scope, spec: spec} + check.declareObj(fileScope, pkg.Scope, obj) } } @@ -155,7 +158,7 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F // (objects in the universe may be shadowed by imports; // with missing imports, identifiers might get resolved // incorrectly to universe objects) - pkgScope.Outer = nil + pkg.Scope.Outer = nil } i := 0 for _, ident := range file.Unresolved { @@ -167,8 +170,8 @@ func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.F } file.Unresolved = file.Unresolved[0:i] - pkgScope.Outer = Universe // reset outer scope + pkg.Scope.Outer = Universe // reset outer scope (is nil if there were importErrors) } - return &Package{Name: pkgName, Scope: pkgScope, Imports: imports}, methods + return }
-
src/pkg/go/types/resolver_test.go
:TestResolveQualifiedIdents
に、Context.Ident
コールバックが構造体フィールド、インターフェースメソッド、複合リテラルのキーなど、すべての関連する識別子に対して呼び出されることを検証する新しいテストロジックが追加されました。--- a/src/pkg/go/types/resolver_test.go +++ b/src/pkg/go/types/resolver_test.go @@ -12,7 +12,8 @@ import ( ) var sources = []string{ - `package p + ` + package p import "fmt" import "math" const pi = math.Pi @@ -21,16 +22,27 @@ var sources = []string{ } var Println = fmt.Println `,\n- `package p + ` + package p import "fmt" func f() string { + _ = "foo" return fmt.Sprintf("%d", g()) } func g() (x int) { return } `,\n- `package p + ` + package p import . "go/parser" - func g() Mode { return ImportsOnly }`,\n+ import "sync" + func g() Mode { return ImportsOnly } + var _, x int = 1, 2 + func init() {} + type T struct{ sync.Mutex; a, b, c int} + type I interface{ m() } + var _ = T{a: 1, b: 2, c: 3} + func (_ T) m() {} + `,\n }\n \n var pkgnames = []string{ @@ -94,4 +106,62 @@ func TestResolveQualifiedIdents(t *testing.T) { return true })\n }\n +\n + // Currently, the Check API doesn't call Ident for fields, methods, and composite literal keys.\n + // Introduce them artifically so that we can run the check below.\n + for _, f := range files {\n + ast.Inspect(f, func(n ast.Node) bool {\n + switch x := n.(type) {\n + case *ast.StructType:\n + for _, list := range x.Fields.List {\n + for _, f := range list.Names {\n + assert(idents[f] == nil)\n + idents[f] = &Var{Name: f.Name}\n + }\n + }\n + case *ast.InterfaceType:\n + for _, list := range x.Methods.List {\n + for _, f := range list.Names {\n + assert(idents[f] == nil)\n + idents[f] = &Func{Name: f.Name}\n + }\n + }\n + case *ast.CompositeLit:\n + for _, e := range x.Elts {\n + if kv, ok := e.(*ast.KeyValueExpr); ok {\n + if k, ok := kv.Key.(*ast.Ident); ok {\n + assert(idents[k] == nil)\n + idents[k] = &Var{Name: k.Name}\n + }\n + }\n + }\n + }\n + return true\n + })\n + }\n +\n + // check that each identifier in the source is enumerated by the Context.Ident callback\n + for _, f := range files {\n + ast.Inspect(f, func(n ast.Node) bool {\n + if x, ok := n.(*ast.Ident); ok && x.Name != "_" && x.Name != "." {\n + obj := idents[x]\n + if obj == nil {\n + t.Errorf("%s: unresolved identifier %s", fset.Position(x.Pos()), x.Name)\n + } else {\n + delete(idents, x)\n + }\n + return false\n + }\n + return true\n + })\n + }\n +\n + // TODO(gri) enable code below\n + // At the moment, the type checker introduces artifical identifiers which are not\n + // present in the source. Once it doesn't do that anymore, enable the checks below.\n + /*\n + \tfor x := range idents {\n + \t\tt.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name)\n + \t}\n + */\n }\n ```
-
src/pkg/go/types/types.go
:QualifiedName
構造体のPkg
フィールドのコメントが更新され、error.Error
の場合のみnil
になることが明記されました。また、QualifiedName.IsSame
メソッドのロジックが簡素化されました。--- a/src/pkg/go/types/types.go +++ b/src/pkg/go/types/types.go @@ -90,7 +90,7 @@ type Slice struct { // A QualifiedName is a name qualified with the package that declared the name.\n type QualifiedName struct { - Pkg *Package // nil for current (non-imported) package + Pkg *Package // nil only for predeclared error.Error Name string // unqualified type name for anonymous fields } @@ -104,19 +104,7 @@ func (p QualifiedName) IsSame(q QualifiedName) bool { 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 + return ast.IsExported(p.Name) || p.Pkg == q.Pkg } // A Field represents a field of a struct.\n ```
-
src/pkg/go/types/universe.go
:error.Error
メソッドのQualifiedName
のPkg
フィールドが明示的にnil
に設定されました。--- a/src/pkg/go/types/universe.go +++ b/src/pkg/go/types/universe.go @@ -97,7 +97,8 @@ func init() { // error type { - err := &Method{QualifiedName{Name: "Error"}, &Signature{Results: []*Var{{Name: "", Type: Typ[String]}}}} + // Error has a nil package in its qualified name since it is in no package + err := &Method{QualifiedName{nil, "Error"}, &Signature{Results: []*Var{{Name: "", Type: Typ[String]}}}} def(&TypeName{Name: "error", Type: &NamedType{Underlying: &Interface{Methods: []*Method{err}}}})\n }
コアとなるコードの解説
このコミットの核心は、Goの型チェッカーが識別子をどのように扱い、外部ツールにその情報をどのように提供するかという点にあります。
-
QualifiedName
のPkg
フィールドの統一: 以前は、現在のパッケージ内の識別子に対してQualifiedName.Pkg
がnil
になることがあり、これはQualifiedName
の比較ロジックを複雑にしていました。このコミットでは、check.go
やexpr.go
のようなファイルで、メソッドやフィールドのQualifiedName
を初期化する際に、nil
の代わりに常にcheck.pkg
(現在のパッケージ) を明示的に設定するように変更されました。これにより、QualifiedName
は常にその識別子が属するパッケージへのポインタを持つようになり、QualifiedName.IsSame
メソッドでの比較がp.Pkg == q.Pkg
のように単純化されました。これは、型チェッカーが識別子の完全なコンテキストを常に保持し、より正確な比較を可能にすることを示しています。 -
Context.Ident
コールバックの網羅性向上:Context.Ident
は、IDEやリンターがGoコードのセマンティックな情報を取得するために非常に重要なコールバックです。このコミット以前は、構造体フィールド、インターフェースメソッド、複合リテラルのキーなど、一部の識別子に対してこのコールバックが呼び出されないというバグがありました。check.go
のchecker.lookup
関数におけるcheck.register(ident, obj)
の呼び出し位置の変更は、この問題を解決する上で重要です。check.register
は、ast.Ident
とtypes.Object
を関連付け、Context.Ident
コールバックをトリガーする役割を担っています。この呼び出しがobj
が完全に初期化され、マップに登録された後に移動されたことで、すべての新しいtypes.Object
に対してIdent
コールバックが確実に呼び出されるようになりました。resolver_test.go
に追加された新しいテストは、この修正の有効性を検証する上で極めて重要です。このテストは、様々な種類の識別子を含むコードを解析し、Context.Ident
がすべての関連する識別子に対して呼び出されたことをプログラム的に確認します。これにより、このバグが将来的に再発するのを防ぐための強力な回帰テストが提供されます。 -
パッケージ解決ロジックの改善:
resolve.go
の変更は、パッケージのスコープ管理とインポート処理をより堅牢にするためのものです。一時的な変数ではなくPackage
構造体自体のフィールドを直接使用することで、コードの明確性が向上し、パッケージのセマンティックな表現がより一貫性を持つようになりました。特に、パッケージ識別子自体をcheck.register
で登録するようになったことや、ドットインポートのスコープマージロジックの改善は、型チェッカーがGoプログラムの構造をより正確に理解し、処理するために役立ちます。
これらの変更は、Goの型チェッカーの内部的な動作を洗練させ、その結果として、Go開発者が使用する様々なツールがより正確で完全な型情報にアクセスできるようになるという点で、Goエコシステム全体に利益をもたらします。
関連リンク
参考にした情報源リンク
- golang/go GitHub Repository
- Go Code Review Comments (golang.org/cl/7101054) - このコミットの変更リスト(CL)ページ。詳細な議論やレビューコメントが含まれている可能性があります。