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

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

このコミットは、Goコンパイラの内部コードベース、具体的にはsrc/cmd/gcディレクトリ内のファイルを変更しています。gcはGo言語の初期のコンパイラであり、Goプログラムのコンパイル時に型チェックやコード生成を担当していました。

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

  • src/cmd/gc/go.h: Goコンパイラのヘッダーファイルで、型定義や関数プロトタイプが宣言されています。
  • src/cmd/gc/go.y: Go言語の文法定義を記述したYacc(またはBison)の入力ファイルです。Goの構文解析器の生成に使用されます。
  • src/cmd/gc/subr.c: Goコンパイラのサブルーチン(補助関数)を実装したC言語のソースファイルです。型システムやその他のコンパイラ内部処理に関連する関数が含まれます。

コミット

reject invalid map key types at compile time

R=ken OCL=25720 CL=25720

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

https://github.com/golang/go/commit/98b34e5bbd6c67cbe20e589d1f74acda8364fb12

元コミット内容

reject invalid map key types at compile time

R=ken
OCL=25720
CL=25720

変更の背景

Go言語のマップ(map)は、キーと値のペアを格納するデータ構造です。Goのマップのキーには、比較可能な型(comparable types)のみを使用できるという重要な制約があります。これは、マップがキーの等価性をチェックするためにハッシュと等価性比較を使用するためです。

このコミット以前は、Goコンパイラがマップのキーとして比較不可能な型(例: スライス、関数、構造体で比較不可能なフィールドを持つもの)が指定された場合、コンパイル時にエラーを検出していませんでした。その結果、実行時エラーや予期せぬ動作につながる可能性がありました。

この変更の背景には、Go言語の型システムの堅牢性を高め、開発者がより安全で予測可能なコードを書けるようにするという目的があります。コンパイル時にエラーを検出することで、開発者は問題を早期に特定し、修正することができます。

前提知識の解説

Go言語のマップ(map)

Goのマップは、map[KeyType]ValueTypeという構文で宣言されます。KeyTypeはマップのキーの型、ValueTypeはマップの値の型です。マップのキーとして使用できる型は、Go言語の仕様によって厳密に定義されています。具体的には、ブール型、数値型、文字列型、ポインタ型、チャネル型、インターフェース型、構造体型(すべてのフィールドが比較可能である場合)、配列型(すべての要素が比較可能である場合)が比較可能です。スライス、関数、および比較不可能なフィールドを持つ構造体は比較不可能です。

Goコンパイラ(gc)

gcは、Go言語の公式コンパイラの実装の一つです。Goのソースコードを解析し、中間表現に変換し、最終的に実行可能なバイナリを生成します。このコミットが対象としているのは、gcのフロントエンド部分、特に型チェックと構文解析に関連する部分です。

Yacc/Bison (go.y)

go.yファイルは、Go言語の文法を定義するためにYacc(Yet Another Compiler Compiler)またはそのGNU版であるBisonが使用する入力ファイルです。Yacc/Bisonは、文法定義に基づいて構文解析器(パーサー)を生成するツールです。go.yでは、Go言語の各構文要素(例: 変数宣言、関数定義、マップリテラル)がどのように解析されるかが記述されています。

型システムと比較可能性

プログラミング言語の型システムは、プログラムのデータ型を定義し、それらの型がどのように相互作用するかを規定します。Goの型システムでは、「比較可能性(comparability)」という概念が重要です。これは、2つの値が等しいかどうかを比較できるかどうかを指します。マップのキーは、その性質上、比較可能である必要があります。

algtype関数とANOEQ

このコミットの核心部分の一つは、algtype関数とANOEQという定数です。

  • algtype関数: algtypeは、Goコンパイラの内部で型が持つ「アルゴリズム的な特性」を分類するために使用される関数です。具体的には、その型が比較可能であるか、等価性チェックが可能か、ハッシュ可能かといった特性を判断します。
  • ANOEQ: ANOEQは "Algorithm No Equality" の略で、algtype関数が返す値の一つです。これは、その型が等価性比較をサポートしていない(つまり、比較不可能である)ことを示します。スライスや関数型などがこれに該当します。

このコミットでは、algtype(key) == ANOEQという条件を使って、マップのキーとして指定された型が比較不可能であるかどうかをチェックしています。

技術的詳細

このコミットの主要な技術的変更点は、マップ型を構築する際に、キーの型が有効であるかをコンパイル時にチェックするロジックを導入したことです。

以前は、go.yファイル内でマップ型が定義される際、以下のように直接typ(TMAP)を呼び出し、キーと値の型をdowntypeフィールドに割り当てていました。

$$ = typ(TMAP);
$$->down = $3; // キーの型
$$->type = $5; // 値の型

このコミットでは、この直接的な型構築をmaptypeという新しいヘルパー関数に置き換えました。

$$ = maptype($3, $5);

このmaptype関数はsrc/cmd/gc/subr.cに新しく追加されました。maptype関数は、マップのキーと値の型を受け取り、マップ型を構築する前にキーの型を検証します。

maptype関数の内部では、以下のチェックが行われます。

if(key != nil && key->etype != TANY && algtype(key) == ANOEQ)
    yyerror("invalid map key type %T", key);

このコードは、以下の条件がすべて真である場合にyyerror関数を呼び出してコンパイルエラーを発生させます。

  1. key != nil: キーの型がnilではない。
  2. key->etype != TANY: キーの型がTANY(任意の型)ではない。これは、ジェネリックな型や不明な型に対するチェックをスキップするためと考えられます。
  3. algtype(key) == ANOEQ: キーの型が比較不可能である(algtype関数がANOEQを返す)。

yyerrorはYacc/Bisonによって生成されたパーサーがエラーを報告するために使用する関数で、指定されたメッセージとともにコンパイルエラーを出力します。

また、subr.cにはiskeytypeという補助関数も追加されていますが、このコミットの差分を見る限り、直接maptype関数内で使用されているわけではありません。しかし、iskeytypealgtype(t) != ANOEQを返すため、マップのキーとして有効な型であるかを判断するロジックをカプセル化しています。これは、将来的に他の場所でキーの型チェックが必要になった場合に再利用できるようにするためのものです。

この変更により、Goコンパイラは、スライスや関数など、比較不可能な型がマップのキーとして使用された場合に、コンパイル時に明確なエラーメッセージを出力するようになります。これにより、開発者は実行時まで待つことなく、コードの誤りを修正できるようになります。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -657,6 +657,7 @@ int	isslice(Type*);
 int	isinter(Type*);
 int	isnilinter(Type*);
 int	isddd(Type*);
+Type*	maptype(Type*, Type*);
 Type*	dclmethod(Type*);
 Type*	methtype(Type*);
 int	methconv(Type*);

maptype関数のプロトタイプ宣言が追加されました。

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1019,9 +1019,7 @@ convtype:
 |\tLMAP '[' type ']' type
  \t{
  \t\t// map literal
-\t\t$$ = typ(TMAP);\
-\t\t$$->down = $3;\
-\t\t$$->type = $5;\
+\t\t$$ = maptype($3, $5);\
  \t}
 |\tstructtype
 |\t'(' type ')'
@@ -1126,9 +1124,7 @@ Aothertype:
  \t}
 |\tLMAP '[' type ']' Atype
  \t{
-\t\t$$ = typ(TMAP);\
-\t\t$$->down = $3;\
-\t\t$$->type = $5;\
+\t\t$$ = maptype($3, $5);\
  \t}
 |\t'*' Atype
  \t{
@@ -1160,9 +1156,7 @@ Bothertype:
  \t}
 |\tLMAP '[' type ']' Btype
  \t{
-\t\t$$ = typ(TMAP);\
-\t\t$$->down = $3;\
-\t\t$$->type = $5;\
+\t\t$$ = maptype($3, $5);\
  \t}
 |\t'*' Btype
  \t{
@@ -1806,9 +1800,7 @@ hidden_type1:
  \t}
 |\tLMAP '[' hidden_type ']' hidden_type
  \t{
-\t\t$$ = typ(TMAP);\
-\t\t$$->down = $3;\
-\t\t$$->type = $5;\
+\t\t$$ = maptype($3, $5);\
  \t}
 |\tLSTRUCT '{' ohidden_structdcl_list '}'
  \t{

マップ型を定義する複数の箇所で、直接的な型構築の代わりにmaptype関数が呼び出されるように変更されました。

src/cmd/gc/subr.c

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -305,6 +305,25 @@ algtype(Type *t)
 	return a;
 }
 
+Type*
+maptype(Type *key, Type *val)
+{
+	Type *t;
+
+	if(key != nil && key->etype != TANY && algtype(key) == ANOEQ)
+		yyerror("invalid map key type %T", key);
+	t = typ(TMAP);
+	t->down = key;
+	t->type = val;
+	return t;
+}
+
+int
+iskeytype(Type *t)
+{
+	return algtype(t) != ANOEQ;
+}
+
 Node*
 list(Node *a, Node *b)
 {

maptype関数とiskeytype関数が新しく追加されました。maptype関数内でキーの型チェックが行われています。

コアとなるコードの解説

maptype関数

Type*
maptype(Type *key, Type *val)
{
	Type *t;

	if(key != nil && key->etype != TANY && algtype(key) == ANOEQ)
		yyerror("invalid map key type %T", key);
	t = typ(TMAP);
	t->down = key;
	t->type = val;
	return t;
}

この関数は、マップのキー型(key)と値型(val)を受け取り、新しいマップ型を構築して返します。 最も重要なのはif文によるキーの型チェックです。

  • key != nil: キー型が有効であることを確認します。
  • key->etype != TANY: キー型がTANY(任意の型)ではないことを確認します。これは、型がまだ完全に解決されていない場合や、特殊なケースを扱うためのものです。
  • algtype(key) == ANOEQ: ここが核心です。algtype関数を呼び出してキー型のアルゴリズム的特性を取得し、それがANOEQ(比較不可能)であるかをチェックします。 これらの条件がすべて真の場合、yyerror関数が呼び出され、「invalid map key type %T」というエラーメッセージとともにコンパイルエラーが発生します。%Tは、エラーメッセージ内で実際の無効なキー型に置き換えられます。 エラーチェックの後、typ(TMAP)で新しいマップ型オブジェクトを作成し、downフィールドにキー型を、typeフィールドに値型を設定して返します。

iskeytype関数

int
iskeytype(Type *t)
{
	return algtype(t) != ANOEQ;
}

この関数は、与えられた型tがマップのキーとして有効であるかどうかを判断します。algtype(t) != ANOEQという条件は、型tが比較可能である(つまり、ANOEQではない)場合に真を返します。この関数自体は今回のコミットのmaptype関数内で直接使用されていませんが、マップキーの有効性を判断するための汎用的なユーティリティとして追加されました。

関連リンク

参考にした情報源リンク