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

[インデックス 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 vetgofmt、様々なIDE、およびその他の静的解析ツールによって、Goコードのセマンティックな意味を理解するために使用されます。

このコミットが行われる前には、主に2つの問題がありました。

  1. QualifiedName における Pkg フィールドの不整合: 現在のパッケージ内の識別子の場合、QualifiedNamePkg フィールドが nil になることが頻繁にありました。これにより、QualifiedName.IsSame メソッドのロジックが複雑になり、nil パッケージを異なる方法で処理する必要があるため、エラーが発生しやすくなっていました。また、パッケージのコンテキストが常に修飾名と共に明示的に伝達されるわけではありませんでした。

  2. 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.Printlog.Print)を区別するために重要です。Pkg フィールドはパッケージへのポインタを保持し、Name フィールドは非修飾名を保持します。

  • スコープ (Scope): Goプログラムにおける名前の可視性(どこでその名前が参照可能か)を定義する領域です。パッケージスコープ、ファイルスコープ、関数スコープなどがあります。型チェッカーは、識別子を解決する際にスコープチェーンを辿ります。

  • インポート (Import): Goプログラムが他のパッケージの機能を利用するために、そのパッケージを現在のファイルに導入するメカニズムです。ドットインポート (import . "pkg") は、インポートされたパッケージのトップレベルの識別子を現在のファイルスコープに直接マージする特殊な形式です。

技術的詳細

このコミットの技術的な詳細は、主に go/types パッケージ内の識別子解決、スコープ管理、および QualifiedName の一貫性に関する改善に焦点を当てています。

  1. QualifiedName の一貫性強化:

    • 以前は、現在のパッケージ内の識別子に対して QualifiedNamePkg フィールドが 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.goQualifiedName.IsSame メソッドのロジックが簡素化されました。以前は非エクスポートされた名前に対して複雑なパス比較を行っていましたが、変更後は ast.IsExported(p.Name) || p.Pkg == q.Pkg という直接的なパッケージポインタの比較に依存できるようになりました。これは、Pkg フィールドが信頼性高く設定されるようになったことを示唆しています。
    • 唯一の例外は、特殊な事前宣言されたメソッドである error.Error です。このメソッドは特定のパッケージに属さないため、src/pkg/go/types/universe.go でその QualifiedNamePkg フィールドが明示的に nil に設定されています。
  2. Context.Ident コールバックのバグ修正と網羅性向上:

    • コミットメッセージには「AST内の types.Object を示すすべての識別子に対して Context.Ident を呼び出す(バグ修正)」と明記されています。
    • src/pkg/go/types/check.gochecker.lookup 関数では、check.register(ident, obj) の呼び出しが、obj が新しく作成され check.objects マップに登録された後に移動されました。check.register 関数は、ast.Identtypes.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 マップを介してシミュレートされる形で)呼び出されたことを検証します。これにより、バグ修正に対する強力な回帰保護が提供されます。
  3. resolve.go のリファクタリング:

    • src/pkg/go/types/resolve.go では、パッケージ解決ロジックが大幅にリファクタリングされました。以前は一時的な pkgNamepkgScope 変数を使用していた箇所が、Package 構造体のフィールド (pkg.Namepkg.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 メソッドの QualifiedNamePkg フィールドが明示的に 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の型チェッカーが識別子をどのように扱い、外部ツールにその情報をどのように提供するかという点にあります。

  1. QualifiedNamePkg フィールドの統一: 以前は、現在のパッケージ内の識別子に対して QualifiedName.Pkgnil になることがあり、これは QualifiedName の比較ロジックを複雑にしていました。このコミットでは、check.goexpr.go のようなファイルで、メソッドやフィールドの QualifiedName を初期化する際に、nil の代わりに常に check.pkg (現在のパッケージ) を明示的に設定するように変更されました。これにより、QualifiedName は常にその識別子が属するパッケージへのポインタを持つようになり、QualifiedName.IsSame メソッドでの比較が p.Pkg == q.Pkg のように単純化されました。これは、型チェッカーが識別子の完全なコンテキストを常に保持し、より正確な比較を可能にすることを示しています。

  2. Context.Ident コールバックの網羅性向上: Context.Ident は、IDEやリンターがGoコードのセマンティックな情報を取得するために非常に重要なコールバックです。このコミット以前は、構造体フィールド、インターフェースメソッド、複合リテラルのキーなど、一部の識別子に対してこのコールバックが呼び出されないというバグがありました。 check.gochecker.lookup 関数における check.register(ident, obj) の呼び出し位置の変更は、この問題を解決する上で重要です。check.register は、ast.Identtypes.Object を関連付け、Context.Ident コールバックをトリガーする役割を担っています。この呼び出しが obj が完全に初期化され、マップに登録された後に移動されたことで、すべての新しい types.Object に対して Ident コールバックが確実に呼び出されるようになりました。 resolver_test.go に追加された新しいテストは、この修正の有効性を検証する上で極めて重要です。このテストは、様々な種類の識別子を含むコードを解析し、Context.Ident がすべての関連する識別子に対して呼び出されたことをプログラム的に確認します。これにより、このバグが将来的に再発するのを防ぐための強力な回帰テストが提供されます。

  3. パッケージ解決ロジックの改善: resolve.go の変更は、パッケージのスコープ管理とインポート処理をより堅牢にするためのものです。一時的な変数ではなく Package 構造体自体のフィールドを直接使用することで、コードの明確性が向上し、パッケージのセマンティックな表現がより一貫性を持つようになりました。特に、パッケージ識別子自体を check.register で登録するようになったことや、ドットインポートのスコープマージロジックの改善は、型チェッカーがGoプログラムの構造をより正確に理解し、処理するために役立ちます。

これらの変更は、Goの型チェッカーの内部的な動作を洗練させ、その結果として、Go開発者が使用する様々なツールがより正確で完全な型情報にアクセスできるようになるという点で、Goエコシステム全体に利益をもたらします。

関連リンク

参考にした情報源リンク