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

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

このコミットは、Go言語のAPI定義生成ツールである cmd/api/goapi における型チェッカーのバグ修正を目的としています。具体的には、定数式の型推論、特に比較演算子を含むブール定数の扱いに問題があり、これが他の重要な変更(net fd timeout 関連の変更)の提出を妨げていました。このコミットは、より広範な型チェッカーの改善が進行中であるものの、緊急性の高い暫定的な修正として適用されました。

コミット

commit a384b5b9c38289cb2b912d8c38a201fee6500663
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Nov 8 10:34:54 2012 -0600

    cmd/api: bug fix for goapi's lame type checker
    
    This is blocking me submitting the net fd timeout
    CL, since goapi chokes on my constant.
    
    The much more extensive fix to goapi's type checker
    in pending review in https://golang.org/cl/6742050
    
    But I'd rather get this quick fix in first.
    
    R=golang-dev, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/6818104

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

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

元コミット内容

cmd/api: goapi の貧弱な型チェッカーのバグ修正

これは、goapi が私の定数で詰まるため、net fd timeout の変更リスト(CL)の提出を妨げています。

goapi の型チェッカーに対するより広範な修正は、https://golang.org/cl/6742050 でレビュー待ちです。

しかし、私はまずこの迅速な修正を適用したいと考えています。

変更の背景

このコミットの背景には、Go言語の標準ライブラリ開発における具体的な問題がありました。コミットメッセージによると、作者(Brad Fitzpatrick)は「net fd timeout CL」という別の重要な変更を提出しようとしていましたが、goapi ツールがその変更に含まれる特定の定数式を正しく処理できず、エラーを発生させていました。

goapi は、Goのパッケージの公開APIサーフェスを抽出・定義するためのツールであり、Goの互換性保証において重要な役割を担っています。このツールが正しく機能しないと、新しいAPIの追加や既存APIの変更が適切に検証されず、Goの安定性に影響を与える可能性があります。

当時、goapi の型チェッカーには、特に定数式の型推論において既知の限界がありました。コミットメッセージでは、より包括的な修正(https://golang.org/cl/6742050)が既にレビュー段階にあることが示されていますが、その修正がマージされるのを待つと、net fd timeout CLのような緊急性の高い他の開発が滞ってしまう状況でした。

そのため、このコミットは、より大規模な修正が適用されるまでの間、開発のブロックを解除するための「迅速な修正(quick fix)」として導入されました。これは、開発プロセスにおける実用性と緊急性を優先した判断であり、Goプロジェクトの継続的な開発フローを維持するための典型的なアプローチと言えます。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  1. Go言語の cmd/api (goapi):

    • goapi は、Go言語のツールチェインの一部であり、Goの標準ライブラリやその他のパッケージの公開API(エクスポートされた型、関数、変数、定数など)を抽出・定義するために使用されます。
    • このツールは、Goの「互換性保証(Go 1 Compatibility Promise)」を維持するために不可欠です。Go 1以降、Go言語は後方互換性を厳密に維持しており、既存のコードが新しいバージョンのGoでも動作することを保証しています。goapi は、APIの変更を追跡し、互換性を損なう変更がないかをチェックするのに役立ちます。
    • goapi は、Goの抽象構文木(AST: Abstract Syntax Tree)を解析し、APIの定義を生成します。このプロセスには、コード内の型情報を正確に理解する「型チェッカー」の機能が含まれます。
  2. 抽象構文木 (AST: Abstract Syntax Tree):

    • コンパイラやリンター、コード分析ツールなどがソースコードを解析する際に内部的に構築するツリー構造の表現です。ソースコードの構造を抽象化し、プログラムの論理的な構造を表現します。
    • Go言語では、go/ast パッケージがASTの操作を提供します。goapi はこのASTを走査(walk)して、必要な情報を抽出します。
  3. 定数 (Constants) と型推論 (Type Inference):

    • Go言語では、const キーワードを使用して定数を宣言します。定数はコンパイル時に値が決定され、不変です。
    • Goは強力な型推論機能を持ち、変数の型を明示的に宣言しなくても、初期値から型を推論できます。定数についても同様で、特に型が明示されていない「型なし定数(untyped constants)」の場合、その使用コンテキストに基づいて型が決定されます。
    • このコミットの問題は、goapi の型チェッカーが、特に比較演算子(==, <, > など)を含む複雑な定数式(例: truth = foo == "foo" || foo2 == "foo2")の最終的な型(この場合は bool)を正しく推論できないことにありました。
  4. net fd timeout CL:

    • これは、Goのネットワーク関連のコードにおけるファイルディスクリプタ(fd: file descriptor)のタイムアウト処理に関する変更リスト(Change List)を指します。Goの標準ライブラリは、ネットワークI/Oを効率的に処理するためにシステムコールやファイルディスクリプタを多用します。タイムアウト処理は、ネットワークの応答がない場合にプログラムが無限に待機するのを防ぐために重要です。
    • このCLは、Goのネットワークスタックの改善に関連するものであり、その提出がgoapiのバグによってブロックされていたことが、このコミットの緊急性を高めていました。
  5. Gerrit Code Review (golang.org/cl/):

    • Goプロジェクトは、コードレビューにGerritというシステムを使用しています。golang.org/cl/ は、Gerrit上の変更リスト(Change List, CL)へのリンクです。開発者は変更を提案する際にCLを作成し、他の開発者からのレビューを受けてからマージされます。
    • コミットメッセージに記載されている https://golang.org/cl/6742050 は、このコミットよりも広範な goapi の型チェッカー修正に関するCLを指しており、このコミットがその大規模な修正がマージされるまでの「つなぎ」であることを示しています。

技術的詳細

このコミットは、src/cmd/api/goapi.go ファイル内の Walker 構造体のメソッド、特に定数式の型を決定するロジックに焦点を当てています。

以前の goapi の型チェッカーは、一部の定数式、特にブール型の結果を返す比較演算子を含む式を正しく処理できませんでした。この問題を回避するため、hardCodedConstantType という「ハック」関数が存在していました。この関数は、特定のパッケージ(例: syscall)と定数名(例: darwinAMD64)の組み合わせに対して、ハードコードされた型(例: bool)を返すことで、型チェッカーの限界を補っていました。しかし、これは汎用的な解決策ではなく、新しいケースが発生するたびに更新が必要となる、保守性の低いアプローチでした。

このコミットの主要な変更点は以下の通りです。

  1. hardCodedConstantType 関数の削除:

    • この関数は、特定の定数に対する型推論の「抜け穴」として機能していましたが、汎用性がなく、コードの複雑性を増していました。このコミットでは、より汎用的な解決策が導入されたため、この関数は完全に削除されました。これにより、コードベースがクリーンになり、将来的なメンテナンスが容易になります。
  2. constValueType メソッドの改善:

    • constValueType メソッドは、ASTノード(ast.Expr)を受け取り、その定数式の型を文字列として返します。
    • このコミットでは、*ast.BinaryExpr(二項演算子を含む式)の処理ロジックが強化されました。具体的には、二項演算子が以下の比較演算子である場合、その結果の型は常に bool であると明示的に指定されました。
      • token.EQL (==)
      • token.LSS (<)
      • token.GTR (>)
      • token.NOT (!, ただしこれは単項演算子であり、BinaryExpr のコンテキストでは通常 != の一部として扱われるか、あるいは NOTtoken.NEQ の誤記である可能性も考慮されます。Goの token パッケージでは NOT は単項演算子ですが、ここでは BinaryExpr の文脈で列挙されているため、!= の一部として解釈されるか、あるいは token.NEQ の誤記である可能性が高いです。コミットのテストケース foo == "foo" || foo2 == "foo2"==|| を含みますが、||BinaryExprOp として直接 token.LOR となります。しかし、この修正は token.EQL などに焦点を当てているため、|| 自体の型推論ではなく、その内部の比較演算子の結果の型を bool と明示することに意味があります。)
      • token.NEQ (!=)
      • token.LEQ (<=)
      • token.GEQ (>=)
    • この変更により、goapifoo == "foo" のような比較式の結果が bool であることを正しく推論できるようになり、以前のバグが修正されました。
  3. walkConst メソッドの変更:

    • walkConst メソッドは、定数宣言(ast.ValueSpec)を走査し、その値を処理します。
    • 以前は、constValueType がエラーを返した場合に hardCodedConstantType を呼び出してフォールバックするロジックがありました。hardCodedConstantType の削除に伴い、このフォールバックロジックも削除されました。これにより、constValueType がエラーを返した場合、即座に log.Fatalf でプログラムが終了するようになります。これは、型推論がより堅牢になったため、以前のような「ハック」なフォールバックが不要になったことを意味します。

これらの変更により、goapi はより正確に定数式の型を推論できるようになり、特にブール定数に関する問題が解決されました。

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

src/cmd/api/goapi.go

--- a/src/cmd/api/goapi.go
+++ b/src/cmd/api/goapi.go
@@ -331,20 +331,6 @@ const (
 	loaded
 )
 
-// hardCodedConstantType is a hack until the type checker is sufficient for our needs.
-// Rather than litter the code with unnecessary type annotations, we'll hard-code
-// the cases we can't handle yet.
-func (w *Walker) hardCodedConstantType(name string) (typ string, ok bool) {
-	switch w.scope[0] {
-	case "pkg syscall":
-		switch name {
-		case "darwinAMD64":
-			return "bool", true
-		}
-	}
-	return "", false
-}
-
 func (w *Walker) Features() (fs []string) {
 	for f := range w.features {
 		fs = append(fs, f)
@@ -596,6 +582,10 @@ func (w *Walker) constValueType(vi interface{}) (string, error) {
 		}
 		return constDepPrefix + v.Name, nil
 	case *ast.BinaryExpr:
+		switch v.Op {
+		case token.EQL, token.LSS, token.GTR, token.NOT, token.NEQ, token.LEQ, token.GEQ:
+			return "bool", nil
+		}
 		left, err := w.constValueType(v.X)
 		if err != nil {
 			return "", err
@@ -768,12 +758,7 @@ func (w *Walker) walkConst(vs *ast.ValueSpec) {
 				var err error
 				litType, err = w.constValueType(vs.Values[0])
 				if err != nil {
-					if t, ok := w.hardCodedConstantType(ident.Name); ok {
-						litType = t
-						err = nil
-					} else {
-						log.Fatalf("unknown kind in const %q (%T): %v", ident.Name, vs.Values[0], err)
-					}
+					log.Fatalf("unknown kind in const %q (%T): %v", ident.Name, vs.Values[0], err)
 				}
 			}
 		}

src/cmd/api/testdata/src/pkg/p1/p1.go

--- a/src/cmd/api/testdata/src/pkg/p1/p1.go
+++ b/src/cmd/api/testdata/src/pkg/p1/p1.go
@@ -161,3 +161,9 @@ func (common) OnBothTandBVal()  {}
 type EmbedSelector struct {
 	time.Time
 }
+
+const (
+	foo          = "foo"
+	foo2  string = "foo2"
+	truth        = foo == "foo" || foo2 == "foo2"
+)

コアとなるコードの解説

src/cmd/api/goapi.go

  1. hardCodedConstantType 関数の削除:

    • この関数は、goapi の型チェッカーが特定の定数(例: syscall パッケージの darwinAMD64)の型を正しく推論できない場合の「一時しのぎ」として存在していました。この関数が削除されたことは、型チェッカーの基本的な能力が向上し、このようなハードコードされた例外処理が不要になったことを示しています。これにより、コードの可読性と保守性が向上しました。
  2. constValueType メソッド内の *ast.BinaryExpr 処理の追加:

    • constValueType は、GoのASTノードを解析して、その定数式の型を文字列で返す重要なメソッドです。
    • 追加された switch v.Op ブロックは、ast.BinaryExpr(二項演算子を含む式)が処理される際に、その演算子(v.Op)が比較演算子(==, <, >, !=, <=, >=)のいずれかである場合、式の評価結果が常にブール型(bool)であることを明示的に指定しています。
    • これにより、goapifoo == "foo" のような式が bool 型の定数 true または false を生成することを正しく認識できるようになりました。これが、コミットメッセージで言及されている「goapi が私の定数で詰まる」問題の直接的な解決策です。
  3. walkConst メソッド内のエラーハンドリングの変更:

    • walkConst は、定数宣言を走査するメソッドです。
    • 以前は、constValueType が定数式の型推論に失敗した場合、hardCodedConstantType を呼び出して代替の型情報を取得しようとしていました。
    • この変更では、hardCodedConstantType が削除されたため、そのフォールバックロジックも削除されました。結果として、constValueType がエラーを返した場合、log.Fatalf が直接呼び出され、プログラムが異常終了するようになります。これは、型チェッカーがより堅牢になり、以前のような「ハック」なフォールバックが不要になったという自信の表れです。もし型推論が失敗するようなケースがあれば、それは致命的なエラーとして扱われるべきであるという設計思想に基づいています。

src/cmd/api/testdata/src/pkg/p1/p1.go

  • このファイルは goapi のテストデータの一部であり、goapi が正しくAPIを解析できるかを検証するために使用されます。
  • 追加された const ブロックは、このコミットで修正された問題、すなわち比較演算子を含むブール定数式の型推論をテストするためのものです。
    • foo = "foo": 文字列定数。
    • foo2 string = "foo2": 明示的に型が指定された文字列定数。
    • truth = foo == "foo" || foo2 == "foo2": この行が重要です。foo == "foo"true というブール定数に評価され、foo2 == "foo2" も同様です。これらのブール値が論理OR (||) で結合され、最終的に true というブール定数 truth が生成されます。以前の goapi は、このような複合的なブール定数式の型を正しく推論できなかったため、このテストケースの追加は、修正が期待通りに機能することを確認するために不可欠です。

これらの変更により、goapi はより正確にGoの定数式、特にブール型の比較結果を伴う定数を解析できるようになり、GoのAPI互換性チェックの信頼性が向上しました。

関連リンク

  • このコミットのGerrit Code Review: https://golang.org/cl/6818104
  • より広範な型チェッカー修正のGerrit Code Review: https://golang.org/cl/6742050 (このリンクは古いGerritインスタンスのものであり、現在はアクセスできない可能性がありますが、コミットメッセージに記載されているため含めます。)

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go 1 Compatibility Promise, Constants, ASTなど)
  • Go言語のソースコード (src/cmd/api/, go/ast, go/token パッケージ)
  • Gerrit Code Review System (Goプロジェクトが使用) に関する一般的な情報