[インデックス 11354] ファイルの概要
このコミットは、Goコンパイラの型スイッチにおける静的なimplements
チェックの挙動を修正するものです。具体的には、型スイッチのcase
句でインターフェース型が指定された場合に、コンパイラが誤って「到達不可能な型スイッチケース」と判断してしまうバグ(Issue 2700)を修正しています。この変更により、コンパイラは具象型(concrete type)に対してのみimplements
チェックを適用するようになります。
コミット
commit 0e919ff2c978294c9b0472055b96bb1a09606934
Author: Luuk van Dijk <lvd@golang.org>
Date: Tue Jan 24 13:53:00 2012 +0100
gc: static implements check on typeswitches only applies to concrete case types.
Fixes #2700.
R=rsc
CC=golang-dev
https://golang.org/cl/5574046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0e919ff2c978294c9b0472055b96bb1a09606934
元コミット内容
gc: static implements check on typeswitches only applies to concrete case types.
Fixes #2700.
R=rsc
CC=golang-dev
https://golang.org/cl/5574046
変更の背景
Go言語のコンパイラ(gc
)は、型スイッチ(type switch
)のcase
句において、指定された型がスイッチ対象のインターフェース型を実装しているかどうかを静的にチェックする機能を持っています。このチェックは、例えばinterface{}
型の変数に対してcase string:
のような具象型を指定した場合に、その具象型がインターフェースを実装しているか(この場合は常に真)を検証し、もし実装していない場合はコンパイル時に「impossible type switch case」(到達不可能な型スイッチケース)というエラーを出すことで、開発者に潜在的なバグを知らせる役割があります。
しかし、この静的チェックには問題がありました。case
句でインターフェース型が指定された場合にも、コンパイラが誤ってimplements
チェックを適用してしまい、結果として不適切なエラーを報告することがありました。
具体的には、Issue 2700で報告された問題は、以下のようなシナリオで発生しました。
package main
import (
"io"
)
func main() {
var r io.Reader
// この行はコンパイルエラーにならない
_, _ = r.(io.Writer) // rがio.Writerインターフェースを実装しているかどうかの型アサーション
switch r.(type) {
case io.Writer: // ここでコンパイルエラーが発生していた
// rがio.Writerインターフェースを実装している場合に実行される
}
}
io.Reader
型の変数r
に対して、io.Writer
インターフェースへの型アサーション(r.(io.Writer)
)はGoの仕様上合法であり、実行時にr
がio.Writer
を実装していれば成功し、そうでなければパニックまたは第二戻り値でfalse
が返されます。しかし、同じロジックを型スイッチのcase io.Writer:
として記述すると、コンパイラが「io.Reader
はio.Writer
を実装できない」と誤解し、「impossible type switch case」エラーを出力していました。
これは、インターフェース型はそれ自体がメソッドセットを持つため、別のインターフェース型を「実装する」という概念が具象型とは異なるためです。インターフェース型A
がインターフェース型B
を実装するということは、A
のメソッドセットがB
のメソッドセットを完全に含むことを意味します。この関係は実行時に動的に評価されるべきであり、静的なimplements
チェックの対象とはなりませんでした。
このコミットは、この誤った静的チェックの適用範囲を修正し、型スイッチのcase
句が具象型である場合にのみimplements
チェックを行うようにすることで、この問題を解決することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とコンパイラの挙動に関する知識が必要です。
-
インターフェース (Interfaces):
- Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。
- ある型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」とみなされます(暗黙的な実装)。
- インターフェース型の変数は、そのインターフェースを実装する任意の具象型の値を保持できます。
- インターフェース型は、それ自体がメソッドセットを持つため、別のインターフェース型を「実装する」という概念も存在します。これは、あるインターフェースのメソッドセットが別のインターフェースのメソッドセットを完全に含む場合に成立します。
-
型スイッチ (Type Switch):
switch x.(type)
構文は、インターフェース型の変数が保持している具象型に基づいて異なるコードパスを実行するために使用されます。case T:
のT
は、具象型(string
,int
,MyStruct
など)またはインターフェース型(io.Reader
,error
など)のいずれかを指定できます。- コンパイラは、型スイッチの
case
句が到達可能かどうかを静的に分析しようとします。
-
型アサーション (Type Assertion):
x.(T)
構文は、インターフェース型の変数x
が特定の型T
を保持しているかどうかをチェックし、もしそうであればその型T
の値として抽出するために使用されます。T
が具象型の場合、x
がその具象型を保持していなければパニックが発生するか、ok
変数がfalse
になります(v, ok := x.(T)
の場合)。T
がインターフェース型の場合、x
がそのインターフェース型を実装していれば成功します。これは、x
が保持する具象型がT
のメソッドセットをすべて実装している場合に真となります。
-
Goコンパイラ (
gc
):- Goの公式コンパイラは
gc
と呼ばれます。 - コンパイラは、ソースコードを解析し、構文チェック、型チェック、最適化などを行い、実行可能なバイナリを生成します。
- 型チェックの段階で、静的に検出可能なエラー(例: 未定義の変数、型不一致、到達不可能なコードパス)を報告します。
- Goの公式コンパイラは
-
implements
関数 (コンパイラ内部):- Goコンパイラの内部には、ある型が別のインターフェースを実装しているかどうかをチェックする
implements
のような関数が存在します。 - この関数は、主に具象型がインターフェースを実装しているかを静的に判断するために使用されます。
- Goコンパイラの内部には、ある型が別のインターフェースを実装しているかどうかをチェックする
このコミットの核心は、コンパイラがimplements
チェックを適用する際に、case
句の型が「具象型」であるか「インターフェース型」であるかを適切に区別する必要があるという点です。インターフェース型に対するimplements
チェックは、具象型に対するそれとは異なるセマンティクスを持ち、静的に「不可能」と判断すべきではないケースが存在します。
技術的詳細
このコミットの技術的詳細は、Goコンパイラの型チェックフェーズにおけるimplements
チェックのロジックにあります。
Goコンパイラのsrc/cmd/gc/swt.c
ファイルは、型スイッチの処理を担当する部分です。typecheckswitch
関数は、型スイッチの各case
句を型チェックする際に呼び出されます。
変更前のコードでは、typecheckswitch
関数内で、case
句の型(ll->n->type
)がスイッチ対象のインターフェース型(t
)を実装しているかどうかをimplements
関数でチェックしていました。このチェックは、case string:
のように具象型が指定された場合には適切に機能し、例えばinterface{}
型の変数に対してcase string:
と書かれた場合、string
型はinterface{}
を常に実装するため問題ありません。しかし、もしcase
句の型がスイッチ対象のインターフェース型を実装できない具象型であれば、「impossible type switch case」エラーが報告されます。
問題は、case io.Writer:
のようにcase
句にインターフェース型が指定された場合にも、このimplements
チェックがそのまま適用されてしまっていた点です。io.Reader
型の変数がio.Writer
インターフェースを実装しているかどうかは、そのio.Reader
が保持している具象型に依存します。例えば、io.Reader
が*bytes.Buffer
を保持している場合、*bytes.Buffer
はio.Writer
も実装しているため、case io.Writer:
は到達可能です。しかし、io.Reader
が*os.File
を保持している場合、*os.File
はio.Writer
を実装していないため、case io.Writer:
は到達不可能です。
このように、インターフェース型に対する型スイッチのcase
は、そのインターフェースが保持する動的な具象型によって到達可能性が変化するため、静的に「不可能」と断定することはできませんでした。
このコミットでは、この問題を解決するために、implements
関数を呼び出す前にll->n->type->etype != TINTER
という条件を追加しています。
ll->n->type
:型スイッチのcase
句で指定された型。etype
:Goコンパイラ内部で型を表す列挙型(TINTER
はインターフェース型を意味します)。
この条件は、「case
句の型がインターフェース型ではない場合(つまり、具象型である場合)にのみ、implements
チェックを実行する」という意味になります。
もしcase
句の型がインターフェース型(TINTER
)であれば、implements
チェックはスキップされます。これにより、コンパイラはインターフェース型に対する型スイッチのcase
を静的に「不可能」と誤って判断することがなくなり、実行時の動的な型チェックに委ねられるようになります。
テストファイルtest/typeswitch3.go
の変更は、この修正の意図を明確にするものです。変更前は、case io.Writer:
の行でコンパイルエラーが発生することを期待していましたが、修正後はエラーが発生しないことを期待するようになっています。これは、インターフェース型に対する型スイッチのcase
は、静的に「不可能」と判断されるべきではないという新しい挙動を反映しています。
コアとなるコードの変更箇所
src/cmd/gc/swt.c
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -889,7 +889,7 @@ typecheckswitch(Node *n)
tyyerror("%lN is not a type", ll->n);
// reset to original type
ll->n = n->ntest->right;
- } else if(!implements(ll->n->type, t, &missing, &have, &ptr)) {
+ } else if(ll->n->type->etype != TINTER && !implements(ll->n->type, t, &missing, &have, &ptr)) {
if(have && !missing->broke && !have->broke)
yyerror("impossible type switch case: %lN cannot have dynamic type %T"
" (wrong type for %S method)\n\thave %S%hT\n\twant %S%hT",
test/typeswitch3.go
--- a/test/typeswitch3.go
+++ b/test/typeswitch3.go
@@ -6,15 +6,30 @@
package main
+import (
+ "io"
+)
+
type I interface {
- M()
+ M()
}
func main(){
- var x I
- switch x.(type) {
- case string: // ERROR "impossible"
- println("FAIL")
- }
+ var x I
+ switch x.(type) {
+ case string: // ERROR "impossible"
+ println("FAIL")
+ }
+
+ // Issue 2700: if the case type is an interface, nothing is impossible
+
+ var r io.Reader
+
+ _, _ = r.(io.Writer)
+
+ switch r.(type) {
+ case io.Writer:
+ }
}
+
+
コアとなるコードの解説
src/cmd/gc/swt.c
の変更
変更の中心は、typecheckswitch
関数内の以下の行です。
- } else if(!implements(ll->n->type, t, &missing, &have, &ptr)) {
+ } else if(ll->n->type->etype != TINTER && !implements(ll->n->type, t, &missing, &have, &ptr)) {
ll->n->type
: これは、型スイッチのcase
句で指定された型を表すコンパイラ内部の型オブジェクトです。t
: これは、型スイッチの対象となっているインターフェース変数の型(例:var x I
のI
)を表すコンパイラ内部の型オブジェクトです。implements(ll->n->type, t, ...)
: この関数は、ll->n->type
がt
を実装しているかどうかを静的にチェックします。もし実装していない場合、false
を返します。ll->n->type->etype != TINTER
: この新しい条件が追加されました。etype
はGoコンパイラ内部で型を分類するための列挙型であり、TINTER
はインターフェース型を示します。- この条件は、「
case
句の型がインターフェース型ではない場合」を意味します。 - つまり、
case
句の型が具象型(string
,int
, 構造体など)である場合にのみ、続くimplements
チェックが実行されます。 - もし
case
句の型がインターフェース型(例:io.Writer
)であれば、この条件はfalse
となり、implements
関数は呼び出されず、静的な「impossible」エラーの判定がスキップされます。
- この条件は、「
この変更により、コンパイラはインターフェース型がcase
句に指定された場合に、その到達可能性を静的に判断しようとすることをやめ、実行時の動的な型アサーションのセマンティクスに合致するようになりました。
test/typeswitch3.go
の変更
このテストファイルは、修正の意図と効果を明確にするために更新されました。
import ("io")
の追加:io.Reader
とio.Writer
を使用するためにio
パッケージがインポートされました。- 既存のテストケースの維持:
この部分は変更されていません。var x I switch x.(type) { case string: // ERROR "impossible" println("FAIL") }
I
インターフェースはM()
メソッドを要求しますが、string
型はM()
メソッドを実装していません。したがって、x
がstring
型であることは静的に不可能であり、コンパイラは引き続きERROR "impossible"
を報告することが期待されます。これは、具象型に対するimplements
チェックが引き続き正しく機能することを示しています。 - Issue 2700 のテストケースの追加:
この新しいテストケースが追加されました。// Issue 2700: if the case type is an interface, nothing is impossible var r io.Reader _, _ = r.(io.Writer) switch r.(type) { case io.Writer: }
var r io.Reader
:io.Reader
型の変数を宣言します。_, _ = r.(io.Writer)
:これは、io.Reader
型の変数r
がio.Writer
インターフェースを実装しているかどうかをチェックする型アサーションです。これはGoの仕様上合法であり、実行時にr
が保持する具象型がio.Writer
を実装していれば成功します。switch r.(type) { case io.Writer: }
:これがIssue 2700で問題となっていた部分です。修正前はここで「impossible type switch case」エラーが発生していましたが、修正後はエラーが発生しないことが期待されます。コメント// Issue 2700: if the case type is an interface, nothing is impossible
は、このテストケースの目的を明確に示しています。つまり、case
句がインターフェース型である場合、コンパイラは静的に「不可能」と判断すべきではない、という新しい挙動を検証しています。
これらの変更により、コンパイラは型スイッチのcase
句における静的な型チェックの挙動がより正確になり、Go言語のインターフェースの動的な性質と整合性が取れるようになりました。
関連リンク
- Go Issue 2700: https://github.com/golang/go/issues/2700 (このコミットメッセージに記載されているIssue番号)
- Go Code Review 5574046: https://golang.org/cl/5574046 (このコミットに対応するGoのコードレビューシステムのエントリ)
参考にした情報源リンク
- Go言語の公式ドキュメント (インターフェース、型スイッチ、型アサーションに関するセクション)
- Goコンパイラのソースコード (
src/cmd/gc/swt.c
の関連部分) - Go言語のIssueトラッカー (Issue 2700の議論)
- Go言語のコードレビューシステム (CL 5574046の議論)
- Go言語の型システムに関する一般的な解説記事やブログポスト