[インデックス 1666] ファイルの概要
このコミットでは、Goコンパイラのランタイム部分、特に型アサーションとインターフェースの扱いに関連するファイルが変更されています。具体的には以下の3つのファイルが修正されました。
src/cmd/gc/go.h
: Goコンパイラのグローバルヘッダーファイルで、型定義や関数プロトタイプが宣言されています。インターフェース関連の関数のシグネチャが変更されています。src/cmd/gc/subr.c
: Goコンパイラのサブルーチンが含まれるファイルで、型チェックやインターフェースの適合性チェックに関するロジックが実装されています。このコミットの主要な変更点が含まれています。src/cmd/gc/walk.c
: GoコンパイラのAST (Abstract Syntax Tree) ウォーカーが含まれるファイルで、コードの変換や型チェックの際にASTを走査します。インターフェース関連の関数呼び出しが更新されています。
コミット
commit 5f4f5647efbea27b90ffc034e931082f843e6333
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 11 17:57:29 2009 -0800
require type assertions when narrowing.
R=ken
OCL=24350
CL=24914
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5f4f5647efbea27b90ffc034e931082f843e6333
元コミット内容
require type assertions when narrowing.
R=ken
OCL=24350
CL=24914
変更の背景
このコミットは、Go言語における型アサーションの振る舞いをより厳密にするための変更です。特に、インターフェース型から具体的な型へ、あるいはより狭いインターフェース型へと「ナローイング(絞り込み)」を行う際に、明示的な型アサーションを要求するようにコンパイラのチェックを強化しています。
Go言語の初期設計段階において、インターフェースの柔軟性と型安全性のバランスは重要な検討事項でした。インターフェースは異なる具象型を抽象的に扱う強力なメカニズムですが、その内部に保持されている具体的な値の型を知る必要がある場合、型アサーション (value.(Type)
) を使用します。
このコミット以前は、コンパイラが暗黙的に型をナローイングするような状況、例えば、あるインターフェース型が別のインターフェース型に代入される際に、そのインターフェースが持つメソッドセットが部分的にしか満たされていない場合でも、エラーが報告されにくいケースがあったと考えられます。この変更は、このような暗黙的なナローイングを許容せず、開発者が意図的に型を絞り込む場合にのみ、明示的な型アサーションを記述することを強制することで、より堅牢なコードを記述できるようにすることを目的としています。これにより、コンパイル時に潜在的な型不一致エラーを早期に発見し、実行時エラーのリスクを低減します。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは、JavaやC#のような明示的なimplements
キーワードを必要とせず、型がインターフェースで定義されたすべてのメソッドを実装していれば、そのインターフェースを満たすと見なされます(構造的型付け)。
例:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
型アサーション (Type Assertion)
型アサーションは、インターフェース型の変数が保持している基になる具体的な値の型を検査し、その値にアクセスするために使用されます。
構文: t := i.(T)
i
はインターフェース型の変数。T
は検査したい型(具象型または別のインターフェース型)。
型アサーションには2つの形式があります。
- 単一の値の型アサーション:
t := i.(T)
i
がT
型の値を保持していない場合、パニック (panic) が発生します。
- 「カンマOK」イディオム (Comma-ok idiom):
t, ok := i.(T)
i
がT
型の値を保持している場合、t
にその値が代入され、ok
はtrue
になります。i
がT
型の値を保持していない場合、t
はT
型のゼロ値になり、ok
はfalse
になります。パニックは発生しません。この形式が推奨されます。
型のナローイング (Type Narrowing)
型のナローイングとは、より広い(汎用的な)型から、より狭い(具体的な)型へと、変数の型を絞り込むプロセスを指します。Go言語では、インターフェース型から具象型への変換や、より広いインターフェース型からより狭いインターフェース型への変換がこれに該当します。
例えば、interface{}
(空インターフェース、任意の型を保持できる)から string
型への変換はナローイングです。また、ReadWriter
インターフェースから Reader
インターフェースへの変換は、メソッドセットが広がる方向なのでナローイングではありませんが、Reader
から ReadWriter
への変換は、メソッドセットが狭まる方向なのでナローイングと見なされる場合があります(このコミットの文脈では、インターフェース間の適合性チェックがより厳密になったことを指します)。
このコミットの「narrowing」という言葉は、特にインターフェースの適合性チェックにおいて、より具体的な型への変換や、メソッドセットがより厳密に一致する必要がある状況を指しています。
技術的詳細
このコミットの主要な変更は、Goコンパイラのインターフェース適合性チェックロジックに、明示的な型アサーションが必要かどうかを示すexplicit
という新しいフラグを導入した点です。
src/cmd/gc/go.h
の変更
ifaceas
、ifaceas1
、ifacecheck
の3つの関数プロトタイプに int explicit
という新しい引数が追加されました。
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -827,9 +827,9 @@ Type* fixchan(Type*);
Node* chanop(Node*, int);
Node* arrayop(Node*, int);
Node* ifaceop(Type*, Node*, int);
-int ifaceas(Type*, Type*);
-int ifaceas1(Type*, Type*);
-void ifacecheck(Type*, Type*, int);
+int ifaceas(Type*, Type*, int);
+int ifaceas1(Type*, Type*, int);
+void ifacecheck(Type*, Type*, int, int);
void runifacechecks(void);
Node* convas(Node*);
void arrayconv(Type*, Node*);
ifaceas
: インターフェース間の型変換の可能性をチェックする関数。ifaceas1
:ifaceas
の内部で使われる、より低レベルなインターフェース型変換チェック関数。ifacecheck
: インターフェースの適合性チェックをキューに入れる関数。
この explicit
引数は、その型変換がコード内で明示的に記述された型アサーションによるものか(explicit
が 1
)、それとも暗黙的な型変換の文脈で発生したものか(explicit
が 0
)を示します。
src/cmd/gc/subr.c
の変更
このファイルでは、ifacecheck
関数の定義が変更され、Icheck
構造体に explicit
フィールドが追加されました。また、インターフェース適合性チェックの主要なロジックである runifacechecks
関数が大幅に修正されました。
-
Icheck
構造体の変更:Icheck
構造体は、後で実行されるインターフェース適合性チェックの情報を保持します。ここにint explicit;
フィールドが追加され、チェックが明示的なアサーションによるものかどうかの情報が保存されるようになりました。--- a/src/cmd/gc/subr.c +++ b/src/cmd/gc/subr.c @@ -2735,12 +2735,13 @@ struct Icheck Type *dst; Type *src; int lineno; + int explicit; }; Icheck *icheck; Icheck *ichecktail; void -ifacecheck(Type *dst, Type *src, int lineno) +ifacecheck(Type *dst, Type *src, int lineno, int explicit) { Icheck *p; @@ -2752,6 +2753,7 @@ ifacecheck(Type *dst, Type *src, int lineno) p->dst = dst; p->src = src; p->lineno = lineno; + p->explicit = explicit; ichecktail = p; }
-
ifaceokT2I
およびifaceokI2I
関数の追加/変更:hasiface
関数がifaceokT2I
にリネームされ、非インターフェース型t
がインターフェース型iface
を満たすかどうかをチェックするようになりました。ifaceokI2I
関数が新しく追加され、インターフェース型i1
が別のインターフェース型i2
を満たすかどうかをチェックするようになりました。これは、インターフェースのメソッドセットの包含関係をチェックします。
--- a/src/cmd/gc/subr.c +++ b/src/cmd/gc/subr.c @@ -2761,6 +2763,9 @@ ifacelookdot(Sym *s, Type *t) int c, d; Type *m; + if(t == T) + return T; + for(d=0; d<nelem(dotlist); d++) { c = adddot1(s, t, d, &m); if(c > 1) { @@ -2773,15 +2778,15 @@ ifacelookdot(Sym *s, Type *t) return T; } +// check whether non-interface type t +// satisifes inteface type iface. int -hasiface(Type *t, Type *iface, Type **m) +ifaceokT2I(Type *t, Type *iface, Type **m) { Type *im, *tm; int imhash; t = methtype(t); - if(t == T) - return 0; // if this is too slow, // could sort these first @@ -2805,26 +2810,66 @@ hasiface(Type *t, Type *iface, Type **m) return 1; } +// check whether interface type i1 satisifes interface type i2. +int +ifaceokI2I(Type *i1, Type *i2, Type **m) +{ + Type *m1, *m2; + + // if this is too slow, + // could sort these first + // and then do one loop. + + for(m2=i2->type; m2; m2=m2->down) { + for(m1=i1->type; m1; m1=m1->down) + if(m1->sym == m2->sym && typehash(m1, 0) == typehash(m2, 0)) + goto found; + *m = m2; + return 0; + found:; + } + return 1; +}
-
runifacechecks
関数のロジック変更: この関数は、コンパイル中にキューに入れられたすべてのインターフェース適合性チェックを実行します。変更後、この関数はexplicit
フラグを考慮し、エラーメッセージをより詳細に生成するようになりました。p->explicit
が0
(暗黙的変換) でneedexplicit
が1
(明示的変換が必要) の場合、yyerror("need explicit conversion...")
というエラーを出力します。これは、コンパイラが暗黙的に型をナローイングしようとしたが、それが許可されない場合に発生します。wrong
が1
の場合(型が適合しない場合)、yyerror("%T is not %T...")
というエラーを出力します。
--- a/src/cmd/gc/subr.c +++ b/src/cmd/gc/subr.c @@ -2810,26 +2855,40 @@ runifacechecks(void) void runifacechecks(void) { Icheck *p; - int lno; - Type *m, *l, *r; + int lno, wrong, needexplicit; + Type *m, *t, *iface; lno = lineno; for(p=icheck; p; p=p->next) { lineno = p->lineno; - if(isinter(p->dst)) { - l = p->src; - r = p->dst; + wrong = 0; + needexplicit = 0; + m = nil; + if(isinter(p->dst) && isinter(p->src)) { + iface = p->dst; + t = p->src; + needexplicit = !ifaceokI2I(t, iface, &m); + } + else if(isinter(p->dst)) { + t = p->src; + iface = p->dst; + wrong = !ifaceokT2I(t, iface, &m); } else { - l = p->dst; - r = p->src; + t = p->dst; + iface = p->src; + wrong = !ifaceokT2I(t, iface, &m); + needexplicit = 1; + } + if(wrong) + yyerror("%T is not %T\n\tmissing %S%hhT", + t, iface, m->sym, m->type); + else if(!p->explicit && needexplicit) { + if(m) + yyerror("need explicit conversion to use %T as %T\n\tmissing %S%hhT", + p->src, p->dst, m->sym, m->type); + else + yyerror("need explicit conversion to use %T as %T", + p->src, p->dst); } - if(!hasiface(l, r, &m)) - yyerror("%T is not %T - missing %S%hhT", - l, r, m->sym, m->type); } lineno = lno; }
src/cmd/gc/walk.c
の変更
このファイルでは、ifaceas
および ifaceas1
関数の呼び出し箇所に、新しい explicit
引数として 1
または 0
が渡されるようになりました。
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -513,7 +513,7 @@ loop:
walktype(r->left, Erv);
if(r->left == N)
break;
- et = ifaceas1(r->type, r->left->type);
+ et = ifaceas1(r->type, r->left->type, 1);
switch(et) {
case I2T:
et = I2T2;
@@ -651,7 +651,7 @@ loop:
}
// interface assignment
- et = ifaceas(n->type, l->type);
+ et = ifaceas(n->type, l->type, 1);
if(et != Inone) {
indir(n, ifaceop(n->type, l, et));
goto ret;
@@ -2812,7 +2812,7 @@ arrayop(Node *n, int top)
* return op to use.
*/
int
-ifaceas1(Type *dst, Type *src)
+ifaceas1(Type *dst, Type *src, int explicit)
{
if(src == T || dst == T)
return Inone;
@@ -2821,17 +2821,17 @@ ifaceas1(Type *dst, Type *src)
if(isinter(src)) {
if(eqtype(dst, src, 0))
return I2Isame;
+ if(!isnilinter(dst))
+ ifacecheck(dst, src, lineno, explicit);
return I2I;
}
if(isnilinter(dst))
return T2I;
- ifacecheck(dst, src, lineno);
+ ifacecheck(dst, src, lineno, explicit);
return T2I;
}
if(isinter(src)) {
- if(isnilinter(src))
- return I2T;
- ifacecheck(dst, src, lineno);
+ ifacecheck(dst, src, lineno, explicit);
return I2T;
}
return Inone;
@@ -2841,11 +2841,11 @@ ifaceas1(Type *dst, Type *src)
* treat convert T to T as noop
*/
int
-ifaceas(Type *dst, Type *src)
+ifaceas(Type *dst, Type *src, int explicit)
{
int et;
- et = ifaceas1(dst, src);
+ et = ifaceas1(dst, src, explicit);
if(et == I2Isame)
et = Inone;
return et;
@@ -2987,7 +2987,7 @@ convas(Node *n)\n if(eqtype(lt, rt, 0))\n goto out;\n\n- et = ifaceas(lt, rt);\n+ et = ifaceas(lt, rt, 0);\n if(et != Inone) {\n n->right = ifaceop(lt, r, et);\n goto out;\n```
* `ifaceas1` の呼び出しでは、明示的な型アサーションの文脈であるため `explicit` に `1` が渡されています。
* `ifaceas` の呼び出しでは、代入などの暗黙的な変換の文脈であるため `explicit` に `0` が渡されています。
これらの変更により、コンパイラは型変換の際に、それが明示的なアサーションによるものか、暗黙的な代入によるものかを区別できるようになり、暗黙的なナローイングに対してより厳密なチェックを適用することが可能になりました。
## コアとなるコードの変更箇所
### `src/cmd/gc/subr.c` における `runifacechecks` 関数の変更
```c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2810,26 +2855,40 @@ runifacechecks(void)
void
runifacechecks(void)
{
Icheck *p;
- int lno;
- Type *m, *l, *r;
+ int lno, wrong, needexplicit;
+ Type *m, *t, *iface;
lno = lineno;
for(p=icheck; p; p=p->next) {
lineno = p->lineno;
- if(isinter(p->dst)) {
- l = p->src;
- r = p->dst;
+ wrong = 0;
+ needexplicit = 0;
+ m = nil;
+ if(isinter(p->dst) && isinter(p->src)) {
+ iface = p->dst;
+ t = p->src;
+ needexplicit = !ifaceokI2I(t, iface, &m);
+ }
+ else if(isinter(p->dst)) {
+ t = p->src;
+ iface = p->dst;
+ wrong = !ifaceokT2I(t, iface, &m);
} else {
- l = p->dst;
- r = p->src;
+ t = p->dst;
+ iface = p->src;
+ wrong = !ifaceokT2I(t, iface, &m);
+ needexplicit = 1;
+ }
+ if(wrong)
+ yyerror("%T is not %T\n\tmissing %S%hhT",
+ t, iface, m->sym, m->type);
+ else if(!p->explicit && needexplicit) {
+ if(m)
+ yyerror("need explicit conversion to use %T as %T\n\tmissing %S%hhT",
+ p->src, p->dst, m->sym, m->type);
+ else
+ yyerror("need explicit conversion to use %T as %T",
+ p->src, p->dst);
}
- if(!hasiface(l, r, &m))
- yyerror("%T is not %T - missing %S%hhT",
- l, r, m->sym, m->type);
}
lineno = lno;
}
コアとなるコードの解説
runifacechecks
関数は、コンパイラが収集したすべてのインターフェース適合性チェックを最終的に実行する場所です。この変更により、チェックのロジックが大幅に洗練されました。
-
型変換の分類:
if(isinter(p->dst) && isinter(p->src))
: 変換元と変換先が両方ともインターフェース型の場合。この場合、ifaceokI2I
を使用してインターフェース間の適合性をチェックし、明示的な変換が必要かどうか (needexplicit
) を判断します。else if(isinter(p->dst))
: 変換先がインターフェース型で、変換元が非インターフェース型の場合(例: 具象型からインターフェース型への変換)。ifaceokT2I
を使用して適合性をチェックし、適合しない場合はwrong
フラグを立てます。else
: 変換元がインターフェース型で、変換先が非インターフェース型の場合(例: インターフェース型から具象型への変換)。これもifaceokT2I
を使用して適合性をチェックし、適合しない場合はwrong
フラグを立てます。このケースでは常に明示的な変換が必要 (needexplicit = 1
) と見なされます。
-
エラーメッセージの生成:
if(wrong)
: 型が適合しない場合、yyerror("%T is not %T...")
というエラーメッセージを出力します。これは、型アサーションが失敗した場合や、インターフェースがメソッドを満たしていない場合に表示されます。else if(!p->explicit && needexplicit)
: ここがこのコミットの核心です。!p->explicit
: チェックが明示的な型アサーションによるものではない(つまり、暗黙的な変換の文脈である)ことを意味します。needexplicit
: しかし、この変換には明示的な型アサーションが必要であると判断された場合。- この両方の条件が真の場合、
yyerror("need explicit conversion to use %T as %T...")
というエラーメッセージが出力されます。これは、コンパイラが暗黙的に型をナローイングしようとしたが、それが許可されず、開発者に明示的な型アサーションの記述を促すものです。
このロジックの変更により、Goコンパイラは、インターフェースの適合性チェックにおいて、より厳密なルールを適用し、特にインターフェースから具象型への変換や、より狭いインターフェース型への変換において、明示的な型アサーションを強制するようになりました。これにより、開発者はコードの意図をより明確に表現し、コンパイル時に潜在的な型関連のエラーを捕捉できるようになります。
関連リンク
- Go言語のインターフェース: https://go.dev/tour/methods/10
- Go言語の型アサーション: https://go.dev/tour/methods/15
- Go言語の仕様 - 型アサーション: https://go.dev/ref/spec#Type_assertions
参考にした情報源リンク
- stackoverflow.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHqsqb-Ze2L1asDXL1asDXLtkXz2MoV1UrcB5rTRGbAxBfGXwtOkqO2tsnhhELs9_kWXlgaGT2bk1I6Q_-pLZ6vGM4cyJKqmK33v1Oxeq0V_IbGxtv3Hq4VojcA1JsyTIaLltGWlxBVlXDtvp9a9KbfJgWRiZf3b7PKRuRM3kBxcytLNjvqhl7Gr8)
- devgenius.io (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF3KyIKke_L48YQ5SjeJcImignB4TrlxrWwvbU3BCa5HLKJVJ3WjrBfzxWkDJy2CJABAb7f0jB4d7E--hZmBxnB2slpcQ9e8efnLMr1XcV_alTKhxkuQqM2Cdp3FlTFj3WCMDHQTSW1TBNJepy7X9a3JAph8lY7ASBQUwwv39kKomA_LcaWGUxS2LYGMusU6FCGM5OX-zExw==)
- geeksforgeeks.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEypLJoOB3rcWE-qIBKeI2SeQ_5EwE99rQzVxcNCMNA0kZ6bFMFhr3J_znPiU9VXCdhh5cXBpuv2WP9zUycKCxWj6_aqtL9ydKBr62ZQ6FzTRE_HHWHQiw2JlASO8b5KJv_UUmgICvjdupoDpDSkFJYNqrrQ-hIiq-tI82oFa3w6QAs1bKqUGGb)
- medium.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE2QcwTIkQqwjFUSar1dmX6eX3UP0mGpkNf_M6zNowkcZ--7IAVJ6iu8ljWAN3o1oOP1juZQL9GO-VH70qejsdjky7SlyGQZ0ZPI-h7BR4M0tkJKXtahBN5u9y7_tCMYw6QXN2QghK0dtidpnuEMaEwzc4SaotxwOgpl14Ekf4ksC-_BZliSF7OoeSXBT7o5Em-aHB6AKqDkZA_tAryej0=)
- labex.io (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFAIi2P8v-_L0ao66vJg1IH3MSh8-oFJAqflFoIBja-LAfx3RITlYQNAMTLo5Vv69mpgp6g6AZDgBfs3Jsa4ItIW0NcsWE5M0VDW18c5wz-Dere0-uG7t0QgMP9Vot-y14plo8zttapSBWnCykqGmukfL2Rvlo0zYnfnBrapz58qCoKwKhwtK9xYp5_Y7ofQwF1EtXK)
- golang.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF0wkmrCNRSedNcd74DedxKBCmrr7R0sb9mBh5ior3upN--_aves_n8jC6rigkDf5PG7cKYCM0rWBw5SBXotnOH7vH3SMjLZRxq__kAeo_BnAxnHnt-U6osUHc=)
- go.dev (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQELgicZ6F0DHlNoi47oOF9IJ6Q0QNOIYC4E8R5470gNig2-P_h_eJWYdwzpAcHFua7aPsV551IC6ijX0XYnOmGhsQiF9IQtUZmnZMUrRL5GFB2EBzabXLbSUg==)
- wikipedia.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHFANmfo11n37TzNhcC2jGkqcrDnTR-iy_6zbq5NbKAPhovy41Z7erfw8LdS3zhSjZ1KVJrUjZXrZ_pVePxnKNiByeWCTmeEl5XkuLTJUBu-o6n_YLmo11Bh0pbaB89DnY4PolL-U-mtx0HFwACVw==)