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

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

このコミットは、Goコンパイラ(cmd/gc)における型アサーションに関する誤ったエラーメッセージの表示を修正するものです。具体的には、インターフェース型から別の型への代入において、実際には型アサーションでは解決できない場合に「: need type assertion」というヒントが誤って表示される問題を修正しています。また、この修正を検証するためのテストケースも更新されています。

変更されたファイルは以下の2つです。

  • src/cmd/gc/subr.c: Goコンパイラの型チェックおよび型変換に関連するサブルーチンが含まれるC言語のソースファイルです。このファイル内のassignop関数が修正されています。
  • test/interface/explicit.go: Go言語のインターフェースの明示的な型変換に関するテストケースが含まれるファイルです。今回の修正によって、期待されるエラーメッセージが変更されたため、テストケースもそれに合わせて更新されています。

コミット

commit b04c890a89e00f52c11ccccf4c76db389ef57d16
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Wed Aug 15 16:53:06 2012 -0700

    cmd/gc: Don't claim type assertion would help when it wont.
    
    Fixes #3465.
    
    R=golang-dev, rsc, remyoudompheng, iant
    CC=golang-dev
    https://golang.org/cl/6448097

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

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

元コミット内容

cmd/gc: Don't claim type assertion would help when it wont.

Fixes #3465.

R=golang-dev, rsc, remyoudompheng, iant
CC=golang-dev
https://golang.org/cl/6448097

変更の背景

このコミットは、Goコンパイラが特定の型変換のシナリオで誤ったエラーメッセージを生成するバグを修正するために行われました。具体的には、インターフェース型iから具象型tへの代入において、itが持つべきメソッドをすべて実装していない場合、コンパイラは「: need type assertion」というヒントを提示していました。しかし、このケースでは型アサーションを行ってもコンパイルエラーは解消されません。なぜなら、インターフェースiが具象型tのすべてのメソッドを実装していないため、そもそもitとして振る舞うことができないからです。

この誤ったヒントは、開発者を混乱させ、問題の根本原因を特定するのを困難にしていました。このコミットの目的は、コンパイラがより正確なエラーメッセージを提供し、開発者が問題をより迅速に解決できるようにすることです。

前提知識の解説

このコミットを理解するためには、Go言語の以下の概念を理解しておく必要があります。

1. Go言語の型システム

Goは静的型付け言語であり、変数は特定の型を持ちます。型は、その変数が保持できる値の種類と、その値に対して実行できる操作を定義します。

2. インターフェース (Interfaces)

Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たしていると見なされます。明示的に「このインターフェースを実装します」と宣言する必要はありません。

例:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type MyReader struct{}

func (mr MyReader) Read(p []byte) (n int, err error) {
    // 実装
    return 0, nil
}

// MyReaderはReadメソッドを実装しているので、Readerインターフェースを満たす
var r Reader = MyReader{}

3. 型アサーション (Type Assertions)

型アサーションは、インターフェース型の変数が、特定の具象型または別のインターフェース型であるかどうかをチェックし、もしそうであればその型に変換するために使用されます。

構文: value.(Type)

例:

var i interface{} = "hello" // 空のインターフェース

s, ok := i.(string) // iがstring型であるかチェックし、そうであればsに代入
if ok {
    fmt.Println(s) // "hello"
}

n, ok := i.(int) // iがint型ではないので、okはfalse、nは0
if !ok {
    fmt.Println("i is not an int")
}

型アサーションは、インターフェース変数が実際に特定の具象型を保持している場合にのみ成功します。インターフェースがその具象型が持つべきメソッドをすべて実装していない場合、型アサーションはコンパイルエラーまたは実行時パニックを引き起こします。

4. コンパイラのエラーメッセージ

コンパイラは、コードがGo言語の仕様に準拠していない場合にエラーメッセージを出力します。これらのメッセージは、開発者が問題を特定し、修正するのに役立ちます。しかし、誤ったエラーメッセージは開発者を混乱させ、デバッグプロセスを遅らせる可能性があります。

技術的詳細

このコミットの核心は、Goコンパイラのsrc/cmd/gc/subr.cファイルにあるassignop関数内のロジック変更です。assignop関数は、Go言語の代入操作における型互換性をチェックする役割を担っています。

変更前のコードでは、srcがインターフェース型(TINTER)であり、dstがブランク型(TBLANK、つまり_)ではない場合に、whynilでなければ、無条件に*why = ": need type assertion"というヒントを設定していました。これは、インターフェースから具象型への代入で型が一致しない場合に、常に型アサーションを提案するというロジックでした。

しかし、このロジックには問題がありました。srcインターフェースがdst具象型が持つべきすべてのメソッドを実装していない場合、たとえ型アサーションを行ったとしても、その代入は成功しません。このような状況で「: need type assertion」というヒントを出すのは誤りであり、開発者を誤った方向に導くことになります。

このコミットでは、この問題を解決するために、why != nilの条件に加えて、implements(dst, src, &missing, &have, &ptr)という条件を追加しました。

  • implements(dst, src, ...): この関数は、srcインターフェースがdst型を実装しているかどうか(つまり、srcdstのすべてのメソッドを持っているか)をチェックします。

この変更により、コンパイラは、srcインターフェースがdst型を実際に実装している場合にのみ、「: need type assertion」というヒントを提示するようになります。srcdstを実装していない場合は、このヒントは表示されず、より適切なエラーメッセージ(例: 「incompatible|missing M method」)が表示されるようになります。

これにより、コンパイラはより賢明になり、開発者に対してより正確で役立つエラーメッセージを提供するようになります。

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

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -1219,7 +1219,7 @@ assignop(Type *src, Type *dst, char **why)
 		return 0;
 	}
 	if(src->etype == TINTER && dst->etype != TBLANK) {
-		if(why != nil)
+		if(why != nil && implements(dst, src, &missing, &have, &ptr))
 			*why = ": need type assertion";
 		return 0;
 	}
--- a/test/interface/explicit.go
+++ b/test/interface/explicit.go
@@ -40,7 +40,7 @@ func main() {
 	// because i has an extra method
 	// that t does not, so i cannot contain a t.
 	i = t // ERROR "incompatible|missing M method"
-	t = i // ERROR "incompatible|need type assertion"
+	t = i // ERROR "incompatible|assignment$"
 
 	i = i2 // ok
 	i2 = i // ERROR "incompatible|missing N method"

コアとなるコードの解説

src/cmd/gc/subr.c の変更

 	if(src->etype == TINTER && dst->etype != TBLANK) {
-		if(why != nil)
+		if(why != nil && implements(dst, src, &missing, &have, &ptr))
 			*why = ": need type assertion";
 		return 0;
 	}
  • 変更前: if(why != nil)
    • srcがインターフェース型であり、dstがブランク型でない場合、whyポインタがnilでなければ(つまり、エラーメッセージのヒントが必要な場合)、無条件に": need type assertion"というヒントを設定していました。
  • 変更後: if(why != nil && implements(dst, src, &missing, &have, &ptr))
    • whyポインタがnilでないことに加えて、implements(dst, src, ...)関数がtrueを返す場合にのみ、": need type assertion"というヒントを設定するようになりました。
    • implements(dst, src, ...)は、srcインターフェースがdst型を実装しているかどうかをチェックします。つまり、srcdstのすべてのメソッドを持っている場合にのみtrueを返します。
    • この変更により、srcインターフェースがdst型を実際に実装していない(したがって、型アサーションを行っても代入が成功しない)場合には、誤ったヒントが表示されなくなります。

test/interface/explicit.go の変更

 	i = t // ERROR "incompatible|missing M method"
-	t = i // ERROR "incompatible|need type assertion"
+	t = i // ERROR "incompatible|assignment$"
  • 変更前: t = i // ERROR "incompatible|need type assertion"
    • この行は、インターフェースiから具象型tへの代入を試みています。このテストケースのコメントにもあるように、itが持たない余分なメソッドを持っているため、itを含むことができません。
    • 変更前は、コンパイラがこの行に対して「incompatible」または「need type assertion」というエラーメッセージを出すことを期待していました。
  • 変更後: t = i // ERROR "incompatible|assignment$"
    • コンパイラの修正により、このケースでは「: need type assertion」というヒントが誤って表示されなくなりました。
    • そのため、テストケースも、より一般的な「incompatible」または「assignment」というエラーメッセージ(行末を示す$を含む)を期待するように変更されました。これは、型アサーションが役に立たない場合の、より正確なエラーメッセージを反映しています。

これらの変更により、Goコンパイラは型変換に関するエラーメッセージをより正確に、そして開発者にとってより有用なものにするように改善されました。

関連リンク

参考にした情報源リンク