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

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

このコミットは、Go言語のコンパイラ(cmd/gc)において、appendおよびcomplex組み込み関数が、2つの結果を返す関数呼び出し式を最初の引数として受け入れられるようにする変更です。これにより、Go言語の柔軟性が向上し、特定のコードパターンがより自然に記述できるようになりました。

コミット

commit 671cc6efba29dc7689d38bcb9893e28375783fbe
Author: Chris Manghane <cmang@golang.org>
Date:   Wed Mar 5 14:16:21 2014 -0500

    cmd/gc: allow append and complex builtins to accept 2-result call expression as first argument.
    
    Fixes #5793.
    
    LGTM=rsc
    R=rsc, adonovan, dave
    CC=golang-codereviews
    https://golang.org/cl/13367051

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

https://github.com/golang/go/commit/671cc6efba29dc7689d38bcb9893e28375783fbe

元コミット内容

cmd/gc: allow append and complex builtins to accept 2-result call expression as first argument.

Fixes #5793.

LGTM=rsc
R=rsc, adonovan, dave
CC=golang-codereviews
https://golang.org/cl/13367051

変更の背景

この変更は、Go言語のIssue 5793「complexおよびappend組み込み関数が、複数の戻り値を持つ関数呼び出しを引数として受け入れない」を修正するために行われました。

Go言語では、関数が複数の値を返すことができます。例えば、func foo() (int, int) のような関数です。しかし、このコミット以前は、complex(foo())append(bar()) のように、複数の戻り値を持つ関数呼び出しの結果を直接 complexappend の引数として渡すことができませんでした。これは、コンパイラがこれらの組み込み関数の引数として、複数の戻り値を持つ式を適切に処理できなかったためです。

具体的には、complex 関数は2つの浮動小数点数を引数として取り、複素数を作成します。append 関数はスライスと要素を引数として取り、スライスに要素を追加します。これらの関数に、例えば (float64, float64)([]string, string) のように複数の値を返す関数を直接渡そうとすると、コンパイラエラーが発生していました。

この制限は、コードの記述を不必要に複雑にし、一時変数を使用する必要があるなど、開発者の利便性を損ねていました。このコミットは、この制限を取り除き、より自然で簡潔なコード記述を可能にすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびGoコンパイラに関する基本的な知識が必要です。

  • Go言語の組み込み関数 (append, complex):
    • append: スライスに要素を追加し、新しいスライスを返します。append(slice, elem1, elem2...) の形式で使用されます。
    • complex: 2つの浮動小数点数(実部と虚部)から複素数を作成します。complex(realPart, imagPart) の形式で使用されます。
  • Go言語の多値戻り値: Goの関数は複数の値を返すことができます。例: func foo() (int, string) { return 1, "hello" }。これらの値は、a, b := foo() のように複数の変数で受け取ることができます。
  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラです。ソースコードを解析し、中間表現に変換し、最終的に実行可能なバイナリを生成します。このコミットで変更されている src/cmd/gc 以下のファイルは、コンパイラのフロントエンド部分、特に型チェックとコード生成の前処理に関わる部分です。
    • AST (Abstract Syntax Tree): ソースコードの構文構造を木構造で表現したものです。コンパイラはソースコードをASTに変換し、その後の処理を行います。
    • 型チェック (Type Checking): ASTを走査し、Go言語の型システムに従って式の型が正しいか、操作が有効かなどを検証するプロセスです。
    • Node 構造体: コンパイラ内部でASTのノードを表す構造体です。Node には、そのノードが表す操作(Op)、関連する型情報(Type)、子ノード(Left, Right, Listなど)が含まれます。
    • OCALLFUNC, OCALLMETH, OCALLINTER: 関数呼び出し、メソッド呼び出し、インターフェースメソッド呼び出しを表すASTノードの操作コードです。
    • OAPPEND, OCOMPLEX: append および complex 組み込み関数を表すASTノードの操作コードです。
    • Erv (Expression returning value): 型チェックのコンテキストフラグで、式が値を返すことを期待していることを示します。
    • Efnstruct: 型チェックのコンテキストフラグで、関数呼び出しの引数が構造体として扱われることを示します。これは、多値戻り値を持つ関数が、内部的にはタプル(構造体)として扱われることに起因します。
    • typecheck.c: 型チェックのロジックを実装しているファイルです。
    • walk.c: ASTを走査し、最適化やコード生成のための変換を行うファイルです。
    • inl.c: インライン化に関する処理を行うファイルです。
    • order.c: 式の評価順序を決定するファイルです。

技術的詳細

このコミットの核心は、Goコンパイラが append および complex 組み込み関数の引数として、多値戻り値を持つ関数呼び出しを適切に「展開」して処理できるようにすることです。

以前のコンパイラでは、complex(f()) のようなコードが与えられた場合、f() が2つの値を返すとしても、complex は単一の引数しか期待しないため、型チェックエラーが発生していました。これは、コンパイラが多値戻り値を単一の「タプル」のような構造体として扱い、それを直接 complexappend の引数に渡そうとしていたためです。

この変更では、以下の主要な修正が行われました。

  1. typecheck.c の修正:

    • OCOMPLEX の型チェックロジックが拡張されました。complex の引数が1つ(つまり、多値戻り値を持つ関数呼び出し)の場合、その関数呼び出しの戻り値の数が2つであることを確認します。もし2つでなければエラーを報告します。
    • 多値戻り値を持つ関数呼び出しの型が TSTRUCT (タプルを表す内部的な構造体型) である場合、その構造体から個々の要素の型(実部と虚部の型)を抽出するように変更されました。
    • OAPPEND の型チェックロジックも同様に修正され、append の最初の引数が多値戻り値を持つ関数呼び出しである場合に、その戻り値を適切に処理できるようにしました。特に、append の引数が1つで、かつ可変長引数 (...) でない場合、Erv | Efnstruct コンテキストで型チェックを行うことで、多値戻り値がタプルとして扱われることを明示的に指示します。
    • append の引数の型が TSTRUCT の場合、その構造体からスライスの要素型を適切に解決するように変更されました。
  2. walk.c の修正:

    • OCOMPLEX ノードの処理において、n->leftn->rightN (nil) である場合(これは多値戻り値を持つ関数呼び出しが引数として渡された場合に発生しうる)、n->list から実際の引数(実部と虚部)を取り出して n->leftn->right に割り当てるように変更されました。これにより、後続の処理で個々の引数が正しく扱われます。
    • append 組み込み関数の処理 (append 関数) において、ソースノード (nsrc) の型が TSTRUCT の場合、その型を解決してスライスの要素型を取得するように変更されました。これは、多値戻り値の最初の値がスライス型である場合に、そのスライス型を正しく認識するために必要です。
  3. inl.c および order.c の修正:

    • inlnode (インライン化処理) および orderexpr (式の評価順序付け) の関数において、OCALLFUNC, OCALLMETH, OCALLINTER と同様に、OAPPENDOCOMPLEX も多値戻り値の展開処理の対象となるように追加されました。これにより、これらの組み込み関数に渡される多値戻り値が、インライン化や評価順序付けの段階で正しく扱われるようになります。

これらの変更により、コンパイラは complex(f())append(g()) のようなコードを、real, imag := f(); complex(real, imag)s, elem := g(); append(s, elem) のように内部的に展開して処理できるようになり、開発者はより簡潔なコードを記述できるようになりました。

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

src/cmd/gc/typecheck.c

@@ -1212,17 +1212,29 @@ reswitch:
 
 	case OCOMPLEX:
 	\tok |= Erv;
-\t\tif(twoarg(n) < 0)\
-\t\t\tgoto error;\
-\t\tl = typecheck(&n->left, Erv | (top & Eiota));
-\t\tr = typecheck(&n->right, Erv | (top & Eiota));
-\t\tif(l->type == T || r->type == T)\
-\t\t\tgoto error;\
-\t\tdefaultlit2(&l, &r, 0);\
-\t\t\tif(l->type == T || r->type == T)\
-\t\t\t\tgoto error;\
-\t\tn->left = l;
-\t\tn->right = r;
+\t\tif(count(n->list) == 1) {
+\t\t\ttypechecklist(n->list, Efnstruct);
+\t\t\tt = n->list->n->left->type;
+\t\t\tif(t->outtuple != 2) {
+\t\t\t\tyyerror("invalid operation: complex expects two arguments, %N returns %d results", n->list->n, t->outtuple);
+\t\t\t\tgoto error;
+\t\t\t}
+\t\t\tt = n->list->n->type->type;
+\t\t\tl = t->nname;
+\t\t\tr = t->down->nname;
+\t\t} else {
+\t\t\tif(twoarg(n) < 0)
+\t\t\t\tgoto error;
+\t\t\tl = typecheck(&n->left, Erv | (top & Eiota));
+\t\t\tr = typecheck(&n->right, Erv | (top & Eiota));
+\t\t\tif(l->type == T || r->type == T)
+\t\t\t\tgoto error;
+\t\t\tdefaultlit2(&l, &r, 0);
+\t\t\tif(l->type == T || r->type == T)
+\t\t\t\tgoto error;
+\t\t\tn->left = l;
+\t\t\tn->right = r;
+\t\t}
 \t\tif(!eqtype(l->type, r->type)) {
 \t\t\tyyerror("invalid operation: %N (mismatched types %T and %T)", n, l->type, r->type);
 \t\t\tgoto error;
@@ -1301,9 +1313,22 @@ reswitch:
 \t\t\tyyerror("missing arguments to append");
 \t\t\tgoto error;\
 \t\t}\
-\t\ttypechecklist(args, Erv);\
+\n+\t\tif(count(args) == 1 && !n->isddd)\
+\t\t\ttypecheck(&args->n, Erv | Efnstruct);\
+\t\telse\
+\t\t\ttypechecklist(args, Erv);\
+\n \t\tif((t = args->n->type) == T)\
 \t\t\tgoto error;\
+\n+\t\t// Unpack multiple-return result before type-checking.\
+\t\tif(istype(t, TSTRUCT)) {\
+\t\t\tt = t->type;\
+\t\t\tif(istype(t, TFIELD))\
+\t\t\t\tt = t->type;\
+\t\t}\
+\n \t\tn->type = t;\
 \t\tif(!isslice(t)) {
 \t\t\tif(isconst(args->n, CTNIL)) {

src/cmd/gc/walk.c

@@ -493,6 +493,11 @@ walkexpr(Node **np, NodeList **init)
 	case OADD:
 	case OCOMPLEX:
 	case OLROT:
+\t\t// Use results from call expression as arguments for complex.
+\t\tif(n->op == OCOMPLEX && n->left == N && n->right == N) {
+\t\t\tn->left = n->list->n;
+\t\t\tn->right = n->list->next->n;
+\t\t}
 \t\twalkexpr(&n->left, init);
 \t\twalkexpr(&n->right, init);
 \t\tgoto ret;
@@ -2772,6 +2777,10 @@ append(Node *n, NodeList **init)
 \t\tl->n = cheapexpr(l->n, init);
 
 \tnsrc = n->list->n;
+\n+\t// Resolve slice type of multi-valued return.
+\tif(istype(nsrc->type, TSTRUCT))\
+\t\tnsrc->type = nsrc->type->type->type;
 \targc = count(n->list) - 1;
 \tif (argc < 1) {
 \t\treturn nsrc;

src/cmd/gc/inl.c

@@ -392,6 +392,8 @@ inlnode(Node **np)
 	case OCALLFUNC:
 	case OCALLMETH:
 	case OCALLINTER:
+\tcase OAPPEND:
+\tcase OCOMPLEX:
 	\t// if we just replaced arg in f(arg()) or return arg with an inlined call
 	\t// and arg returns multiple values, glue as list
 	\tif(count(n->list) == 1 && n->list->n->op == OINLCALL && count(n->list->n->rlist) > 1) {

src/cmd/gc/order.c

@@ -344,6 +344,8 @@ orderexpr(Node **np, NodeList **out)
 	case OCALLFUNC:
 	case OCALLMETH:
 	case OCALLINTER:
+\tcase OAPPEND:
+\tcase OCOMPLEX:
 	\tordercall(n, out);
 	\tn = copyexpr(n, n->type, out);
 	\tbreak;

test/fixedbugs/issue5793.go

+// run
+
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Issue 5793: calling 2-arg builtin with multiple-result f() call expression gives
+// spurious error.
+
+package main
+
+func complexArgs() (float64, float64) {
+	return 5, 7
+}
+
+func appendArgs() ([]string, string) {
+	return []string{"foo"}, "bar"
+}
+
+func appendMultiArgs() ([]byte, byte, byte) {
+	return []byte{'a', 'b'}, '1', '2'
+}
+
+func main() {
+	if c := complex(complexArgs()); c != 5+7i {
+		panic(c)
+	}
+
+	if s := append(appendArgs()); len(s) != 2 || s[0] != "foo" || s[1] != "bar" {
+		panic(s)
+	}
+
+	if b := append(appendMultiArgs()); len(b) != 4 || b[0] != 'a' || b[1] != 'b' || b[2] != '1' || b[3] != '2' {
+		panic(b)
+	}
+}

コアとなるコードの解説

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

  • OCOMPLEX の型チェック:
    • 変更前は、complex の引数が常に2つあることを前提としていました。
    • 変更後は、count(n->list) == 1 (引数が1つ、つまり多値戻り値の関数呼び出しが渡された場合) のケースが追加されました。
    • この場合、typechecklist(n->list, Efnstruct) を呼び出し、多値戻り値が内部的に構造体として扱われることをコンパイラに伝えます。
    • t->outtuple != 2 で、関数が2つの値を返さない場合はエラーを報告します。
    • t = n->list->n->type->type; l = t->nname; r = t->down->nname; の行で、多値戻り値の関数呼び出しの型情報から、個々の戻り値の型(実部と虚部)を抽出し、それぞれ lr に割り当てています。これにより、complex が期待する2つの引数として扱えるようになります。
  • OAPPEND の型チェック:
    • 変更前は、typechecklist(args, Erv) で一律に引数を型チェックしていました。
    • 変更後は、count(args) == 1 && !n->isddd (引数が1つで、かつ可変長引数でない場合) の条件が追加されました。これは、append(f()) のようなケースを指します。
    • この場合、typecheck(&args->n, Erv | Efnstruct) を呼び出し、Efnstruct フラグを使って、引数が多値戻り値の構造体として扱われることを明示します。
    • // Unpack multiple-return result before type-checking. のコメント以下のコードブロックで、append の引数の型 tTSTRUCT (多値戻り値のタプル型) である場合、その構造体から実際のスライス型を抽出しています。これにより、append がスライスと要素を正しく認識できるようになります。

src/cmd/gc/walk.c の変更点

  • OCOMPLEX の引数展開:
    • n->op == OCOMPLEX && n->left == N && n->right == N の条件は、complex(f()) のように、complex の引数が直接指定されず、多値戻り値の関数呼び出しによって提供される場合に真となります。
    • n->left = n->list->n; n->right = n->list->next->n; の行で、n->list に格納されている多値戻り値の個々の要素(実部と虚部)を、complex ノードの leftright の引数として設定しています。これにより、後続のコード生成フェーズでこれらの値が正しく使用されます。
  • append のスライス型解決:
    • if(istype(nsrc->type, TSTRUCT)) の条件は、append の最初の引数(スライス)が多値戻り値の関数呼び出しによって提供され、その結果が内部的に TSTRUCT として表現されている場合に真となります。
    • nsrc->type = nsrc->type->type->type; の行で、TSTRUCT から実際のスライス型を抽出しています。これにより、append が操作するスライスの型を正しく特定できます。

src/cmd/gc/inl.c および src/cmd/gc/order.c の変更点

  • これらのファイルでは、OCALLFUNC, OCALLMETH, OCALLINTER と並んで、OAPPENDOCOMPLEX が追加されています。これは、インライン化や式の評価順序付けの際に、これらの組み込み関数に渡される多値戻り値の式も適切に処理されるようにするためです。これにより、コンパイラの異なるフェーズ間で一貫した処理が保証されます。

test/fixedbugs/issue5793.go

  • この新しいテストファイルは、このコミットによって修正された問題が実際に解決されたことを検証するためのものです。
  • complexArgs()(float64, float64) を返し、appendArgs()([]string, string) を返し、appendMultiArgs()([]byte, byte, byte) を返します。
  • main 関数内で、これらの多値戻り値を持つ関数を complexappend の引数として直接呼び出し、期待通りの結果が得られることを確認しています。これにより、コンパイラの変更が正しく機能していることが保証されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goコンパイラのソースコード
  • Go言語のIssueトラッカー (Issue 5793)
  • Go言語のコードレビューシステム (Gerrit)
  • Go言語のコンパイラに関する一般的な知識(AST、型チェックなど)