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

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

このコミットは、Go言語の実験的な型チェッカーパッケージ exp/types/staging におけるテストドライバーの追加と改善に関するものです。特に、check_test.go が複数のエラーを同じ位置で処理できるように変更され、開発中のテストの使いやすさが向上しています。

コミット

  • コミットハッシュ: 328f0e7f2efd9ad96b4c09119a18e622e74b3802
  • 作者: Robert Griesemer gri@golang.org
  • 日付: Sun Oct 7 18:00:56 2012 -0700

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

https://github.com/golang/go/commit/328f0e7f2efd9ad96b4c09119a18e622e74b3802

元コミット内容

exp/types/staging: test drivers

This code has been reviewed before. The most significant
change is to check_test which now can handle more than
one error at the same error position (due to spurious
errors - should not happen in praxis once error handling
has been fine-tuned). This change makes check_test easier
to use during development.

R=rsc
CC=golang-dev
https://golang.org/cl/6584057

変更の背景

このコミットは、Go言語の型チェッカーの初期開発段階におけるテストインフラの強化を目的としています。exp/types/staging パッケージは、Go言語の型システムと型チェックロジックを実験的に開発するためのステージングエリアでした。型チェッカーのような複雑なシステムでは、正確なエラー報告が非常に重要です。

元のコミットメッセージによると、このコードは以前にレビューされており、最も重要な変更点は check_test.go にあります。以前のバージョンでは、同じソースコードの位置で複数のエラーが発生した場合(特に開発初期段階で「スプリアスエラー(偽のエラー)」が発生しやすい状況)、テストハーネスが適切に処理できない可能性がありました。この制限は、開発者が型チェッカーのロジックをデバッグし、エラー処理を微調整する際に不便をもたらしていました。

このコミットの目的は、check_test.go を改良し、同じエラー位置で複数のエラーを処理できるようにすることで、開発中のテストの柔軟性と使いやすさを向上させることにあります。これにより、型チェッカーのエラー報告がより堅牢になり、開発者はより効率的に作業を進めることができるようになります。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と標準ライブラリに関する知識が必要です。

  1. Go言語のexpパッケージ: exp(experimental)パッケージは、Go言語の標準ライブラリの一部として将来的に組み込まれる可能性のある、実験的なAPIや機能を含むパッケージです。これらのパッケージは、まだ安定しておらず、APIが変更される可能性があります。exp/types/staging は、Go言語の型チェッカーの初期開発バージョンをホストするために使用されていました。

  2. Go言語の型システムと型チェッカー: Go言語は静的型付け言語であり、プログラムの実行前に型チェックが行われます。型チェッカーは、プログラムが型規則に準拠しているかを確認し、型エラーを検出します。これは、コンパイル時に多くのバグを捕捉するために不可欠なプロセスです。

  3. 抽象構文木 (AST - Abstract Syntax Tree): コンパイラやインタープリタは、ソースコードを直接処理するのではなく、まずソースコードを抽象構文木(AST)と呼ばれるツリー構造に変換します。ASTは、プログラムの構造を抽象的に表現したもので、型チェッカーはASTを走査して型情報を収集し、型規則を適用します。

  4. go/tokenパッケージ: Go言語のソースコードを解析する際に使用されるトークン(キーワード、識別子、演算子など)と、それらのトークンのソースコード上の位置(ファイル名、行番号、列番号、オフセット)を管理するためのパッケージです。token.FileSet は、複数のソースファイルをまとめて管理し、正確な位置情報を提供します。

  5. go/scannerパッケージ: Go言語のソースコードをトークンに分割する字句解析(スキャン)を行うパッケージです。エラーが発生した場合、scanner.ErrorList にエラー情報を収集します。

  6. go/parserパッケージ: Go言語のソースコードを解析し、ASTを構築する構文解析(パース)を行うパッケージです。parser.ParseFile 関数は、指定されたファイルからASTを生成します。

  7. go/astパッケージ: Go言語のASTのノード構造を定義するパッケージです。ASTの各要素(宣言、式、文など)は、astパッケージで定義された型として表現されます。ast.Inspect 関数は、ASTを走査するためのユーティリティです。

  8. テストハーネスと期待されるエラー: ソフトウェアテストにおいて、テストハーネスはテストの実行環境を提供し、テストケースの実行を管理するフレームワークです。このコミットで言及されているテストハーネスは、型チェッカーが特定のエラーを正しく報告するかどうかを検証するために設計されています。 「期待されるエラー」とは、テスト対象のコードが意図的にエラーを発生させるように記述されており、そのエラーが型チェッカーによって正しく検出され、報告されることをテストで確認するものです。テストファイル内に特定のコメント形式(例: /* ERROR "rx" */)で期待されるエラーメッセージの正規表現を記述することで、テストハーネスが自動的に検証を行います。

技術的詳細

このコミットは、Go言語の型チェッカー exp/types/staging パッケージのテストインフラストラクチャに焦点を当てています。具体的には、以下の3つの新しいテストファイルが追加されています。

  1. src/pkg/exp/types/staging/check_test.go: このファイルは、型チェッカーのテストハーネスの主要な実装を含んでいます。その主な機能は以下の通りです。

    • テストファイルの読み込みと解析: テストデータディレクトリ (testdata/) にあるGoソースファイル(.src拡張子を持つ)を読み込み、go/parser を使用してASTを構築します。これらのファイルは、gofmt が変更しないように.goではなく.src拡張子を使用しています。
    • 期待されるエラーの収集: テストファイル内に記述された /* ERROR "rx" */ 形式のコメントを解析し、期待されるエラーメッセージの正規表現と、それが期待されるソースコード上の位置を収集します。errRx という正規表現 (^/\* *ERROR *\"([^\"]*)\" *\*/$) を使用してコメントをマッチングします。
    • エラーの検証: 型チェッカーが報告した実際のエラーメッセージと、収集した期待されるエラーメッセージを比較します。eliminate 関数は、報告されたエラーが期待されるエラーリストのいずれかにマッチした場合、その期待されるエラーをリストから削除します。
    • 複数エラーの処理: 最も重要な変更点として、check_test.go は同じソースコード位置で複数のエラーを処理できるようになりました。これは、expectedErrors 関数が map[token.Pos][]string を使用して、1つの位置に対して複数の期待されるエラーメッセージ(正規表現)をリストとして保持することで実現されています。eliminate 関数は、報告されたエラーがこのリスト内のいずれかの正規表現にマッチすれば、そのエラーを処理済みと見なします。これにより、開発中に発生する可能性のある「スプリアスエラー」に対応し、テストの柔軟性を高めています。
    • テスト実行: TestCheck 関数は、定義されたテストケース (tests 変数) をループし、各テストケースに対して checkFiles 関数を呼び出して型チェックとエラー検証を実行します。
  2. src/pkg/exp/types/staging/resolver_test.go: このファイルは、識別子の解決(特に修飾識別子、例: fmt.PrintlnfmtPrintln)に関するテストを含んでいます。

    • ResolveQualifiedIdents 関数: この関数は、パッケージのASTを走査し、修飾識別子(例: pkg.Name)のセレクタ部分(Name)を、対応するパッケージスコープ内のオブジェクトに解決します。これは、型チェックの前の段階で、名前解決が正しく行われることを保証するために重要です。
    • テストケース: sources 変数にGoコードのスニペットが定義されており、これらのコードが正しく解析され、識別子が解決されることを TestResolveQualifiedIdents 関数で検証します。
  3. src/pkg/exp/types/staging/types_test.go: このファイルは、型チェッカーがGo言語の様々な型(基本型、配列、スライス、構造体、ポインタ、関数、インターフェース、マップ、チャネルなど)を正しく表現し、処理できることを検証するためのテストを含んでいます。

    • makePkg 関数: 与えられたソースコードからパッケージを構築し、型チェックを実行するヘルパー関数です。
    • testTypes 変数: 様々なGoの型定義とその文字列表現のペアが定義されており、型チェッカーがこれらの型を正しく解析し、内部的に表現できることを TestTypes 関数で検証します。
    • testExprs 変数: 様々なGoの式とその文字列表現のペアが定義されており、型チェッカーがこれらの式を正しく解析し、内部的に表現できることを TestExprs 関数で検証します。

これらのテストファイルは、Go言語の型チェッカーが正しく機能していることを多角的に検証するための基盤を構築しています。特に check_test.go の改良は、開発者が型チェッカーのバグを特定し、修正するプロセスを大幅に効率化することを目的としています。

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

このコミットでは、以下の3つの新しいファイルが追加されています。

  • src/pkg/exp/types/staging/check_test.go: 型チェッカーのテストハーネスの実装。特に、テストファイル内の /* ERROR "rx" */ コメントを解析し、期待されるエラーを検証するロジックが含まれています。同じ位置での複数エラー処理が改善されました。

  • src/pkg/exp/types/staging/resolver_test.go: 修飾識別子(例: fmt.Println)の名前解決に関するテスト。パッケージのASTを走査し、セレクタが正しいオブジェクトに解決されることを確認します。

  • src/pkg/exp/types/staging/types_test.go: Go言語の様々な型(基本型、複合型、関数型など)や式が型チェッカーによって正しく処理されることを検証するテスト。

コアとなるコードの解説

src/pkg/exp/types/staging/check_test.go の主要な変更点と機能

このファイルは、型チェッカーのテストフレームワークの中核をなします。

  1. expectedErrors 関数: この関数は、テストファイル内の /* ERROR "rx" */ コメントをスキャンし、期待されるエラーの正規表現を収集します。

    var errRx = regexp.MustCompile(`^/\* *ERROR *\"([^\"]*)\" *\*/$`)
    
    func expectedErrors(t *testing.T, testname string, files map[string]*ast.File) map[token.Pos][]string {
        errors := make(map[token.Pos][]string)
        // ... (ファイル読み込みとスキャンロジック) ...
        case token.COMMENT:
            s := errRx.FindStringSubmatch(lit)
            if len(s) == 2 {
                list := errors[prev]
                errors[prev] = append(list, string(s[1])) // 同じ位置に複数のエラーを追加
            }
        // ...
        return errors
    }
    

    ここで注目すべきは、errors マップの型が map[token.Pos][]string である点です。これにより、prev(エラーが期待されるトークンの位置)をキーとして、複数のエラーメッセージの正規表現をスライス ([]string) として格納できます。これが、同じ位置で複数のエラーを処理できるようになった主要な変更点です。

  2. eliminate 関数: この関数は、型チェッカーが報告した実際のエラー (errors 引数) を受け取り、それが expected マップに格納されている期待されるエラーのいずれかにマッチするかどうかを検証します。

    func eliminate(t *testing.T, expected map[token.Pos][]string, errors error) {
        // ...
        for _, error := range errors.(scanner.ErrorList) {
            pos := getPos(error.Pos.Filename, error.Pos.Offset)
            list := expected[pos] // その位置で期待されるエラーのリストを取得
            index := -1
            for i, msg := range list {
                rx, err := regexp.Compile(msg)
                if err != nil { /* ... */ }
                if match := rx.MatchString(error.Msg); match {
                    index = i // マッチするエラーが見つかった
                    break
                }
            }
            if index >= 0 {
                // マッチしたエラーをリストから削除
                n := len(list) - 1
                if n > 0 {
                    list[index] = list[n]
                    expected[pos] = list[:n]
                } else {
                    delete(expected, pos) // リストが空になったらマップからエントリを削除
                }
            } else {
                t.Errorf("%s: no error expected: %q", error.Pos, error.Msg)
            }
        }
    }
    

    eliminate 関数は、報告されたエラーが期待されるエラーリスト内のいずれかの正規表現にマッチした場合、その期待されるエラーをリストから削除します。これにより、テストの最後に expected マップが空であれば、すべての期待されるエラーが報告され、余分なエラーが報告されていないことが確認できます。

  3. checkFiles 関数: この関数は、テストケースのGoソースファイルを解析し、識別子解決、型チェックの各段階で発生するエラーを eliminate 関数を使って検証します。

    func checkFiles(t *testing.T, testname string, testfiles []string) {
        files, err := parseFiles(t, testname, testfiles)
        errors := expectedErrors(t, testname, files) // 期待されるエラーを収集
    
        eliminate(t, errors, err) // パーサーからのエラーを検証
    
        pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
        eliminate(t, errors, err) // 識別子解決からのエラーを検証
    
        var list scanner.ErrorList
        errh := func(pos token.Pos, msg string) {
            list.Add(fset.Position(pos), msg)
        }
        err = Check(fset, pkg, errh, nil)
        eliminate(t, errors, list) // 型チェッカーからのエラーを検証
    
        if len(errors) > 0 {
            t.Errorf("%s: %d errors not reported:", testname, len(errors))
            // ... 報告されなかったエラーを出力 ...
        }
    }
    

    この関数は、パーサー、識別子解決、型チェッカーの各段階で発生するエラーを順次検証し、最終的にすべての期待されるエラーが報告されたことを確認します。

src/pkg/exp/types/staging/resolver_test.go の主要な機能

このファイルは、GoのASTにおける修飾識別子の解決をテストします。

  • ResolveQualifiedIdents 関数: この関数は、ast.SelectorExpr(例: pkg.Name)を走査し、セレクタの Sel 部分が正しい ast.Object に解決されていることを確認します。これは、型チェックの前にシンボル解決が正しく行われることを保証するために重要です。

src/pkg/exp/types/staging/types_test.go の主要な機能

このファイルは、型チェッカーがGoの様々な型や式を正しく処理できることを検証します。

  • TestTypes 関数: testTypes に定義されたGoの型定義文字列を解析し、型チェッカーがそれらを正しく内部表現に変換できることを確認します。例えば、"[]int" が正しくスライス型として認識されるかなどをテストします。

  • TestExprs 関数: testExprs に定義されたGoの式文字列を解析し、型チェッカーがそれらを正しくASTとして構築し、型情報を付与できることを確認します。例えば、"x + y * 10" のような複雑な式が正しく解析されるかなどをテストします。

これらの新しいテストファイルは、exp/types/staging パッケージの堅牢性を高め、Go言語の型チェッカーの継続的な開発と改善をサポートするための重要な基盤となります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(go/token, go/scanner, go/parser, go/ast パッケージに関する情報)
  • Go言語のexpパッケージに関する一般的な情報
  • 型チェッカー、AST、コンパイラの基本概念に関する一般的なプログラミング知識