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

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

このコミットは、Goコンパイラのsrc/cmd/gcディレクトリ内のlex.cwalk.c、およびテストファイルtest/bugs/bug123.go(後にtest/fixedbugs/bug123.goにリネーム)とtest/golden.outに変更を加えています。主な目的は、Goコンパイラが非関数型の値を関数として呼び出そうとした際に発生するバグ(bug123)を修正し、より適切なエラーメッセージを出力するようにすることです。

コミット

commit d3d0c256be36bbe159f54f9d32456865354a618b
Author: Russ Cox <rsc@golang.org>
Date:   Mon Feb 16 17:44:05 2009 -0800

    bug123
    
    R=ken
    OCL=25075
    CL=25075

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

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

元コミット内容

    bug123
    
    R=ken
    OCL=25075
    CL=25075

変更の背景

このコミットは、Goコンパイラが特定の不正なコードパターン、具体的には関数ではない定数を関数として呼び出そうとした場合に、誤ったエラーメッセージを生成するバグ(内部的にbug123として識別されていたもの)を修正するために行われました。

Go言語の初期段階では、コンパイラのエラーメッセージはまだ洗練されておらず、開発者が問題の原因を正確に特定するのに役立つよう、継続的に改善されていました。このbug123は、コンパイラの型チェックフェーズにおいて、定数と関数の区別が曖昧になるケースがあったことを示唆しています。

具体的には、test/bugs/bug123.goというテストファイルがこのバグを再現していました。このファイルは、const F = 1と定義された定数をF()のように関数として呼び出すコードを含んでおり、コンパイラはこれに対して「function」というエラーメッセージを出力していました。しかし、より正確には「func」(関数型)に関するエラーであるべきでした。このコミットは、このエラーメッセージの精度を向上させ、コンパイラの堅牢性を高めることを目的としています。

前提知識の解説

このコミットを理解するためには、Goコンパイラの基本的な構造と、特に以下の概念についての知識が必要です。

  1. Goコンパイラ (gc): Go言語の公式コンパイラは、当初gcと呼ばれていました(現在はcmd/compile)。これは、ソースコードを機械語に変換する役割を担います。コンパイルプロセスは、主に以下のフェーズに分かれています。

    • Lexing (字句解析): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。src/cmd/gc/lex.cはこのフェーズの一部を扱います。
    • Parsing (構文解析): トークンのストリームを抽象構文木(AST: Abstract Syntax Tree)に変換します。
    • Type Checking (型チェック): ASTを走査し、各ノードの型がGo言語の型システムに適合しているか検証します。
    • Walking (ウォーク): 型チェック後、ASTをさらに変換し、最適化やコード生成に適した形にします。src/cmd/gc/walk.cはこのフェーズの主要な部分を担います。このフェーズでは、ASTノードの走査と変換が行われ、型推論、定数畳み込み、組み込み関数の処理など、多くのセマンティックな処理が含まれます。
    • Code Generation (コード生成): 変換されたASTから最終的な機械語コードを生成します。
  2. AST (Abstract Syntax Tree): ソースコードの構文構造を木構造で表現したものです。コンパイラはASTを操作することで、プログラムの意味を理解し、変換を行います。各ノードは、変数、関数呼び出し、演算子などのプログラム要素を表します。

  3. TFUNC: Goコンパイラの内部では、様々な型が数値定数や構造体で表現されます。TFUNCは、関数型を表す内部的な定数または識別子です。コンパイラが関数呼び出しを処理する際、呼び出されるエンティティの型がTFUNCであるか、あるいは関数として呼び出し可能であるかを検証します。

  4. walktypeconvlit:

    • walktype(node, context): walk.c内で使用される関数で、ASTノードnodeを走査し、指定されたcontext(例えばErvは"expression for value"を意味し、値として評価される式であることを示す)に基づいて型チェックや変換を行います。
    • convlit(node, target_type): walk.c内で使用される関数で、リテラル(定数)の型をtarget_typeに変換しようと試みます。これは、型推論や型強制のプロセスの一部として行われます。例えば、整数リテラルが浮動小数点数型を期待するコンテキストで使用された場合に、適切な変換を試みるような場合です。このコミットでは、非関数型の値を関数型として扱おうとした際に、このconvlitTFUNC型をターゲットとして呼び出されることで、より正確なエラー検出とメッセージ生成に寄与しています。
  5. golden.out ファイル: Goプロジェクトのテストスイートの一部で、コンパイラのエラーメッセージや出力の「ゴールデン(期待される)結果」を記録するファイルです。テストが実行されると、実際の出力がgolden.outと比較され、一致しない場合はテストが失敗します。このファイルから特定のバグに関するエントリが削除されることは、そのバグが修正され、もはや期待されるエラーではないことを意味します。

技術的詳細

このコミットの技術的詳細は、Goコンパイラの型チェックとAST変換のフェーズにおける、関数呼び出しの処理とエラー報告の改善に焦点を当てています。

  1. src/cmd/gc/lex.c の変更: lexinit()関数内で、types[TFUNC] = functype(N, N, N);という行が追加されています。

    • lexinit()は、コンパイラの字句解析フェーズの初期化を行う関数です。
    • typesは、コンパイラが認識する様々な型を格納するグローバルな配列またはマップのようなものです。
    • TFUNCは、前述の通り関数型を表す内部識別子です。
    • functype(N, N, N)は、引数なし、戻り値なしの汎用的な関数型を生成する関数です(Nはnilノードを意味し、ここでは引数や戻り値のリストが空であることを示唆します)。
    • この変更は、「エラーメッセージでwalkが使用するための」というコメントが示すように、walkフェーズで不正な関数呼び出しを検出した際に、比較対象となるデフォルトの関数型を事前に初期化しておくことを目的としています。これにより、walkフェーズがより堅牢に型チェックを行えるようになります。
  2. src/cmd/gc/walk.c の変更: walk.cは、ASTの走査と変換を行うコンパイラの中心的な部分です。このファイルでは、関数呼び出し(OCALLOCALLINTER)に関連する複数の箇所でconvlit(node, types[TFUNC]);が追加されています。

    • OCALLは通常の関数呼び出し、OCALLINTERはインターフェースメソッド呼び出しを表すASTノードのオペレーションコードです。
    • これらの変更は、関数呼び出しの対象(n->leftrnr->left)がwalktypeによって走査された後、その型がtypes[TFUNC](汎用関数型)に変換可能であるか、またはその型として扱えるかをconvlit関数で明示的にチェックすることを意味します。
    • 元のバグは、F()のように定数を関数として呼び出した際に、コンパイラがその定数を関数として誤って解釈しようとし、結果として不正確なエラーメッセージを出力していた可能性があります。convlit(..., types[TFUNC])を追加することで、コンパイラは「このエンティティは関数として呼び出されているが、本当に関数型に変換できるのか?」というチェックを強制します。もし変換できない場合(例えば、int型の定数である場合)、convlitは適切な型エラーを発生させ、より正確なエラーメッセージ(例: "func"に関するエラー)を導くことができます。
    • これにより、コンパイラは、関数呼び出しのコンテキストで非関数型の値が使用された場合に、より早期かつ正確にエラーを検出できるようになります。
  3. test/{bugs => fixedbugs}/bug123.go の変更:

    • ファイル名がtest/bugs/bug123.goからtest/fixedbugs/bug123.goにリネームされました。これは、このテストケースがもはや未修正のバグではなく、修正済みであることを示します。Goのテストスイートでは、bugsディレクトリは未修正のバグを再現するテストを、fixedbugsディレクトリは修正済みのバグを再現するテストを格納するのが一般的です。
    • テストコード内のコメントが// ERROR "function"から// ERROR "func"に変更されました。これは、このコードがコンパイルエラーになることは変わらないものの、期待されるエラーメッセージが「function」という一般的な文字列から、「func」(関数型)というより具体的な文字列に変わったことを示しています。これは、convlitによる型チェックの強化が、より精度の高いエラーメッセージ生成に貢献した結果です。
  4. test/golden.out の変更: test/golden.outからbugs/bug123.goに関するエントリが削除されました。これは、bug123.goがもはやbugsディレクトリに存在せず、かつそのテストがerrchk(エラーチェック)で成功するようになった(つまり、期待通りにエラーを出すようになった)ため、golden.outにその失敗の記録が不要になったことを意味します。

これらの変更は、Goコンパイラの型システムとエラー報告の初期の洗練プロセスの一部であり、コンパイラがより正確で役立つ診断メッセージを提供するための重要なステップでした。

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

src/cmd/gc/lex.c

@@ -1233,6 +1233,9 @@ lexinit(void)\n \t\ts->otype = t;\n \t}\n \n+\t/* for walk to use in error messages */\n+\ttypes[TFUNC] = functype(N, N, N);\n+\n \t/* pick up the backend typedefs */\n \tbelexinit(LBASETYPE);\n \n```

### `src/cmd/gc/walk.c`
```c
@@ -373,9 +373,12 @@ loop:\n \t\tif(n->type != T)\n \t\t\tgoto ret;\n \n-\t\twalktype(n->left, Erv);\n \t\tif(n->left == N)\n \t\t\tgoto ret;\n+\n+\t\twalktype(n->left, Erv);\n+\t\tconvlit(n->left, types[TFUNC]);\n+\n \t\tt = n->left->type;\n \t\tif(t == T)\n \t\t\tgoto ret;\n@@ -472,6 +475,7 @@ loop:\n \t\t\tif(cr == 1) {\n \t\t\t\t// a,b,... = fn()\n \t\t\t\twalktype(r, Erv);\n+\t\t\t\tconvlit(r, types[TFUNC]);\n \t\t\t\tl = ascompatet(n->op, &n->left, &r->type, 0);\n \t\t\t\tif(l != N)\n \t\t\t\t\tindir(n, list(r, reorder2(l)));\n@@ -3108,6 +3112,7 @@ multi:\n \tcase OCALLINTER:\n \tcase OCALL:\n \t\twalktype(nr->left, Erv);\n+\t\tconvlit(nr->left, types[TFUNC]);\n \t\tt = nr->left->type;\n \t\tif(t != T && t->etype == tptr)\n \t\t\tt = t->type;\n```

### `test/bugs/bug123.go` (renamed to `test/fixedbugs/bug123.go`)
```diff
--- a/test/bugs/bug123.go
+++ b/test/fixedbugs/bug123.go
@@ -7,7 +7,7 @@
 package main
 const ( F = 1 )
 func fn(i int) int {
-  if i == F() {		// ERROR "function"
+  if i == F() {		// ERROR "func"
     return 0
   }
   return 1

test/golden.out

--- a/test/golden.out
+++ b/test/golden.out
@@ -143,9 +143,6 @@ BUG: should compile
 =========== bugs/bug122.go
 BUG: compilation succeeds incorrectly
 
--=========== bugs/bug123.go
--BUG: errchk: command succeeded unexpectedly:  6g bugs/bug123.go
--
 =========== bugs/bug125.go
 BUG: errchk: command succeeded unexpectedly:  6g bugs/bug125.go
 

コアとなるコードの解説

このコミットの核心は、Goコンパイラのwalkフェーズにおける型チェックの厳密化と、それに伴うエラーメッセージの改善です。

  1. lex.cでのTFUNCの初期化: lexinit関数は、コンパイラの初期化時に様々な内部構造を設定します。ここでtypes[TFUNC] = functype(N, N, N);が追加されたのは、walkフェーズが関数型に関するエラーを報告する際に参照できる、汎用的な関数型の定義を事前に用意するためです。これにより、walkフェーズは、不正な関数呼び出しを検出した際に、その対象が「関数型」ではないことをより明確に診断できるようになります。

  2. walk.cでのconvlitの追加: walk.c内の複数の箇所、特にOCALL(通常の関数呼び出し)やOCALLINTER(インターフェースメソッド呼び出し)を処理するロジックにおいて、walktypeの呼び出し後にconvlit(..., types[TFUNC]);が追加されました。

    • walktypeは、式が値として評価されるべきであることを示唆するErvコンテキストで、その式の型を決定し、必要に応じてASTを変換します。
    • その直後にconvlit(node, types[TFUNC])が呼び出されることで、コンパイラは、現在処理しているnode(関数呼び出しの対象)が、types[TFUNC]で表される関数型に「変換可能」であるかを明示的に検証します。
    • もしnodeが実際には関数ではない(例えば、int型の定数である)場合、このconvlitの呼び出しは失敗し、コンパイラは「この値は関数として呼び出せない」という、より正確な型エラーを生成します。これにより、以前は「function」という曖昧なエラーだったものが、「func」(関数型)に関するより具体的なエラーに変わります。これは、コンパイラが不正なコードパターンに対して、より的確な診断情報を提供するための重要な改善です。
  3. テストケースの更新: bug123.gofixedbugsディレクトリに移動され、期待されるエラーメッセージが"function"から"func"に変更されたことは、この修正が意図通りに機能し、コンパイラがより正確なエラーメッセージを出力するようになったことを明確に示しています。golden.outからのエントリ削除も、このバグが修正されたことの確認です。

これらの変更は、Goコンパイラの初期開発段階における型システムとエラー報告の成熟度を高めるための、細かではあるが重要なステップでした。これにより、開発者はコンパイルエラーからより迅速に問題を特定し、修正できるようになりました。

関連リンク

参考にした情報源リンク

  • Goコンパイラの内部構造に関する一般的な情報 (例: Go Compiler Design, Go Internalsなど)
    • Goのコンパイラに関する公式ドキュメントやブログ記事 (初期のものは見つけにくい可能性がありますが、Goのコンパイルプロセスに関する一般的な説明は参考になります)
    • Goのソースコード自体 (特にsrc/cmd/compileディレクトリ内のファイル)
  • Go言語の型システムに関するドキュメント
  • Go言語のテストフレームワークとgoldenファイルの概念に関する情報