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

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

このコミットは、Goコンパイラ(cmd/gc)におけるバグ修正を目的としています。具体的には、メソッド値が可変引数(variadic)のプロパティを正しく保持しないという問題(Issue 5231)を解決します。これにより、可変引数を持つメソッドのメソッド値が、期待通りに可変引数として扱われるようになります。

コミット

commit 20e05303febf53d959926a05c6f019db352bb963
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Mon Apr 8 08:59:33 2013 +0200

    cmd/gc: properly set variadic flag on method values.
    
    Fixes #5231.
    
    R=golang-dev, daniel.morsing, adg
    CC=golang-dev
    https://golang.org/cl/8275044

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

https://github.com/golang/go/commit/20e05303febf53d959926a05c6f019db352bb963

元コミット内容

このコミットの元の内容は、Goコンパイラのcmd/gcが、メソッド値(method values)に対して可変引数(variadic)のフラグを適切に設定していなかったというバグを修正することです。この問題はGoのIssue 5231として追跡されており、このコミットによって解決されました。

変更の背景

Go言語では、メソッドを関数値として扱う「メソッド値」という機能があります。例えば、t.Methodのように記述することで、Methodというメソッドを通常の関数のように変数に代入したり、引数として渡したりすることができます。

一方で、Goには可変引数関数という機能もあります。これは、引数の数が不定の関数を定義できるもので、引数リストの最後に...を付けて表現します(例: func foo(args ...int))。

このコミットが修正するバグは、可変引数を持つメソッド(例: func (t T) Variadic(s ...int) int)のメソッド値を作成した際に、そのメソッド値が可変引数であるという情報(フラグ)がコンパイラ内部で正しく伝播・設定されていなかったことに起因します。結果として、可変引数メソッドから生成されたメソッド値を、可変引数関数として呼び出そうとすると、コンパイルエラーになったり、予期せぬ動作を引き起こしたりする可能性がありました。

test/fixedbugs/issue5231.goという新しいテストファイルが追加されており、このテストはまさにこの問題が修正されたことを検証するために書かれています。このテストケースでは、可変引数メソッドのメソッド値を生成し、それを可変引数として呼び出したり、可変引数関数型に代入したりするシナリオが含まれています。

前提知識の解説

Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラの一部です。Goのソースコードを解析し、中間表現に変換し、最終的に実行可能なバイナリを生成する役割を担っています。コンパイラの内部では、ソースコードの各要素(関数、変数、型など)が抽象構文木(AST)のノードとして表現され、これらのノードには様々な属性(型情報、フラグなど)が付与されます。

メソッド値 (Method Values)

Goにおけるメソッド値は、構造体やインターフェースのインスタンスに紐付けられたメソッドを、通常の関数のように扱うための機能です。 例えば、type T struct{}; func (t T) M(x int) { ... }という定義がある場合、var t T; f := t.Mとすることで、ffunc(int)型の関数値となり、f(10)のように呼び出すことができます。このfがメソッド値です。メソッド値は、レシーバ(この例ではt)がバインドされた状態で関数として振る舞います。

可変引数関数 (Variadic Functions)

可変引数関数は、引数の数が不定である関数です。Goでは、引数リストの最後のパラメータの型の前に...を付けることで定義します。 例: func sum(nums ...int) int { ... } このnumsは関数内部では[]int(intのスライス)として扱われます。可変引数関数を呼び出す際には、sum(1, 2, 3)のように複数の引数を渡すこともできますし、s := []int{1, 2}; sum(s...)のようにスライスを展開して渡すこともできます。

コンパイラ内部のフラグ (isddd)

Goコンパイラは、言語の様々な特性を表現するために、内部的なデータ構造(ASTノードなど)にフラグを設定します。このコミットで言及されているisdddは、おそらく「is ellipsis」または「is variadic」の略であり、そのノードが可変引数に関連するものであることを示すフラグです。このフラグが正しく設定されることで、コンパイラは可変引数に関する型チェックやコード生成を適切に行うことができます。

技術的詳細

このバグは、Goコンパイラのsrc/cmd/gc/closure.cファイル内のmakepartialcall関数に存在していました。この関数は、メソッド値(部分適用された呼び出し)を生成する際に使用されます。

問題の核心は、可変引数を持つメソッド(例: func (t T) Variadic(s ...int) int)のメソッド値が作成される際、そのメソッドが可変引数であるという情報が、生成される内部的な関数表現(OCALLノードなど)に適切に伝達されていなかったことです。

修正前は、makepartialcall関数内で新しい関数型が構築される際に、可変引数パラメータのisdddフラグが、その関数型を構成するフィールド(ODCLFIELDノード)には設定されていましたが、最終的に生成されるOCALLノード(メソッド呼び出しを表す)には伝播していませんでした。

このコミットでは、以下の変更が加えられました。

  1. makepartialcall関数内に、可変引数であるかどうかを追跡するための新しい変数ddd(おそらくisdddの略)が導入されました。
  2. メソッドの引数を処理するループ内で、引数型が可変引数(t->isdddが真)である場合、対応するフィールドノード(fld)にisddd = 1を設定するとともに、新しく導入されたddd変数も1に設定されます。
  3. 最終的にメソッド呼び出しを表すOCALLノードが構築される際に、このddd変数の値がcall->isdddに代入されます。これにより、メソッド値が可変引数であるという情報が、コンパイラの内部表現に正しく反映されるようになりました。

この修正により、コンパイラは可変引数メソッドのメソッド値を正しく認識し、可変引数としての呼び出しや型アサインメントを適切に処理できるようになりました。

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

src/cmd/gc/closure.c

--- a/src/cmd/gc/closure.c
+++ b/src/cmd/gc/closure.c
@@ -280,12 +280,12 @@ typecheckpartialcall(Node *fn, Node *sym)\n static Node*\n makepartialcall(Node *fn, Type *t0, Node *meth)\n {\n-\tNode *ptr, *n, *call, *xtype, *xfunc, *cv;\n+\tNode *ptr, *n, *fld, *call, *xtype, *xfunc, *cv;\n \tType *rcvrtype, *basetype, *t;\n \tNodeList *body, *l, *callargs, *retargs;\n \tchar *p;\n \tSym *sym;\n-\tint i;\n+\tint i, ddd;\n \n \t// TODO: names are not right\n \trcvrtype = fn->left->type;\
@@ -309,6 +309,7 @@ makepartialcall(Node *fn, Type *t0, Node *meth)\n \ti = 0;\n \tl = nil;\n \tcallargs = nil;\
+\tddd = 0;\
 \txfunc = nod(ODCLFUNC, N, N);\
 \tfor(t = getinargx(t0)->type; t; t = t->down) {\
 \t\tsnprint(namebuf, sizeof namebuf, \"a%d\", i++);\
@@ -316,7 +317,12 @@ makepartialcall(Node *fn, Type *t0, Node *meth)\n \t\tn->class = PPARAM;\
 \t\txfunc->dcl = list(xfunc->dcl, n);\
 \t\tcallargs = list(callargs, n);\
-\t\tl = list(l, nod(ODCLFIELD, n, typenod(t->type)));\
+\t\tfld = nod(ODCLFIELD, n, typenod(t->type));\
+\t\tif(t->isddd) {\
+\t\t\tfld->isddd = 1;\
+\t\t\tddd = 1;\
+\t\t}\
+\t\tl = list(l, fld);\
 \t}\
 \txtype->list = l;\
 \ti = 0;\
@@ -338,7 +344,7 @@ makepartialcall(Node *fn, Type *t0, Node *meth)\n \txfunc->nname->ntype = xtype;\
 \txfunc->nname->defn = xfunc;\
 \tdeclare(xfunc->nname, PFUNC);\
-\t\n+\n \t// Declare and initialize variable holding receiver.\
 \tbody = nil;\
 \tcv = nod(OCLOSUREVAR, N, N);\
@@ -362,6 +368,7 @@ makepartialcall(Node *fn, Type *t0, Node *meth)\n \n \tcall = nod(OCALL, nod(OXDOT, ptr, meth), N);\
 \tcall->list = callargs;\
+\tcall->isddd = ddd;\
 \tif(t0->outtuple == 0) {\
 \t\tbody = list(body, call);\
 \t} else {\
@@ -393,7 +400,7 @@ walkpartialcall(Node *n, NodeList **init)\n \t//\tclos = &struct{F uintptr; R T}{M.T·f, x}\n \t//\n \t// Like walkclosure above.\
-\t\n+\n \tif(isinter(n->left->type)) {\
 \t\tn->left = cheapexpr(n->left, init);\
 \t\tchecknotnil(n->left, init);\

test/fixedbugs/issue5231.go

--- /dev/null
+++ b/test/fixedbugs/issue5231.go
@@ -0,0 +1,45 @@
+// compile
+
+// 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 5231: method values lose their variadic property.
+
+package p
+
+type T int
+
+func (t T) NotVariadic(s []int) int {
+	return int(t) + s[0]
+}
+
+func (t T) Variadic(s ...int) int {
+	return int(t) + s[0]
+}
+
+type I interface {
+	NotVariadic(s []int) int
+	Variadic(s ...int) int
+}
+
+func F() {
+	var t T
+	var p *T = &t
+	var i I = p
+
+	nv := t.NotVariadic
+	nv = p.NotVariadic
+	nv = i.NotVariadic
+	var s int = nv([]int{1, 2, 3})
+
+	v := t.Variadic
+	v = p.Variadic
+	v = i.Variadic
+	s = v(1, 2, 3)
+
+	var f1 func([]int) int = nv
+	var f2 func(...int) int = v
+
+	_, _, _ = f1, f2, s
+}

コアとなるコードの解説

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

  • makepartialcall 関数の変更:
    • fldddd という新しい変数が追加されました。fldはフィールドノード、dddは可変引数フラグを保持するための整数型変数です。
    • ddd0で初期化されます。
    • メソッドの引数を処理するループ内で、t->isddd(引数型が可変引数であるか)がチェックされます。
    • もしt->isdddが真であれば、新しく作成されるフィールドノードfldisdddフラグが1に設定され、同時にddd変数も1に設定されます。これにより、可変引数であるという情報がddd変数に集約されます。
    • 最終的に、メソッド呼び出しを表すcallノードのisdddフラグに、このddd変数の値が代入されます(call->isddd = ddd;)。この行が追加されたことで、メソッド値が可変引数であるという情報が、コンパイラの内部表現に正しく伝播されるようになりました。

test/fixedbugs/issue5231.go の追加

  • このファイルは、// compileディレクティブを持つコンパイルテストです。つまり、このコードがエラーなくコンパイルできることを検証します。
  • T型と、通常の引数を取るNotVariadicメソッド、可変引数を取るVariadicメソッドが定義されています。
  • Iインターフェースは、これら両方のメソッドを定義しています。
  • F()関数内で、T型、*T型、I型のインスタンスからNotVariadicVariadicのメソッド値がそれぞれ取得されます。
  • 重要なのは、v = i.Variadicのようにインターフェースのメソッド値を取得し、それをs = v(1, 2, 3)のように可変引数として呼び出している点です。また、var f2 func(...int) int = vのように、可変引数関数型にメソッド値を代入している点も重要です。
  • このテストがエラーなくコンパイルできるようになったことで、Issue 5231のバグが修正されたことが確認できます。修正前は、これらの操作がコンパイルエラーを引き起こしていました。

関連リンク

  • Go言語の公式Issueトラッカー: https://github.com/golang/go/issues (ただし、Issue 5231は古いIssueであり、直接検索では見つからない可能性があります。GoのChange List (CL) を参照するのが確実です。)
  • Go Change List 8275044: https://golang.org/cl/8275044 (コミットメッセージに記載されているリンク)

参考にした情報源リンク

  • Go言語のメソッド値に関する公式ドキュメントやチュートリアル
  • Go言語の可変引数関数に関する公式ドキュメントやチュートリアル
  • Goコンパイラのソースコード(特にsrc/cmd/gcディレクトリ)
  • Go言語のIssueトラッカーおよびChange Listのアーカイブ
  • Stack OverflowやGoコミュニティの議論(可変引数やメソッド値に関する一般的な情報)