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

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

このコミットは、Go言語の標準ライブラリの一部であるgo/typesパッケージに対する変更です。go/typesパッケージは、Goプログラムの型チェックとセマンティック分析を行うための重要なコンポーネントです。このコミットでは、主に以下のファイルが変更されています。

  • src/pkg/go/types/check.go: 型チェックの主要ロジックとエラーハンドリング。
  • src/pkg/go/types/errors.go: 型チェックエラーの報告とフォーマット。
  • src/pkg/go/types/expr.go: 式の型チェック。
  • src/pkg/go/types/gcimporter.go: gcコンパイラによって生成されたバイナリから型情報をインポートするロジック。
  • src/pkg/go/types/predicates.go: 型に関する述語(例: 型が同一かどうかのチェック)。
  • src/pkg/go/types/stmt.go: ステートメントの型チェック。
  • src/pkg/go/types/types.go: go/typesパッケージの主要な型定義。
  • src/pkg/go/types/universe.go: 組み込み型やユニバーススコープの定義。

これらの変更は、go/typesパッケージの内部APIからgo/astパッケージのast.Object型への依存を減らし、よりクリーンで独立した型システムを構築することを目的としています。また、エラーハンドリングに関するバグ修正も含まれています。

コミット

go/typesパッケージの公開APIからast.Objectのほとんどの残りの使用箇所を削除しました。 また、エラーハンドラがコンテキストに設定されているかどうかにかかわらず、最初のエラーを返すように修正しました(バグ修正)。

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

https://github.com/golang/go/commit/a846d479b319c54780ce83c94d7f7b5cc28b94c2

元コミット内容

go/types: remove most remaining uses of ast.Object from exported API

Also: Return first error whether an error handler is set in the
      context or not (bug fix).

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

変更の背景

このコミットの主な背景は、Go言語のコンパイラツールチェーンにおけるモジュール間の依存関係の整理と、より堅牢なエラーハンドリングの実現です。

  1. go/typesgo/astの分離: Go言語のツールチェーンでは、ソースコードはまずgo/parserによって抽象構文木(AST)にパースされ、そのASTがgo/typesパッケージによって型チェックされます。初期の実装では、go/typesパッケージの内部構造やAPIがgo/astパッケージのast.Object型に直接依存している部分が多く存在しました。 しかし、理想的には、型システムはASTの具体的な表現から独立しているべきです。これにより、go/typesパッケージはAST以外のソース(例えば、コンパイル済みバイナリからの型情報)からも型チェックを行えるようになり、モジュール性が向上し、将来的な拡張性やパフォーマンス改善の余地が生まれます。ast.Objectへの依存を減らすことで、go/typesパッケージはより抽象的な「オブジェクト」の概念(名前と型を持つエンティティ)を独自に定義し、ASTの具体的なノードに縛られずに型情報を扱うことができるようになります。これは、コンパイラのフロントエンドとバックエンドの間の明確な分離を促進する一般的な設計原則です。

  2. エラーハンドリングの改善(バグ修正): go/typesパッケージは、型チェック中に発生したエラーを報告するメカニズムを持っています。このコミット以前は、エラーハンドラが設定されているかどうかに応じて、最初のエラーの扱いが異なるというバグがありました。これは、型チェックのプロセスが途中でパニック(panic)し、そのパニックがrecoverされる際に、エラーハンドラの有無によって最終的に返されるエラーが意図せず変わってしまう可能性があったことを示唆しています。このバグは、型チェックの結果の一貫性を損なうものであり、修正が必要でした。

これらの変更は、Goコンパイラの内部構造をよりクリーンで保守しやすいものにし、将来的な開発の基盤を強化することを目的としています。

前提知識の解説

Go言語のgo/astパッケージ

go/astパッケージは、Go言語のソースコードを抽象構文木(Abstract Syntax Tree, AST)として表現するためのデータ構造と関数を提供します。ASTは、プログラムの構造を木構造で表現したもので、コンパイラやリンター、コード分析ツールなどがソースコードを理解し、操作するために利用します。

  • ast.Node: ASTのすべてのノードが実装するインターフェース。
  • ast.Expr: 式を表すノード。
  • ast.Stmt: ステートメントを表すノード。
  • ast.Decl: 宣言を表すノード。
  • ast.Object: 識別子(変数、関数、型など)の宣言を表す汎用的な構造体。名前、種類(変数、関数など)、宣言された場所などの情報を含みます。ast.Objectは、AST内の異なるノード間でシンボル解決を行うためのリンケージを提供します。

Go言語のgo/typesパッケージ

go/typesパッケージは、Go言語のプログラムに対して型チェックとセマンティック分析を行うためのパッケージです。これは、Goコンパイラの重要な部分であり、プログラムがGo言語の型規則に準拠していることを保証します。

  • 型チェック: 変数の使用、関数の呼び出し、演算子の適用などが、定義された型と互換性があるかを検証します。
  • セマンティック分析: プログラムの意味論的な正当性を検証します。例えば、未宣言の変数の使用、到達不能なコード、インポートされたパッケージの解決などを行います。
  • 型システム: go/typesは独自の型システムを定義しており、ast.ExprなどのASTノードから独立した形で型情報を表現します。

ast.Objectgo/typesの関連性

go/typesパッケージは、ASTを走査しながら型チェックを行うため、go/astパッケージの構造体と密接に連携します。特に、識別子(変数名、関数名など)の型情報を管理する際に、ast.Objectが利用されることがありました。しかし、ast.ObjectはASTの具体的なノードに紐づくため、go/typesがより抽象的な型システムを構築する上で、直接的な依存は望ましくありません。このコミットは、その依存を解消し、go/typesが独自の「シンボル」表現を持つようにする一環です。

型システムとASTの分離の重要性

型システムとASTの分離は、コンパイラ設計における重要な原則です。

  • モジュール性: 型システムがASTの具体的な表現から独立することで、型チェックロジックをASTの変更から隔離できます。これにより、ASTの変更が型チェッカーに与える影響を最小限に抑えられます。
  • 再利用性: 型システムをASTから分離することで、異なるフロントエンド(例えば、異なる言語のパーサーや、コンパイル済みバイナリからの情報)からでも同じ型チェッカーを利用できるようになります。
  • パフォーマンス: ASTノードは多くの情報を持つため、型情報だけが必要な場合にASTノード全体を扱うのは非効率な場合があります。より軽量な独自の型表現を用いることで、パフォーマンスが向上する可能性があります。
  • 抽象化: 型システムは、プログラムのセマンティクス(意味)を扱うべきであり、その構文(AST)の具体的な詳細に縛られるべきではありません。分離することで、より高レベルな抽象化が可能になります。

Go言語のエラーハンドリング(panicrecover

Go言語では、エラーは通常、多値戻り値(error型)で扱われますが、回復不可能な例外的な状況ではpanicrecoverが使用されます。

  • panic: プログラムの実行を中断し、現在のゴルーチンのスタックをアンワインドします。
  • recover: defer関数内で呼び出されると、panicからの回復を試み、パニックに渡された値を返します。recovernilを返した場合、パニックは発生していません。

go/typesパッケージのような複雑なシステムでは、内部的な整合性チェックや予期せぬ状態を検出するためにpanicが使用されることがあります。このコミットでは、check.gocheck関数内でpanicrecoverするロジックがあり、その中でエラーハンドリングのバグが修正されています。

技術的詳細

このコミットの技術的詳細は、主にgo/typesパッケージ内でのast.Objectから新しいVar型への移行、およびエラーハンドリングの修正に集約されます。

1. ast.Objectから*Varへの移行

最も重要な変更は、go/typesパッケージの内部および公開APIで、go/astパッケージのast.Object型への依存を減らし、代わりにgo/typesパッケージ内で新しく定義されたVar型を使用する点です。

  • Var構造体の導入: src/pkg/go/types/types.goに新しい構造体Varが導入されました。

    // A Variable represents a variable (including function parameters and results).
    type Var struct {
    	Name string
    	Type Type
    }
    

    このVar構造体は、変数、関数パラメータ、関数結果など、名前と型を持つエンティティを表現するためのものです。これにより、go/typesパッケージはast.Objectに依存することなく、独自のセマンティックなオブジェクト表現を持つことができます。

  • Signature構造体の変更: 関数のシグネチャを表すSignature構造体も変更されました。

    --- a/src/pkg/go/types/types.go
    +++ b/src/pkg/go/types/types.go
    @@ -120,19 +120,25 @@ type Pointer struct {
     	Base Type
     }
    
    +// A Variable represents a variable (including function parameters and results).
    +type Var struct {
    +	Name string
    +	Type Type
    +}
    +
     // A Result represents a (multi-value) function call result.
     type Result struct {
     	implementsType
    -	Values []*ast.Object // Signature.Results of the function called
    +	Values []*Var // Signature.Results of the function called
     }
    
     // A Signature represents a user-defined function type func(...) (...).
     type Signature struct {
     	implementsType
    -	Recv       *ast.Object   // nil if not a method
    -	Params     []*ast.Object // (incoming) parameters from left to right; or nil
    -	Results    []*ast.Object // (outgoing) results from left to right; or nil
    -	IsVariadic bool          // true if the last parameter's type is of the form ...T
    +	Recv       *Var   // nil if not a method
    +	Params     []*Var // (incoming) parameters from left to right; or nil
    +	Results    []*Var // (outgoing) results from left to right; or nil
    +	IsVariadic bool   // true if the last parameter's type is of the form ...T
     }
    

    Recv (レシーバ)、Params (パラメータ)、Results (結果) の各フィールドが、*ast.Objectのスライスから*Varのスライスに変更されました。これにより、関数のシグネチャがASTの具体的な表現から完全に独立しました。

  • Result構造体の変更: 関数の結果を表すResult構造体も同様に、Valuesフィールドが*ast.Objectのスライスから*Varのスライスに変更されました。

  • 関連する関数のシグネチャ変更: writeParams (errors.go)、collectParams (expr.go)、parseParameter (gcimporter.go)、parseParameters (gcimporter.go)、parseSignature (gcimporter.go)、identicalTypes (predicates.go) など、ast.Objectを引数や戻り値として使用していた多くの関数が、*Varを使用するようにシグネチャが変更されました。

  • gcimporter.goでの変更: gcimporter.goは、コンパイル済みGoバイナリから型情報をインポートする役割を担っています。このファイルでは、パラメータや結果の解析時にast.NewObjを呼び出してast.Objectを作成する代わりに、直接&Var{name, typ}のような形でVar構造体を生成するように変更されました。これにより、インポートされた型情報もast.Objectに依存しない形で表現されるようになります。

  • predicates.goからのgo/astのインポート削除: src/pkg/go/types/predicates.goからimport "go/ast"が削除されました。これは、このファイルがast.Objectに直接依存しなくなったことを明確に示しています。

2. エラーハンドリングのバグ修正

src/pkg/go/types/check.gocheck関数におけるpanic/recoverロジックが修正されました。

--- a/src/pkg/go/types/check.go
+++ b/src/pkg/go/types/check.go
@@ -379,10 +379,8 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
 	// handle panics
 	defer func() {
 		switch p := recover().(type) {
-		case nil:
-			// normal return - nothing to do
-		case bailout:
-			// early exit
+		case nil, bailout:
+			// normal return or early exit
 			err = check.firsterr
 		default:
 			// unexpected panic: don't crash clients

以前のコードでは、recover()nilを返した場合(パニックが発生しなかった場合)と、bailout型のパニック(早期終了を示す内部的なパニック)の場合で、err = check.firsterrの代入が行われるかどうかが異なっていました。この修正により、nilまたはbailoutのいずれの場合でもerr = check.firsterrが実行されるようになりました。これにより、型チェック中に発生した最初のエラーが、エラーハンドラの有無にかかわらず、常に適切に返されることが保証されます。これは、型チェック結果の一貫性を保つための重要なバグ修正です。

まとめ

これらの変更は、go/typesパッケージをgo/astパッケージからより独立させ、Goコンパイラの型チェック部分の設計を改善することを目的としています。Var型の導入は、型システムが独自のセマンティックなオブジェクト表現を持つための基盤を築き、将来的なコンパイラの進化に貢献します。エラーハンドリングの修正は、システムの堅牢性と予測可能性を高めます。

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

src/pkg/go/types/types.go (Var構造体の導入と主要な型定義の変更)

--- a/src/pkg/go/types/types.go
+++ b/src/pkg/go/types/types.go
@@ -120,19 +120,25 @@ type Pointer struct {
 	Base Type
 }

+// A Variable represents a variable (including function parameters and results).
+type Var struct {
+	Name string
+	Type Type
+}
+
 // A Result represents a (multi-value) function call result.
 type Result struct {
 	implementsType
-	Values []*ast.Object // Signature.Results of the function called
+	Values []*Var // Signature.Results of the function called
 }

 // A Signature represents a user-defined function type func(...) (...).
 type Signature struct {
 	implementsType
-	Recv       *ast.Object   // nil if not a method
-	Params     []*ast.Object // (incoming) parameters from left to right; or nil
-	Results    []*ast.Object // (outgoing) results from left to right; or nil
-	IsVariadic bool          // true if the last parameter's type is of the form ...T
+	Recv       *Var   // nil if not a method
+	Params     []*Var // (incoming) parameters from left to right; or nil
+	Results    []*Var // (outgoing) results from left to right; or nil
+	IsVariadic bool   // true if the last parameter's type is of the form ...T
 }

src/pkg/go/types/errors.go (writeParams関数のシグネチャ変更)

--- a/src/pkg/go/types/errors.go
+++ b/src/pkg/go/types/errors.go
@@ -197,7 +197,7 @@ func typeString(typ Type) string {
 	return buf.String()
 }

-func writeParams(buf *bytes.Buffer, params []*ast.Object, isVariadic bool) {
+func writeParams(buf *bytes.Buffer, params []*Var, isVariadic bool) {
 	buf.WriteByte('(')
 	for i, par := range params {
 		if i > 0 {
@@ -210,7 +210,7 @@ func writeParams(buf *bytes.Buffer, params []*ast.Object, isVariadic bool) {
 		if isVariadic && i == len(params)-1 {
 			buf.WriteString("...")
 		}
-		writeType(buf, par.Type.(Type))
+		writeType(buf, par.Type)
 	}
 	buf.WriteByte(')')
 }

src/pkg/go/types/expr.go (collectParams関数のシグネチャ変更と内部ロジック)

--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -17,12 +17,13 @@ import (
 // - simplify invalid handling: maybe just use Typ[Invalid] as marker, get rid of invalid Mode for values?
 // - rethink error handling: should all callers check if x.mode == valid after making a call?
 // - at the moment, iota is passed around almost everywhere - in many places we know it cannot be used
+// - use "" or "_" consistently for anonymous identifiers? (e.g. reeceivers that have no name)

 // TODO(gri) API issues
 // - clients need access to builtins type information
 // - API tests are missing (e.g., identifiers should be handled as expressions in callbacks)

-func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*ast.Object, isVariadic bool) {
+func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) {
 	if list == nil {
 		return
 	}
@@ -46,26 +47,22 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param
 			for _, name := range field.Names {
 				obj := name.Obj
 				obj.Type = typ
-				params = append(params, obj)
 				last = obj
+				params = append(params, &Var{obj.Name, typ})
 			}
 		} else {
 			// anonymous parameter
 			obj := ast.NewObj(ast.Var, "")
 			obj.Type = typ
-			params = append(params, obj)
 			last = obj
+			params = append(params, &Var{obj.Name, typ})
 		}
 	}
 	// For a variadic function, change the last parameter's object type
 	// from T to []T (this is the type used inside the function), but
-	// keep a copy of the object with the original type T in the params
-	// list (this is the externally visible type).
+	// keep the params list unchanged (this is the externally visible type).
 	if isVariadic {
-		// if isVariadic is set, last must exist and len(params) > 0
-		copy := *last
 		last.Type = &Slice{Elt: last.Type.(Type)}
-		params[len(params)-1] = &copy
 	}
 	return
 }
@@ -576,7 +573,7 @@ func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota
 //
 func (check *checker) argument(sig *Signature, i int, arg ast.Expr, x *operand, passSlice bool) {
 	// determine parameter
-	var par *ast.Object
+	var par *Var
 	n := len(sig.Params)
 	if i < n {
 		par = sig.Params[i]
@@ -922,11 +919,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
 			// argument of the method expression's function type
 			// TODO(gri) at the moment, method sets don't correctly track
 			// pointer vs non-pointer receivers => typechecker is too lenient
-			arg := ast.NewObj(ast.Var, "")
-			arg.Type = x.typ
 			x.mode = value
 			x.typ = &Signature{
-				Params:     append([]*ast.Object{arg}, sig.Params...),\
+				Params:     append([]*Var{{"", x.typ}}, sig.Params...),\
 				Results:    sig.Results,\
 				IsVariadic: sig.IsVariadic,\
 			}

src/pkg/go/types/check.go (エラーハンドリングのバグ修正)

--- a/src/pkg/go/types/check.go
+++ b/src/pkg/go/types/check.go
@@ -379,10 +379,8 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
 	// handle panics
 	defer func() {
 		switch p := recover().(type) {
-		case nil:
-			// normal return - nothing to do
-		case bailout:
-			// early exit
+		case nil, bailout:
+			// normal return or early exit
 			err = check.firsterr
 		default:
 			// unexpected panic: don't crash clients

コアとなるコードの解説

src/pkg/go/types/types.go の変更

  • Var構造体の追加: type Var struct { Name string; Type Type } が追加されました。これは、Go言語の変数、関数パラメータ、関数結果など、名前と型を持つすべてのエンティティを抽象的に表現するための新しい型です。これにより、go/typesパッケージはgo/astパッケージのast.Objectに直接依存することなく、独自のセマンティックなオブジェクト表現を持つことができます。

  • ResultおよびSignature構造体の変更: Result構造体のValuesフィールドと、Signature構造体のRecvParamsResultsフィールドの型が、それぞれ[]*ast.Object*ast.Objectから、新しく定義された[]*Var*Varに変更されました。これは、go/typesパッケージがASTの具体的な表現から独立した型システムを構築するための中心的な変更です。これにより、型チェックのロジックがASTの構造に縛られず、より柔軟で再利用可能なものになります。

src/pkg/go/types/errors.go の変更

  • writeParams関数のシグネチャ変更: func writeParams(buf *bytes.Buffer, params []*ast.Object, isVariadic bool)func writeParams(buf *bytes.Buffer, params []*Var, isVariadic bool) に変更されました。これは、関数のパラメータリストを文字列としてフォーマットする際に、もはやast.Objectではなく、新しいVar型を使用することを示しています。また、内部でpar.Type.(Type)として型アサーションを行っていた箇所がpar.Typeに直接アクセスするように変更されており、Var構造体が直接Typeフィールドを持つため、よりシンプルになっています。

src/pkg/go/types/expr.go の変更

  • collectParams関数のシグネチャ変更と内部ロジック: func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*ast.Object, isVariadic bool)func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) に変更されました。この関数は、ASTのフィールドリストから関数のパラメータを収集する役割を担っています。 変更前は、ast.NewObjast.Objectを作成し、それをparamsスライスに追加していました。変更後は、params = append(params, &Var{obj.Name, typ}) のように、直接Var構造体のポインタを生成して追加しています。これにより、パラメータの収集プロセスがast.Objectから独立しました。 特に、可変長引数(variadic function)の処理において、以前はast.Objectのコピーを作成して元の型を保持していましたが、新しいVar型ではparamsリストをそのまま保持し、last.TypeSlice型に変更するだけで済むようになりました。これは、Varが名前と型を直接持つため、より直感的でシンプルな表現が可能になったことを示しています。

  • argumentおよびrawExpr関数の変更: これらの関数も、引数やレシーバの型を扱う際にast.Objectを生成する代わりに、直接Var構造体を使用するように変更されています。例えば、メソッド式の引数を表現する際に、ast.NewObj(ast.Var, "") を使って匿名ast.Objectを作成していた箇所が、&Var{"", x.typ} のように匿名Var構造体を直接生成する形に変わっています。

src/pkg/go/types/check.go のエラーハンドリング修正

  • deferブロック内のrecoverロジックの変更: check関数のdeferブロック内で、recover()が返す値に応じた処理が変更されました。 以前は、case nil:(パニックなし)とcase bailout:(早期終了パニック)が別々に扱われ、err = check.firsterrの代入がbailoutの場合にのみ行われるようなロジックでした。 修正後は、case nil, bailout: とまとめて処理されるようになり、パニックがなかった場合でも、早期終了のパニックの場合でも、常にerr = check.firsterrが実行されるようになりました。これにより、型チェック中に捕捉された最初のエラーが、どのような終了パスを辿ったとしても、一貫して呼び出し元に返されることが保証されます。これは、型チェックの信頼性と予測可能性を高めるための重要なバグ修正です。

これらの変更は、go/typesパッケージがより自己完結的で、go/astパッケージの具体的な実装詳細から独立した、堅牢な型システムへと進化していることを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(go/astおよびgo/typesパッケージに関する一般的な情報)
  • Go言語のソースコード(変更点の詳細な分析)