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

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

このコミットは、Goコンパイラ(cmd/gc)における配列の比較可能性に関する挙動の修正です。具体的には、要素型が比較可能でない場合でも、長さがゼロの配列が比較可能であると誤って判断されていた問題を修正します。これにより、Go言語の配列の比較可能性に関する一般的なルール(配列の要素型が比較可能である場合にのみ、その配列も比較可能である)に準拠するようになります。

コミット

commit 502958ffa65440dd79a31f831114d51a610242ef
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Jan 31 00:30:56 2014 +0100

    cmd/gc: do not consider length zero arrays as comparable.
    
    Array values are comparable if values of the array element type
    are comparable.
    
    Fixes #6526.
    
    LGTM=khr
    R=rsc, bradfitz, khr
    CC=golang-codereviews
    https://golang.org/cl/58580043

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

https://github.com/golang/go/commit/502958ffa65440dd79a31f831114d51a610242ef

元コミット内容

cmd/gc: do not consider length zero arrays as comparable.cmd/gc: 長さゼロの配列を比較可能とみなさない。)

Array values are comparable if values of the array element type are comparable. (配列の値は、配列の要素型が比較可能である場合に比較可能である。)

Fixes #6526. (Issue #6526を修正。)

変更の背景

Go言語では、特定の型の値は比較可能(comparable)であり、==!= 演算子を使って比較できます。配列の比較可能性に関する一般的なルールは、「配列の要素型が比較可能である場合にのみ、その配列も比較可能である」というものです。例えば、int 型は比較可能なので [5]int は比較可能ですが、func() 型は比較可能ではないので [5]func() は比較できません。

しかし、このコミット以前のGoコンパイラ(cmd/gc)には、長さがゼロの配列(例: [0]func())が、その要素型が比較可能であるかどうかにかかわらず、常に比較可能であると誤って判断されるバグが存在しました。これは、Goの型システムにおける比較可能性のルールに違反しており、予期せぬ挙動やコンパイルエラーの欠如を引き起こす可能性がありました。

この問題は、Go issue #6526として報告され、このコミットによって修正されました。この修正の目的は、長さゼロの配列も他の配列と同様に、その要素型の比較可能性に依存するようにし、Go言語の型システムの整合性を保つことです。

前提知識の解説

Goにおける型の「比較可能性 (comparability)」

Go言語では、すべての型が比較可能であるわけではありません。比較可能な型は、== および != 演算子を使用して値を比較できます。比較可能な型の例としては、数値型(int, float64など)、文字列型、ブール型、ポインタ型、チャネル型、インターフェース型(動的な型と値が両方とも比較可能な場合)、そして比較可能な要素型を持つ配列や構造体があります。

一方、比較不可能な型としては、スライス、マップ、関数があります。これらの型は、==!= 演算子で直接比較することはできません(ただし、スライスやマップは nil との比較は可能です)。

Goにおける配列の比較ルール

Goの配列は、その要素型が比較可能である場合にのみ比較可能です。

  • 要素型が比較可能な場合: 配列のすべての要素が順番に比較されます。すべての要素が等しい場合にのみ、配列全体が等しいと判断されます。 例: [3]int は比較可能。
  • 要素型が比較不可能な場合: その配列型自体も比較不可能になります。 例: [3][]int (スライスは比較不可能なので) は比較不可能。[3]func() (関数は比較不可能なので) も比較不可能。

cmd/gc の役割

cmd/gc は、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要なバックエンドです。型チェック、最適化、コード生成など、コンパイルプロセスの多くの重要な段階を担当します。このコミットで修正された algtype1 関数は、cmd/gc の内部で型の比較可能性を判断するために使用される関数です。

algtype1 関数の役割

algtype1 関数は、Goコンパイラの型システムの一部であり、与えられた型が比較可能であるかどうか、または比較可能でない理由(例えば、ANOEQ は比較不可能、AMEM はメモリ比較可能だが要素型に依存)を判断します。この関数は、==!= などの比較演算子が使用される際に、その操作が型システム的に有効であるかを検証するために呼び出されます。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/subr.c 内の algtype1 関数から特定の条件分岐を削除した点にあります。

変更前の algtype1 関数には、配列型を処理する部分で以下のコードがありました。

		if(t->bound == 0)
			return AMEM;

ここで、t は検査対象の型を表し、t->bound は配列の長さを表します。この条件 t->bound == 0 は、「もし配列の長さがゼロならば、その配列は AMEM(メモリ比較可能、つまり比較可能)であるとみなす」という意味を持っていました。

このロジックは、長さゼロの配列が特殊なケースとして扱われ、その要素型が比較可能であるかどうかにかかわらず、常に比較可能であると判断される原因となっていました。例えば、[0]func() のような配列は、func() 型が比較不可能であるにもかかわらず、この条件によって比較可能とされてしまっていたのです。

このコミットでは、上記の if(t->bound == 0) の行が削除されました。これにより、長さゼロの配列も他の配列と同様に、algtype1 関数内で要素型(t->type)の比較可能性(algtype1(t->type, bad) の結果)に依存して比較可能性が判断されるようになりました。結果として、[0]func() のような非比較可能な要素型を持つゼロ長配列は、正しく比較不可能であると判断されるようになります。

test/cmp6.go に追加されたテストケースは、この変更が意図通りに機能することを確認しています。特に、[1]func()[0]func() の比較がコンパイルエラーになることを期待する // ERROR コメントが追加されており、修正後のコンパイラの挙動を検証しています。

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

src/cmd/gc/subr.c

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -590,8 +590,6 @@ algtype1(Type *t, Type **bad)\n 		\t\t*bad = t;\n 		\treturn ANOEQ;\n 		}\n-\t\tif(t->bound == 0)\n-\t\t\treturn AMEM;\n \t\ta = algtype1(t->type, bad);\n \t\tif(a == ANOEQ || a == AMEM) {\n \t\t\tif(a == ANOEQ && bad)\

この変更では、algtype1 関数内の配列型を処理する部分から、if(t->bound == 0) という条件分岐が削除されています。

test/cmp6.go

--- a/test/cmp6.go
+++ b/test/cmp6.go
@@ -51,6 +54,14 @@ func main() {\n 	use(p3 == p1)\n 	use(p3 == p2)\n \n+\t// Arrays are comparable if and only if their element type is comparable.\n+\tvar a1 [1]int\n+\tvar a2 [1]func()\n+\tvar a3 [0]func()\n+\tuse(a1 == a1)\n+\tuse(a2 == a2) // ERROR \"invalid operation|invalid comparison\"\n+\tuse(a3 == a3) // ERROR \"invalid operation|invalid comparison\"\n+\n \t// Comparison of structs should have a good message\n \tuse(t3 == t3) // ERROR \"struct|expected\"\n \tuse(t4 == t4) // ERROR \"cannot be compared|non-comparable\"\

このテストファイルには、配列の比較可能性を検証するための新しいテストケースが追加されています。

  • a1 [1]int: 比較可能な要素型を持つ配列。use(a1 == a1) は成功するはずです。
  • a2 [1]func(): 比較不可能な要素型を持つ配列。use(a2 == a2) はコンパイルエラーになるはずです(// ERROR コメントで示されている)。
  • a3 [0]func(): 比較不可能な要素型を持つゼロ長配列。use(a3 == a3) もコンパイルエラーになるはずです(// ERROR コメントで示されている)。

コアとなるコードの解説

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

src/cmd/gc/subr.calgtype1 関数は、Goコンパイラが型の比較可能性を判断する際の中心的なロジックを含んでいます。削除された if(t->bound == 0) return AMEM; の行は、配列の長さがゼロの場合に、その配列を無条件に比較可能(AMEM)とマークしていました。

この行が削除されたことで、長さゼロの配列も、他の長さの配列と同様に、その要素型(t->type)の比較可能性に依存して判断されるようになりました。つまり、algtype1 関数は、配列の要素型が比較可能であるかどうかを再帰的にチェックし、その結果に基づいて配列全体の比較可能性を決定するようになります。これにより、Go言語の型システムにおける配列の比較可能性に関する一貫性が保たれます。

test/cmp6.go の変更

test/cmp6.go に追加されたテストケースは、この修正の有効性を直接的に検証します。

  • var a1 [1]int: int 型は比較可能なので、[1]int も比較可能です。use(a1 == a1) は期待通りに成功します。
  • var a2 [1]func(): func() 型は比較不可能です。したがって、Goのルールに従えば [1]func() も比較不可能であるべきです。この行の use(a2 == a2) の後に // ERROR "invalid operation|invalid comparison" が付いているのは、コンパイラがこの比較を無効な操作として検出し、エラーを報告することを期待しているためです。
  • var a3 [0]func(): これがこのコミットの核心となるテストケースです。以前のコンパイラでは、長さがゼロであるために a3 は比較可能と誤って判断されていました。しかし、func() 型は比較不可能です。修正後、a3a2 と同様に比較不可能と判断され、use(a3 == a3) はコンパイルエラーになることが期待されます。このテストケースは、ゼロ長配列の特殊な扱いが排除され、一般的な配列の比較ルールが適用されるようになったことを明確に示しています。

これらのテストケースは、コンパイラがGoの型システムルールに厳密に従い、非比較可能な要素型を持つ配列(長さがゼロであっても)の比較を正しく禁止することを確認するために不可欠です。

関連リンク

参考にした情報源リンク

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

このコミットは、Goコンパイラ(cmd/gc)における配列の比較可能性に関する挙動の修正です。具体的には、要素型が比較可能でない場合でも、長さがゼロの配列が比較可能であると誤って判断されていた問題を修正します。これにより、Go言語の配列の比較可能性に関する一般的なルール(配列の要素型が比較可能である場合にのみ、その配列も比較可能である)に準拠するようになります。

コミット

commit 502958ffa65440dd79a31f831114d51a610242ef
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Jan 31 00:30:56 2014 +0100

    cmd/gc: do not consider length zero arrays as comparable.
    
    Array values are comparable if values of the array element type
    are comparable.
    
    Fixes #6526.
    
    LGTM=khr
    R=rsc, bradfitz, khr
    CC=golang-codereviews
    https://golang.org/cl/58580043

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

https://github.com/golang/go/commit/502958ffa65440dd79a31f831114d51a610242ef

元コミット内容

cmd/gc: do not consider length zero arrays as comparable.cmd/gc: 長さゼロの配列を比較可能とみなさない。)

Array values are comparable if values of the array element type are comparable. (配列の値は、配列の要素型が比較可能である場合に比較可能である。)

Fixes #6526. (Issue #6526を修正。)

変更の背景

Go言語では、特定の型の値は比較可能(comparable)であり、==!= 演算子を使って比較できます。配列の比較可能性に関する一般的なルールは、「配列の要素型が比較可能である場合にのみ、その配列も比較可能である」というものです。例えば、int 型は比較可能なので [5]int は比較可能ですが、func() 型は比較可能ではないので [5]func() は比較できません。

しかし、このコミット以前のGoコンパイラ(cmd/gc)には、長さがゼロの配列(例: [0]func())が、その要素型が比較可能であるかどうかにかかわらず、常に比較可能であると誤って判断されるバグが存在しました。これは、Goの型システムにおける比較可能性のルールに違反しており、予期せぬ挙動やコンパイルエラーの欠如を引き起こす可能性がありました。

この問題は、Go issue #6526として報告され、このコミットによって修正されました。この修正の目的は、長さゼロの配列も他の配列と同様に、その要素型の比較可能性に依存するようにし、Go言語の型システムの整合性を保つことです。

前提知識の解説

Goにおける型の「比較可能性 (comparability)」

Go言語では、すべての型が比較可能であるわけではありません。比較可能な型は、== および != 演算子を使用して値を比較できます。比較可能な型の例としては、数値型(int, float64など)、文字列型、ブール型、ポインタ型、チャネル型、インターフェース型(動的な型と値が両方とも比較可能な場合)、そして比較可能な要素型を持つ配列や構造体があります。

一方、比較不可能な型としては、スライス、マップ、関数があります。これらの型は、==!= 演算子で直接比較することはできません(ただし、スライスやマップは nil との比較は可能です)。

Goにおける配列の比較ルール

Goの配列は、その要素型が比較可能である場合にのみ比較可能です。

  • 要素型が比較可能な場合: 配列のすべての要素が順番に比較されます。すべての要素が等しい場合にのみ、配列全体が等しいと判断されます。 例: [3]int は比較可能。
  • 要素型が比較不可能な場合: その配列型自体も比較不可能になります。 例: [3][]int (スライスは比較不可能なので) は比較不可能。[3]func() (関数は比較不可能なので) も比較不可能。

cmd/gc の役割

cmd/gc は、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要なバックエンドです。型チェック、最適化、コード生成など、コンパイルプロセスの多くの重要な段階を担当します。このコミットで修正された algtype1 関数は、cmd/gc の内部で型の比較可能性を判断するために使用される関数です。

algtype1 関数の役割

algtype1 関数は、Goコンパイラの型システムの一部であり、与えられた型が比較可能であるかどうか、または比較可能でない理由(例えば、ANOEQ は比較不可能、AMEM はメモリ比較可能だが要素型に依存)を判断します。この関数は、==!= などの比較演算子が使用される際に、その操作が型システム的に有効であるかを検証するために呼び出されます。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/subr.c 内の algtype1 関数から特定の条件分岐を削除した点にあります。

変更前の algtype1 関数には、配列型を処理する部分で以下のコードがありました。

		if(t->bound == 0)
			return AMEM;

ここで、t は検査対象の型を表し、t->bound は配列の長さを表します。この条件 t->bound == 0 は、「もし配列の長さがゼロならば、その配列は AMEM(メモリ比較可能、つまり比較可能)であるとみなす」という意味を持っていました。

このロジックは、長さゼロの配列が特殊なケースとして扱われ、その要素型が比較可能であるかどうかにかかわらず、常に比較可能であると判断される原因となっていました。例えば、[0]func() のような配列は、func() 型が比較不可能であるにもかかわらず、この条件によって比較可能とされてしまっていたのです。

このコミットでは、上記の if(t->bound == 0) の行が削除されました。これにより、長さゼロの配列も他の配列と同様に、algtype1 関数内で要素型(t->type)の比較可能性(algtype1(t->type, bad) の結果)に依存して比較可能性が判断されるようになりました。結果として、[0]func() のような非比較可能な要素型を持つゼロ長配列は、正しく比較不可能であると判断されるようになります。

test/cmp6.go に追加されたテストケースは、この変更が意図通りに機能することを確認しています。特に、[1]func()[0]func() の比較がコンパイルエラーになることを期待する // ERROR コメントが追加されており、修正後のコンパイラの挙動を検証しています。

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

src/cmd/gc/subr.c

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -590,8 +590,6 @@ algtype1(Type *t, Type **bad)\n 		\t\t*bad = t;\n 		\treturn ANOEQ;\n 		}\n-\t\tif(t->bound == 0)\n-\t\t\treturn AMEM;\n \t\ta = algtype1(t->type, bad);\n \t\tif(a == ANOEQ || a == AMEM) {\n \t\t\tif(a == ANOEQ && bad)\

この変更では、algtype1 関数内の配列型を処理する部分から、if(t->bound == 0) という条件分岐が削除されています。

test/cmp6.go

--- a/test/cmp6.go
+++ b/test/cmp6.go
@@ -51,6 +54,14 @@ func main() {\n 	use(p3 == p1)\n 	use(p3 == p2)\n \n+\t// Arrays are comparable if and only if their element type is comparable.\n+\tvar a1 [1]int\n+\tvar a2 [1]func()\n+\tvar a3 [0]func()\n+\tuse(a1 == a1)\n+\tuse(a2 == a2) // ERROR \"invalid operation|invalid comparison\"\n+\tuse(a3 == a3) // ERROR \"invalid operation|invalid comparison\"\n+\n \t// Comparison of structs should have a good message\n \tuse(t3 == t3) // ERROR \"struct|expected\"\n \tuse(t4 == t4) // ERROR \"cannot be compared|non-comparable\"\

このテストファイルには、配列の比較可能性を検証するための新しいテストケースが追加されています。

  • a1 [1]int: 比較可能な要素型を持つ配列。use(a1 == a1) は成功するはずです。
  • a2 [1]func(): 比較不可能な要素型を持つ配列。use(a2 == a2) はコンパイルエラーになるはずです(// ERROR コメントで示されている)。
  • a3 [0]func(): 比較不可能な要素型を持つゼロ長配列。use(a3 == a3) もコンパイルエラーになるはずです(// ERROR コメントで示されている)。

コアとなるコードの解説

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

src/cmd/gc/subr.calgtype1 関数は、Goコンパイラが型の比較可能性を判断する際の中心的なロジックを含んでいます。削除された if(t->bound == 0) return AMEM; の行は、配列の長さがゼロの場合に、その配列を無条件に比較可能(AMEM)とマークしていました。

この行が削除されたことで、長さゼロの配列も、他の長さの配列と同様に、その要素型(t->type)の比較可能性に依存して判断されるようになりました。つまり、algtype1 関数は、配列の要素型が比較可能であるかどうかを再帰的にチェックし、その結果に基づいて配列全体の比較可能性を決定するようになります。これにより、Go言語の型システムにおける配列の比較可能性に関する一貫性が保たれます。

test/cmp6.go の変更

test/cmp6.go に追加されたテストケースは、この修正の有効性を直接的に検証します。

  • var a1 [1]int: int 型は比較可能なので、[1]int も比較可能です。use(a1 == a1) は期待通りに成功します。
  • var a2 [1]func(): func() 型は比較不可能です。したがって、Goのルールに従えば [1]func() も比較不可能であるべきです。この行の use(a2 == a2) の後に // ERROR "invalid operation|invalid comparison" が付いているのは、コンパイラがこの比較を無効な操作として検出し、エラーを報告することを期待しているためです。
  • var a3 [0]func(): これがこのコミットの核心となるテストケースです。以前のコンパイラでは、長さがゼロであるために a3 は比較可能と誤って判断されていました。しかし、func() 型は比較不可能です。修正後、a3a2 と同様に比較不可能と判断され、use(a3 == a3) はコンパイルエラーになることが期待されます。このテストケースは、ゼロ長配列の特殊な扱いが排除され、一般的な配列の比較ルールが適用されるようになったことを明確に示しています。

これらのテストケースは、コンパイラがGoの型システムルールに厳密に従い、非比較可能な要素型を持つ配列(長さがゼロであっても)の比較を正しく禁止することを確認するために不可欠です。

関連リンク

参考にした情報源リンク