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

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

このコミットは、Go言語の型チェッカーの実験的なパッケージ exp/types における重要な改善とバグ修正を含んでいます。特に、type switch 文と range 句の型チェック機能が大幅に強化され、型アサーションの扱いが改善され、可変引数関数の初期的なサポートが追加されています。また、組み込みの error 型の実装も行われています。

コミット

commit 69d0f0cc473d42fd7a49125d6b4667fe301c0d86
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Dec 6 09:21:30 2012 -0800

    exp/types: checking of type switches and range clauses
    
    Also:
    - better handling of type assertions
    - implemented built-in error type
    - first cut at handling variadic function signatures
    - several bug fixes
    
    R=rsc, rogpeppe
    CC=golang-dev
    https://golang.org/cl/6846131

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

https://github.com/golang/go/commit/69d0f0cc473d42fd7a49125d6b4667fe301c0d86

元コミット内容

exp/types: checking of type switches and range clauses

Also:
- better handling of type assertions
- implemented built-in error type
- first cut at handling variadic function signatures
- several bug fixes

R=rsc, rogpeppe
CC=golang-dev
https://golang.org/cl/6846131

変更の背景

このコミットが行われた2012年12月は、Go言語がまだ比較的新しい時期であり、言語仕様の策定とコンパイラ/ツールチェインの実装が活発に進められていました。exp/types パッケージは、Goの型システムと型チェックロジックを実験的に開発するためのものでした。

当時のGo言語では、type switchfor...range といった制御構造、そして可変引数関数や型アサーションといった機能が既に存在していましたが、それらの型チェックの厳密性や正確性にはまだ改善の余地がありました。特に、Goのインターフェース型と具象型の間の関係性、そしてそれらが動的な型チェックにどのように影響するかは、型チェッカーにとって複雑な課題でした。

このコミットの主な目的は、これらの言語機能に対する型チェックのロジックを強化し、より堅牢で正確なコンパイル時エラー検出を可能にすることでした。これにより、開発者はより信頼性の高いコードを書くことができ、Go言語の型システムが意図する安全性と表現力を最大限に引き出すことが期待されました。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の概念と型チェックの基本を把握しておく必要があります。

  • Go言語の型システム: Goは静的型付け言語であり、変数は宣言時に型を持ちます。型チェックは、プログラムが型の規則に従っていることをコンパイル時に検証するプロセスです。
  • AST (Abstract Syntax Tree): Goのソースコードは、パーサーによって抽象構文木(AST)に変換されます。型チェッカーはこのASTを走査し、各ノードの型情報を解決し、型規則に違反がないかを確認します。
  • インターフェース型: Goのインターフェースは、メソッドのシグネチャの集合を定義します。ある型がインターフェースのすべてのメソッドを実装していれば、その型は自動的にそのインターフェースを実装しているとみなされます(構造的型付け)。
  • 型アサーション (x.(T)): インターフェース型の値 x が、特定の具象型 T であるかどうかを動的にチェックし、もしそうであればその具象型の値として取り出すための構文です。
  • type switch: インターフェース型の値の動的な型に基づいて、異なるコードブロックを実行するための制御構造です。switch x.(type) の形式で記述されます。
  • for...range: スライス、配列、文字列、マップ、チャネルなどのコレクションを反復処理するための制御構造です。
  • 可変引数関数 (func(...T)): 最後のパラメータの前に ... を付けることで、任意の数の引数を受け取ることができる関数です。可変引数は関数内部ではスライスとして扱われます。
  • exp/types パッケージ: Goの標準ライブラリ go/types パッケージの前身となる実験的な型チェッカーの実装です。Go言語の進化の過程で、型チェックのロジックを開発・検証するために使用されました。

技術的詳細

このコミットは、Goの型チェッカーの複数の側面を改善しています。

  1. type switch 文の型チェックの強化:

    • type switch のガード (switch x.(type)) の構文と意味論がより厳密にチェックされるようになりました。特に、x がインターフェース型であることの検証が強化されています。
    • case 句で指定された型が、スイッチ対象のインターフェース型を正しく実装しているかどうかのチェックが導入されました。これは、新しく追加された missingMethod 関数によって行われます。missingMethod は、ある型が特定のインターフェースを実装しているかどうか、または実装していない場合にどのメソッドが不足しているか、あるいはシグネチャが間違っているかを判断します。
    • case 句内で宣言される変数の型が、その case に対応する型に正しく設定されるようになりました。特に、単一の型が指定された case ではその型に、複数の型が指定された casedefault 句では元のインターフェース型に設定されます。
    • 複数の default 句が存在する場合のエラー検出が追加されました。
  2. for...range 句の型チェックの強化:

    • for...range の対象となる式の型に基づいて、イテレーション変数(キーと値)の型が正確に推論され、チェックされるようになりました。
    • 文字列、配列、スライス、ポインタ配列、マップ、チャネルに対する range の振る舞いが適切に型チェックされます。
    • 特に、チャネルに対する range の場合、送信専用チャネル (chan<- T) を range できないことや、イテレーション変数が1つしか許されないことなどがチェックされます。
    • 無効な range 対象に対するエラーメッセージが改善されました。
  3. 型アサーションの改善:

    • 型アサーション x.(T) において、x がインターフェース型でない場合にエラーが報告されるようになりました。以前は「non-interface type」という一般的なエラーでしたが、より具体的な「not an interface」というメッセージに変わっています。
    • アサーションされる型 T が、x のインターフェース型を正しく実装しているかどうかのチェックに missingMethod 関数が活用されるようになりました。これにより、コンパイル時にインターフェースの実装不足やメソッドシグネチャの不一致を検出できるようになりました。
    • .(type) 構文が type switch の外部で使用された場合にエラーを報告するようになりました。
  4. 可変引数関数の初期サポート:

    • collectParams 関数が variadicOk パラメータを受け取るようになり、可変引数 (...T) の処理がより正確になりました。最後のパラメータが可変引数である場合にのみ isVariadictrue に設定されます。
    • 可変引数関数の最後のパラメータの型が、関数内部でスライス型 ([]T) として扱われるように変更されました。ただし、外部から見える型は元の要素型 T のまま保持されます。
    • 関数呼び出しにおける引数の型チェック (argument 関数) が、可変引数関数のシグネチャを考慮するようになりました。
  5. 組み込み error 型の実装:

    • src/pkg/exp/types/universe.go において、組み込みの error インターフェースが、Error() string メソッドを持つインターフェースとして明示的に定義されるようになりました。これにより、型チェッカーが error 型を正しく認識し、そのメソッドを解決できるようになります。
  6. バグ修正とコードのクリーンアップ:

    • check.go での collectParams の呼び出しに false が追加され、メソッドレシーバの型チェックがより正確になりました。
    • const.gonilTypeUnsafePointer にも表現可能であるとマークされました。
    • expr.go での callRecordrecordType にリネームされ、より汎用的な型記録メカニズムになりました。
    • ポインタ配列 (*[N]T) のインデックス付けやスライス化の型チェックが追加されました。
    • テストデータ (decls1.src, expr3.src, stmt0.src) が更新され、新しい型チェックルールが反映されています。

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

このコミットのコアとなる変更は、主に src/pkg/exp/types/stmt.gosrc/pkg/exp/types/expr.go、そして src/pkg/exp/types/predicates.go に集中しています。

  1. src/pkg/exp/types/predicates.go における missingMethod 関数の追加:

    // missingMethod returns (nil, false) if typ implements T, otherwise
    // it returns the first missing method required by T and whether it
    // is missing or simply has the wrong type.
    func missingMethod(typ Type, T *Interface) (method *ast.Object, wrongType bool) {
        // ... (implementation details for interface and concrete types)
    }
    

    この関数は、型アサーションや type switch におけるインターフェース適合性のチェックの基盤となります。

  2. src/pkg/exp/types/stmt.go における TypeSwitchStmt の型チェックロジック:

    case *ast.TypeSwitchStmt:
        // ...
        // rhs must be of the form: expr.(type) and expr must be an interface
        // ...
        var T *Interface
        if T, _ = underlying(x.typ).(*Interface); T == nil {
            check.errorf(x.pos(), "%s is not an interface", &x)
            return
        }
    
        check.multipleDefaults(s.Body.List)
        for _, s := range s.Body.List {
            clause, _ := s.(*ast.CaseClause)
            // ...
            // Check each type in this type switch case.
            var typ Type
            for _, expr := range clause.List {
                typ = check.typOrNil(expr, false)
                if typ != nil && typ != Typ[Invalid] {
                    if method, wrongType := missingMethod(typ, T); method != nil {
                        // ... report error for missing/wrong method ...
                    }
                }
            }
            // If lhs exists, set its type for each clause.
            if lhs != nil {
                if len(clause.List) != 1 || typ == nil {
                    typ = x.typ
                }
                lhs.Type = typ
            }
            check.stmtList(clause.Body)
        }
        // ...
    

    このセクションは、type switch のガードの検証、case 句の型のインターフェース適合性チェック、および case 句内の変数 lhs の型設定を処理します。

  3. src/pkg/exp/types/stmt.go における RangeStmt の型チェックロジック:

    case *ast.RangeStmt:
        // ...
        // determine key/value types
        var key, val Type
        switch typ := underlying(x.typ).(type) {
        case *Basic:
            if isString(typ) {
                key = Typ[UntypedInt]
                val = Typ[UntypedRune]
            }
        case *Array:
            key = Typ[UntypedInt]
            val = typ.Elt
        case *Slice:
            key = Typ[UntypedInt]
            val = typ.Elt
        case *Pointer:
            if typ, _ := underlying(typ.Base).(*Array); typ != nil {
                key = Typ[UntypedInt]
                val = typ.Elt
            }
        case *Map:
            key = typ.Key
            val = typ.Elt
        case *Chan:
            key = typ.Elt
            if typ.Dir&ast.RECV == 0 {
                check.errorf(x.pos(), "cannot range over send-only channel %s", &x)
            }
            if s.Value != nil {
                check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x)
            }
        }
        // ... (error handling for invalid range types)
        // check assignment to/declaration of iteration variables
        // ...
    

    このセクションは、range 対象の型に基づいてキーと値の型を決定し、それらの型がイテレーション変数に正しく割り当てられるかをチェックします。

  4. src/pkg/exp/types/expr.go における型アサーションの改善:

    case *ast.TypeAssertExpr:
        check.expr(x, e.X, hint, iota)
        if x.mode == invalid {
            goto Error
        }
        var T *Interface
        if T, _ = underlying(x.typ).(*Interface); T == nil {
            check.errorf(x.pos(), "%s is not an interface", x)
            goto Error
        }
        // x.(type) expressions are handled explicitly in type switches
        if e.Type == nil {
            check.errorf(e.Pos(), "use of .(type) outside type switch")
            goto Error
        }
        typ := check.typ(e.Type, false)
        if typ == Typ[Invalid] {
            goto Error
        }
        if method, wrongType := missingMethod(typ, T); method != nil {
            // ... report error for missing/wrong method ...
            // ok to continue
        }
        x.mode = valueok
        x.expr = e
        x.typ = typ
    

    このコードは、型アサーションの対象がインターフェース型であることの確認、.(type) の誤用チェック、そして missingMethod を用いたインターフェース適合性の検証を行います。

  5. src/pkg/exp/types/expr.go における可変引数関数のパラメータ収集 (collectParams):

    func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params ObjList, isVariadic bool) {
        // ...
        if t, _ := ftype.(*ast.Ellipsis); t != nil {
            if variadicOk && i == len(list.List)-1 {
                isVariadic = true
            } else {
                check.invalidAST(field.Pos(), "... not permitted")
                // ok to continue
            }
        }
        // ...
        // 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).
        if isVariadic {
            copy := *last
            last.Type = &Slice{Elt: last.Type.(Type)}
            params[len(params)-1] = &copy
        }
        return
    }
    

    この関数は、可変引数パラメータの正しい識別と、関数内部でのスライス型への変換を処理します。

コアとなるコードの解説

上記の変更箇所は、Go言語の型チェックの正確性と堅牢性を大幅に向上させています。

  • missingMethod 関数: この関数は、Goのインターフェースの構造的型付けの核心をなすものです。ある具象型がインターフェースを実装しているかどうかを判断するために、インターフェースが要求するすべてのメソッドが具象型に存在し、かつそのシグネチャが一致するかどうかを検証します。この関数が導入されたことで、型アサーションや type switch において、より詳細で正確なコンパイル時エラーメッセージを提供できるようになりました。例えば、メソッドが完全に不足しているのか、それともシグネチャが間違っているのかを区別して報告できます。

  • TypeSwitchStmt の型チェック: type switch はGoの強力な動的型チェック機能ですが、その型チェックは複雑です。このコミットでは、スイッチ対象がインターフェース型であることの厳密な検証、各 case 句で指定された型がそのインターフェースを実際に実装しているかどうかの missingMethod を用いたチェック、そして case 句内で宣言される変数の型がその case のコンテキストに応じて正しく設定されるロジックが追加されました。これにより、type switch の誤用や型不一致によるランタイムエラーをコンパイル時に捕捉できるようになりました。

  • RangeStmt の型チェック: for...range 句は、Goのコレクション型を扱う上で非常に便利ですが、その振る舞いはコレクションの型によって異なります。このコミットでは、range の対象が文字列、配列、スライス、マップ、チャネルのいずれであるかに応じて、イテレーション変数 keyval の型を正確に推論し、それらが正しく使用されているかを検証するロジックが実装されました。特に、チャネルの range における送信専用チャネルの制限や、イテレーション変数の数の制限など、Goの仕様に厳密に従ったチェックが行われるようになりました。これにより、range 句の誤った使用によるコンパイルエラーや予期せぬ振る舞いを防ぎます。

  • 可変引数関数のパラメータ処理: 可変引数関数はGoの柔軟な機能ですが、その型チェックは通常の関数とは異なります。collectParams 関数における variadicOk パラメータの導入と、最後の可変引数パラメータの型を関数内部でスライス型として扱うロジックは、可変引数関数の正しい型チェックを可能にします。これにより、可変引数関数への引数の渡し方や、関数内部での可変引数の使用方法に関する型エラーを正確に検出できるようになりました。

これらの変更は、Go言語の型チェッカーの成熟度を高め、開発者がより安全で信頼性の高いGoプログラムを記述するための基盤を強化するものでした。

関連リンク

  • Go CL 6846131: https://golang.org/cl/6846131 - このコミットに対応するGoの変更リスト(Change List)です。Goのコミットは通常、このようなCLとしてレビュー・承認されます。

参考にした情報源リンク

  • Go言語仕様: Go言語の型システム、type switchfor...range、可変引数関数、型アサーションに関する公式な定義は、Go言語仕様に記載されています。
  • Goの型チェックに関する資料: go/types パッケージやGoのコンパイラに関する技術文書は、Goの公式ドキュメントやブログで参照できます。
  • GoのASTパッケージ: go/ast パッケージのドキュメントは、GoのAST構造を理解する上で役立ちます。
  • Goのトークンパッケージ: go/token パッケージのドキュメントは、Goの字句要素を理解する上で役立ちます。