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

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

このコミットは、Go言語のコンパイラ(cmd/gc)における、多値式(multiple-valued expressions)と可変引数(variadic arguments)を組み合わせた際の不正な使用を検出・拒否する変更を導入しています。具体的には、複数の戻り値を返す関数呼び出しに対して、スプレッド演算子(...)を適用しようとした場合にコンパイルエラーを発生させるように修正されています。

コミット

commit 656b192c1672e4d4b874dfd3b510cd459406e50d
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Jul 13 08:05:41 2012 +0200

    cmd/gc: reject use of ... with multiple-valued expressions.
    
    Fixes #3334.
    
    R=golang-dev, r
    CC=golang-dev, remy
    https://golang.org/cl/6350103

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

https://github.com/golang/go/commit/656b192c1672e4d4b874dfd3b510cd459406e50d

元コミット内容

cmd/gc: reject use of ... with multiple-valued expressions.

Fixes #3334.

R=golang-dev, r
CC=golang-dev, remy
https://golang.org/cl/6350103

変更の背景

この変更は、Go言語のIssue #3334「... operator on multiple-value expression」を修正するために行われました。このIssueは、複数の戻り値を返す関数(例: func tuple() (int, int, int))の呼び出し結果に対して、可変引数関数(例: func sum(args ...int))に引数を渡す際にスプレッド演算子(...)を使用しようとすると、コンパイラが不正なコードを生成してしまうというバグを報告していました。

Go言語では、可変引数関数にスライスを渡す際にスプレッド演算子を使用できますが、複数の戻り値を持つ式はスライスとは異なるため、この組み合わせは意味をなしません。しかし、当時のコンパイラはこの不正なパターンを適切に検出できず、コンパイルが通ってしまうか、あるいは予期せぬ動作を引き起こす可能性がありました。このコミットは、このような誤用をコンパイル時にエラーとして明確に拒否することで、Goプログラムの堅牢性と安全性を向上させることを目的としています。

前提知識の解説

Go言語の可変引数(Variadic Functions)

Go言語では、関数の最後のパラメータに...を付けることで、その関数が任意の数の引数を受け取れるように定義できます。これを可変引数関数と呼びます。 例: func sum(nums ...int) int この関数内では、nums[]int型のスライスとして扱われます。

可変引数関数を呼び出す際には、以下の2つの方法があります。

  1. 個々の引数を列挙する: sum(1, 2, 3)
  2. スライスを渡し、その要素を展開する(スプレッド演算子...を使用する): s := []int{1, 2, 3}; sum(s...)

Go言語の多値戻り値(Multiple Return Values)

Go言語の関数は、複数の値を返すことができます。これは、エラーハンドリングや複数の関連する結果を一度に返す際に非常に便利です。 例: func divide(a, b int) (int, error)

スプレッド演算子(...)の役割

Goにおけるスプレッド演算子...は、主に以下の2つの文脈で使用されます。

  1. 可変引数関数の定義: 上述の通り、関数の最後のパラメータが任意の数の引数を受け取ることを示す。
  2. スライスの展開: 可変引数関数を呼び出す際に、スライスの要素を個々の引数として展開する。

このコミットの背景にある問題は、このスプレッド演算子が、スライスではない「多値戻り値」に対して誤って適用された場合に発生していました。多値戻り値は、複数の独立した値の集合であり、スライスのように要素のコレクションとして扱われるものではありません。したがって、多値戻り値にスプレッド演算子を適用することは、Goの型システムとセマンティクスにおいて不正な操作となります。

Goコンパイラのgc

gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。gcは、構文解析、型チェック、最適化、コード生成など、コンパイルの様々な段階を実行します。このコミットで変更されているtypecheck.cは、gcの型チェックフェーズの一部であり、Goプログラムの型の一貫性と正当性を検証する重要な部分です。

技術的詳細

このコミットの核心は、Goコンパイラの型チェックロジックに、多値式に対するスプレッド演算子(...)の不正な使用を検出する条件を追加することです。

変更はsrc/cmd/gc/typecheck.cファイル内のreswitchラベルの直下、具体的には可変引数関数の引数リストを処理する部分で行われています。

元のコードは以下のようになっていました。

		if(count(n->list) == 1)
			typecheck(&n->list->n, Erv | Efnstruct);
		else
			typechecklist(n->list, Erv);

ここで、n->listは関数呼び出しの引数リストを表し、count(n->list)はその引数の数を返します。n->isdddは、その引数リストがスプレッド演算子(...)を使用して展開されているかどうかを示すフラグです。

変更後のコードは以下の通りです。

		if(count(n->list) == 1 && !n->isddd)
			typecheck(&n->list->n, Erv | Efnstruct);
		else
			typechecklist(n->list, Erv);

追加された条件は && !n->isddd です。 これは、「引数リストの要素が1つであり、かつスプレッド演算子が使用されていない場合」にのみ、単一の引数として型チェックを行うというロジックに変更しています。

この変更の意図は以下の通りです。

  • count(n->list) == 1 の意味: これは、関数呼び出しの引数リストが形式的には1つの要素しか持たない場合を指します。通常、これは単一の変数や式が引数として渡されるケースです。しかし、多値戻り値を返す関数呼び出し(例: tuple())も、その結果全体が「1つの多値式」として扱われるため、この条件に合致する可能性があります。
  • n->isddd の意味: このフラグは、引数リストの最後に...(スプレッド演算子)が付いているかどうかを示します。
  • 変更前の問題: 変更前は、tuple()... のように多値式にスプレッド演算子を適用した場合でも、count(n->list) == 1 が真となり、typecheck(&n->list->n, Erv | Efnstruct) が実行されていました。Ervは「右辺値」を意味し、Efnstructは「関数構造体」を意味する型チェックのコンテキストフラグです。このパスでは、多値式が単一の値として扱われ、スプレッド演算子の意味が正しく解釈されず、コンパイラが不正な状態に陥る可能性がありました。
  • 変更後の修正: && !n->isddd を追加することで、tuple()... のように多値式にスプレッド演算子が適用された場合(この場合 n->isddd が真になる)、count(n->list) == 1 && !n->isddd の条件が偽になります。これにより、処理は else ブロックの typechecklist(n->list, Erv) に進みます。typechecklistは引数リスト全体を型チェックする関数であり、この文脈では多値式にスプレッド演算子を適用することが不正であることを検出し、適切なコンパイルエラー("multiple-value in single-value context" や "cannot use ... with multiple-value expression" など)を発生させるようになります。

要するに、この変更は、多値式とスプレッド演算子の組み合わせがGo言語のセマンティクスにおいて不正であることを、コンパイラの型チェック段階で明示的に強制するためのものです。

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

src/cmd/gc/typecheck.c の929行目付近:

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -929,7 +929,7 @@ reswitch:
 		        goto doconv;
 		}
 
-		if(count(n->list) == 1)
+		if(count(n->list) == 1 && !n->isddd)
 			typecheck(&n->list->n, Erv | Efnstruct);
 		else
 			typechecklist(n->list, Erv);

test/ddd1.go の追加:

--- a/test/ddd1.go
+++ b/test/ddd1.go
@@ -22,6 +22,16 @@ var (
 	_ = sum([]int{1}) // ERROR "\[\]int literal.*as type int|incompatible"
 )
 
+func sum3(int, int, int) int { return 0 }
+func tuple() (int, int, int) { return 1, 2, 3 }
+
+var (
+	_ = sum(tuple())
+	_ = sum(tuple()...) // ERROR "multiple-value"
+	_ = sum3(tuple())
+	_ = sum3(tuple()...) // ERROR "multiple-value" "not enough"
+)
+
 type T []T
 
 func funny(args ...T) int { return 0 }

コアとなるコードの解説

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

変更された行 if(count(n->list) == 1 && !n->isddd) は、Goコンパイラの型チェックロジックの一部です。

  • n は現在処理しているAST(抽象構文木)ノードを表します。
  • n->list は、関数呼び出しの引数リストなど、複数の式を含むリストです。
  • count(n->list) は、そのリスト内の要素の数を返します。
  • n->isddd は、このリストがスプレッド演算子(...)によって展開されたものである場合に真となるフラグです。

この変更により、以下の挙動が保証されます。

  1. 単一の引数でスプレッド演算子がない場合: count(n->list) == 1 が真で、n->isddd が偽の場合(例: sum(x))、typecheck(&n->list->n, Erv | Efnstruct) が実行され、単一の引数として適切に型チェックされます。
  2. スライスを展開する場合: count(n->list) == 1 が真で、n->isddd が真の場合(例: sum(s...) where s is a slice)、変更後の条件 !n->isddd が偽となるため、else ブロックに分岐し typechecklist(n->list, Erv) が実行されます。このパスは、スライスが可変引数に展開される正しいケースを処理します。
  3. 多値式を展開しようとする場合(今回の修正対象): count(n->list) == 1 が真で、n->isddd が真の場合(例: sum(tuple()...))、変更後の条件 !n->isddd が偽となるため、else ブロックに分岐し typechecklist(n->list, Erv) が実行されます。この typechecklist の内部で、多値式をスプレッド演算子で展開しようとする不正なパターンが検出され、コンパイルエラーが報告されるようになります。

この修正は、Goの型システムにおける多値とスプレッド演算子のセマンティクスを厳密に適用し、開発者が意図しない、あるいは不正なコードを記述することを防ぎます。

test/ddd1.go の追加

このテストファイルは、src/cmd/gc/typecheck.c の変更が正しく機能することを確認するために追加されました。

  • func sum3(int, int, int) int { return 0 }func tuple() (int, int, int) { return 1, 2, 3 } は、それぞれ3つのint引数を受け取る関数と、3つのint値を返す関数を定義しています。
  • _ = sum(tuple()) は、tuple() の多値戻り値を sum 関数にそのまま渡す例です。sum は可変引数関数ですが、この場合は多値が単一の引数として渡されるため、型が合わずコンパイルエラーになります(sum...int を期待しているため)。これは既存のGoの挙動です。
  • _ = sum(tuple()...) // ERROR "multiple-value" は、今回のコミットで修正された主要なケースです。tuple() の多値戻り値に対してスプレッド演算子 ... を適用しようとしています。これは不正な操作であり、コンパイラが「multiple-value in single-value context」のようなエラーを出すべきであることを示しています。
  • _ = sum3(tuple()) は、tuple() の多値戻り値を sum3 関数にそのまま渡す例です。sum3 は3つのint引数を期待していますが、tuple() の戻り値は単一の多値式として扱われるため、引数の数が合わずコンパイルエラーになります。これも既存のGoの挙動です。
  • _ = sum3(tuple()...) // ERROR "multiple-value" "not enough" も、今回のコミットで修正されたケースです。tuple() の多値戻り値に対してスプレッド演算子 ... を適用しようとしています。これも不正な操作であり、コンパイラが「multiple-value in single-value context」と「not enough arguments」のようなエラーを出すべきであることを示しています。

これらのテストケースは、多値式とスプレッド演算子の不正な組み合わせが、コンパイラによって適切に検出され、エラーとして報告されることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(可変引数、多値戻り値に関するセクション)
  • Goコンパイラのソースコード(src/cmd/gc/typecheck.c の関連部分)
  • Go言語のIssueトラッカー(Issue #3334の詳細)
  • Go言語のコードレビューシステム(CL 6350103の詳細)
  • Go言語の仕様書(多値とスプレッド演算子のセマンティクスに関する記述)

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

このコミットは、Go言語のコンパイラ(cmd/gc)における、多値式(multiple-valued expressions)と可変引数(variadic arguments)を組み合わせた際の不正な使用を検出・拒否する変更を導入しています。具体的には、複数の戻り値を返す関数呼び出しに対して、スプレッド演算子(...)を適用しようとした場合にコンパイルエラーを発生させるように修正されています。

コミット

commit 656b192c1672e4d4b874dfd3b510cd459406e50d
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Jul 13 08:05:41 2012 +0200

    cmd/gc: reject use of ... with multiple-valued expressions.
    
    Fixes #3334.
    
    R=golang-dev, r
    CC=golang-dev, remy
    https://golang.org/cl/6350103

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

https://github.com/golang/go/commit/656b192c1672e4d4b874dfd3b510cd459406e50d

元コミット内容

cmd/gc: reject use of ... with multiple-valued expressions.

Fixes #3334.

R=golang-dev, r
CC=golang-dev, remy
https://golang.org/cl/6350103

変更の背景

この変更は、Go言語のIssue #3334「... operator on multiple-value expression」を修正するために行われました。このIssueは、複数の戻り値を返す関数(例: func tuple() (int, int, int))の呼び出し結果に対して、可変引数関数(例: func sum(args ...int))に引数を渡す際にスプレッド演算子(...)を使用しようとすると、コンパイラが不正なコードを生成してしまうというバグを報告していました。

Go言語では、可変引数関数にスライスを渡す際にスプレッド演算子を使用できますが、複数の戻り値を持つ式はスライスとは異なるため、この組み合わせは意味をなしません。しかし、当時のコンパイラはこの不正なパターンを適切に検出できず、コンパイルが通ってしまうか、あるいは予期せぬ動作を引き起こす可能性がありました。このコミットは、このような誤用をコンパイル時にエラーとして明確に拒否することで、Goプログラムの堅牢性と安全性を向上させることを目的としています。

前提知識の解説

Go言語の可変引数(Variadic Functions)

Go言語では、関数の最後のパラメータに...を付けることで、その関数が任意の数の引数を受け取れるように定義できます。これを可変引数関数と呼びます。 例: func sum(nums ...int) int この関数内では、nums[]int型のスライスとして扱われます。

可変引数関数を呼び出す際には、以下の2つの方法があります。

  1. 個々の引数を列挙する: sum(1, 2, 3)
  2. スライスを渡し、その要素を展開する(スプレッド演算子...を使用する): s := []int{1, 2, 3}; sum(s...)

Go言語の多値戻り値(Multiple Return Values)

Go言語の関数は、複数の値を返すことができます。これは、エラーハンドリングや複数の関連する結果を一度に返す際に非常に便利です。 例: func divide(a, b int) (int, error)

スプレッド演算子(...)の役割

Goにおけるスプレッド演算子...は、主に以下の2つの文脈で使用されます。

  1. 可変引数関数の定義: 上述の通り、関数の最後のパラメータが任意の数の引数を受け取ることを示す。
  2. スライスの展開: 可変引数関数を呼び出す際に、スライスの要素を個々の引数として展開する。

このコミットの背景にある問題は、このスプレッド演算子が、スライスではない「多値戻り値」に対して誤って適用された場合に発生していました。多値戻り値は、複数の独立した値の集合であり、スライスのように要素のコレクションとして扱われるものではありません。したがって、多値戻り値にスプレッド演算子を適用することは、Goの型システムとセマンティクスにおいて不正な操作となります。

Goコンパイラのgc

gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。gcは、構文解析、型チェック、最適化、コード生成など、コンパイルの様々な段階を実行します。このコミットで変更されているtypecheck.cは、gcの型チェックフェーズの一部であり、Goプログラムの型の一貫性と正当性を検証する重要な部分です。

技術的詳細

このコミットの核心は、Goコンパイラの型チェックロジックに、多値式に対するスプレッド演算子(...)の不正な使用を検出する条件を追加することです。

変更はsrc/cmd/gc/typecheck.cファイル内のreswitchラベルの直下、具体的には可変引数関数の引数リストを処理する部分で行われています。

元のコードは以下のようになっていました。

		if(count(n->list) == 1)
			typecheck(&n->list->n, Erv | Efnstruct);
		else
			typechecklist(n->list, Erv);

ここで、n->listは関数呼び出しの引数リストを表し、count(n->list)はその引数の数を返します。n->isdddは、その引数リストがスプレッド演算子(...)を使用して展開されているかどうかを示すフラグです。

変更後のコードは以下の通りです。

		if(count(n->list) == 1 && !n->isddd)
			typecheck(&n->list->n, Erv | Efnstruct);
		else
			typechecklist(n->list, Erv);

追加された条件は && !n->isddd です。 これは、「引数リストの要素が1つであり、かつスプレッド演算子が使用されていない場合」にのみ、単一の引数として型チェックを行うというロジックに変更しています。

この意図は以下の通りです。

  • count(n->list) == 1 の意味: これは、関数呼び出しの引数リストが形式的には1つの要素しか持たない場合を指します。通常、これは単一の変数や式が引数として渡されるケースです。しかし、多値戻り値を返す関数呼び出し(例: tuple())も、その結果全体が「1つの多値式」として扱われるため、この条件に合致する可能性があります。
  • n->isddd の意味: このフラグは、引数リストの最後に...(スプレッド演算子)が付いているかどうかを示します。
  • 変更前の問題: 変更前は、tuple()... のように多値式にスプレッド演算子を適用した場合でも、count(n->list) == 1 が真となり、typecheck(&n->list->n, Erv | Efnstruct) が実行されていました。Ervは「右辺値」を意味し、Efnstructは「関数構造体」を意味する型チェックのコンテキストフラグです。このパスでは、多値式が単一の値として扱われ、スプレッド演算子の意味が正しく解釈されず、コンパイラが不正な状態に陥る可能性がありました。
  • 変更後の修正: && !n->isddd を追加することで、tuple()... のように多値式にスプレッド演算子が適用された場合(この場合 n->isddd が真になる)、count(n->list) == 1 && !n->isddd の条件が偽になります。これにより、処理は else ブロックの typechecklist(n->list, Erv) に進みます。typechecklistは引数リスト全体を型チェックする関数であり、この文脈では多値式にスプレッド演算子を適用することが不正であることを検出し、適切なコンパイルエラー("multiple-value" や "not enough" など)を発生させるようになります。

要するに、この変更は、多値式とスプレッド演算子の組み合わせがGo言語のセマンティクスにおいて不正であることを、コンパイラの型チェック段階で明示的に強制するためのものです。

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

src/cmd/gc/typecheck.c の929行目付近:

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -929,7 +929,7 @@ reswitch:
 		        goto doconv;
 		}
 
-		if(count(n->list) == 1)
+		if(count(n->list) == 1 && !n->isddd)
 			typecheck(&n->list->n, Erv | Efnstruct);
 		else
 			typechecklist(n->list, Erv);

test/ddd1.go の追加:

--- a/test/ddd1.go
+++ b/test/ddd1.go
@@ -22,6 +22,16 @@ var (
 	_ = sum([]int{1}) // ERROR "\[\]int literal.*as type int|incompatible"
 )
 
+func sum3(int, int, int) int { return 0 }
+func tuple() (int, int, int) { return 1, 2, 3 }
+
+var (
+	_ = sum(tuple())
+	_ = sum(tuple()...) // ERROR "multiple-value"
+	_ = sum3(tuple())
+	_ = sum3(tuple()...) // ERROR "multiple-value" "not enough"
+)
+
 type T []T
 
 func funny(args ...T) int { return 0 }

コアとなるコードの解説

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

変更された行 if(count(n->list) == 1 && !n->isddd) は、Goコンパイラの型チェックロジックの一部です。

  • n は現在処理しているAST(抽象構文木)ノードを表します。
  • n->list は、関数呼び出しの引数リストなど、複数の式を含むリストです。
  • count(n->list) は、そのリスト内の要素の数を返します。
  • n->isddd は、このリストがスプレッド演算子(...)によって展開されたものである場合に真となるフラグです。

この変更により、以下の挙動が保証されます。

  1. 単一の引数でスプレッド演算子がない場合: count(n->list) == 1 が真で、n->isddd が偽の場合(例: sum(x))、typecheck(&n->list->n, Erv | Efnstruct) が実行され、単一の引数として適切に型チェックされます。
  2. スライスを展開する場合: count(n->list) == 1 が真で、n->isddd が真の場合(例: sum(s...) where s is a slice)、変更後の条件 !n->isddd が偽となるため、else ブロックに分岐し typechecklist(n->list, Erv) が実行されます。このパスは、スライスが可変引数に展開される正しいケースを処理します。
  3. 多値式を展開しようとする場合(今回の修正対象): count(n->list) == 1 が真で、n->isddd が真の場合(例: sum(tuple()...))、変更後の条件 !n->isddd が偽となるため、else ブロックに分岐し typechecklist(n->list, Erv) が実行されます。この typechecklist の内部で、多値式をスプレッド演算子で展開しようとする不正なパターンが検出され、コンパイルエラーが報告されるようになります。

この修正は、Goの型システムにおける多値とスプレッド演算子のセマンティクスを厳密に適用し、開発者が意図しない、あるいは不正なコードを記述することを防ぎます。

test/ddd1.go の追加

このテストファイルは、src/cmd/gc/typecheck.c の変更が正しく機能することを確認するために追加されました。

  • func sum3(int, int, int) int { return 0 }func tuple() (int, int, int) { return 1, 2, 3 } は、それぞれ3つのint引数を受け取る関数と、3つのint値を返す関数を定義しています。
  • _ = sum(tuple()) は、tuple() の多値戻り値を sum 関数にそのまま渡す例です。sum は可変引数関数ですが、この場合は多値が単一の引数として渡されるため、型が合わずコンパイルエラーになります(sum...int を期待しているため)。これは既存のGoの挙動です。
  • _ = sum(tuple()...) // ERROR "multiple-value" は、今回のコミットで修正された主要なケースです。tuple() の多値戻り値に対してスプレッド演算子 ... を適用しようとしています。これは不正な操作であり、コンパイラが「multiple-value」のようなエラーを出すべきであることを示しています。
  • _ = sum3(tuple()) は、tuple() の多値戻り値を sum3 関数にそのまま渡す例です。sum3 は3つのint引数を期待していますが、tuple() の戻り値は単一の多値式として扱われるため、引数の数が合わずコンパイルエラーになります。これも既存のGoの挙動です。
  • _ = sum3(tuple()...) // ERROR "multiple-value" "not enough" も、今回のコミットで修正されたケースです。tuple() の多値戻り値に対してスプレッド演算子 ... を適用しようとしています。これも不正な操作であり、コンパイラが「multiple-value」と「not enough」のようなエラーを出すべきであることを示しています。

これらのテストケースは、多値式とスプレッド演算子の不正な組み合わせが、コンパイラによって適切に検出され、エラーとして報告されることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(可変引数、多値戻り値に関するセクション)
  • Goコンパイラのソースコード(src/cmd/gc/typecheck.c の関連部分)
  • Go言語のIssueトラッカー(Issue #3334の詳細)
  • Go言語のコードレビューシステム(CL 6350103の詳細)
  • Go言語の仕様書(多値とスプレッド演算子のセマンティクスに関する記述)