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

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

このコミットは、Go言語のgo/printerパッケージにおける、リテラル関数型を型変換で使用する際の括弧の扱いに関する修正と、それに伴うgofmtの適用、およびreflectパッケージのテストケースの更新を含んでいます。

コミット

commit 1065c6f65ad3083976f10f0828633a322f7114b8
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Oct 4 21:03:50 2012 -0700

    go/printer: parenthesize literal function types in conversions
    
    Also: gofmt -w src misc
    
    R=r
    CC=golang-dev, iant
    https://golang.org/cl/6591071

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

https://github.com/golang/go/commit/1065c6f65ad3083976f10f0828633a322f7114b8

元コミット内容

go/printer: parenthesize literal function types in conversions (go/printer: 型変換におけるリテラル関数型を括弧で囲む)

Also: gofmt -w src misc (また: srcとmiscディレクトリに対してgofmt -wを実行)

変更の背景

Go言語では、型変換(type conversion)の構文は Type(expression) の形式を取ります。ここで Type は変換先の型、expression は変換される値です。しかし、変換先の型がリテラル関数型(例: func(), func(int) float)である場合、構文の曖昧さや可読性の問題が生じる可能性がありました。

特に、func()()(nil) のような記述は、func()() が関数呼び出しのように見えたり、func()() 全体が型として認識されにくいといった問題がありました。Goのパーサーは、文脈によっては括弧がない形式も受け入れていましたが、プリンター(コード整形ツール)がこれを一貫して正しく出力するためには、明示的に括弧を付与する必要がありました。

このコミットの目的は、go/printerがリテラル関数型への型変換を処理する際に、その型全体を括弧で囲むようにすることで、生成されるコードの正確性、一貫性、および可読性を向上させることにあります。これにより、_ = func()()(nil) のようなコードが _ = (func())(nil) のように整形され、意図が明確になります。

また、この変更はgofmtの動作にも影響を与えるため、srcおよびmiscディレクトリ全体に対してgofmt -wを実行し、既存のコードベースが新しい整形ルールに準拠するように更新されています。

前提知識の解説

Go言語の型変換 (Type Conversion)

Go言語における型変換は、ある型の値を別の型に明示的に変換する操作です。構文は T(x) の形式で、x の値を型 T に変換します。例えば、int(3.14) は浮動小数点数 3.14 を整数 3 に変換します。

リテラル関数型 (Literal Function Types)

Go言語では、関数型をその場で定義することができます。これをリテラル関数型と呼びます。例えば、func(int, string) bool は、intstringを引数にとり、boolを返す関数型を表します。これは、名前付きの関数型(例: type MyFunc func(int, string) bool)とは異なり、その場で匿名で定義される型です。

go/printer パッケージ

go/printerパッケージは、Goの抽象構文木(AST: Abstract Syntax Tree)をGoのソースコードとして整形して出力するためのパッケージです。gofmtツールはこのパッケージを利用してGoのコードを整形します。コードの整形ルールやスタイルを決定する重要な役割を担っています。

gofmt ツール

gofmtはGo言語の公式なコード整形ツールです。Goのソースコードを標準的なスタイルに自動的に整形します。これにより、Goコミュニティ全体で一貫したコードスタイルが保たれ、コードの可読性が向上します。gofmt -wは、整形結果を元のファイルに書き戻すオプションです。

ast パッケージ

go/astパッケージは、Goのソースコードの抽象構文木(AST)を表現するためのデータ構造を提供します。Goのコンパイラ、ツール、および静的解析ツールは、このASTを使用してコードを解析し、操作します。ast.ExprはASTノードの一種で、式を表します。ast.FuncTypeは関数型を表すASTノードです。

技術的詳細

このコミットの主要な変更は、src/pkg/go/printer/nodes.goファイル内のexpr1メソッドにあります。このメソッドは、Goの式のASTノードを整形して出力する役割を担っています。

変更前は、型変換の対象となる関数(x.Fun)がどのような型であるかに関わらず、単純にp.expr1(x.Fun, token.HighestPrec, depth)を呼び出して整形していました。

変更後は、型変換の対象が*ast.FuncType(リテラル関数型)であるかどうかをチェックする条件分岐が追加されました。

if _, ok := x.Fun.(*ast.FuncType); ok {
    // conversions to literal function types require parentheses around the type
    p.print(token.LPAREN)
    p.expr1(x.Fun, token.HighestPrec, depth)
    p.print(token.RPAREN)
} else {
    p.expr1(x.Fun, token.HighestPrec, depth)
}

このコードスニペットは、x.Funast.FuncType(つまりリテラル関数型)である場合に、その型の前後にtoken.LPAREN(左括弧)とtoken.RPAREN(右括弧)を出力するように指示しています。これにより、func()()のようなリテラル関数型が型変換の文脈で使用される際に、(func()())のように括弧で囲まれて出力されるようになります。

この変更は、Goの構文解析器(パーサー)が、文脈によっては括弧がないリテラル関数型への変換も受け入れていたものの、go/printerがより一貫性のある、曖昧さのない出力を生成するためのものです。特に、func() func() func()()のようなネストされた関数型の場合、括弧がないと解釈が難しくなる可能性があります。明示的な括弧は、型変換の対象がリテラル関数型全体であることを明確に示します。

また、src/pkg/go/printer/testdata/expressions.inputexpressions.goldenexpressions.rawのテストデータが更新されています。これらはgo/printerの出力が期待通りになることを検証するためのファイルです。expressions.inputは整形前のコード、expressions.goldenは整形後の期待されるコード、expressions.rawはASTを直接出力したものです。これらのファイルが更新されたことで、リテラル関数型への変換が正しく括弧で囲まれるようになったことが確認できます。

さらに、src/pkg/reflect/all_test.goのテストケースも修正されています。

// 変更前
tfunc := TypeOf(func(int) int(nil))
// 変更後
tfunc := TypeOf((func(int) int)(nil))

この変更は、reflectパッケージのテストコード自体が、新しいgo/printerの整形ルールに準拠するように更新されたことを示しています。func(int) int(nil)という記述は、func(int) intというリテラル関数型をnilに型変換していることを意図していますが、変更前は括弧がありませんでした。変更後は(func(int) int)(nil)と明示的に括弧が追加され、より明確な記述になっています。

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

src/pkg/go/printer/nodes.go

--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -791,7 +791,14 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 		if len(x.Args) > 1 {
 			depth++
 		}
-		p.expr1(x.Fun, token.HighestPrec, depth)
+		if _, ok := x.Fun.(*ast.FuncType); ok {
+			// conversions to literal function types require parentheses around the type
+			p.print(token.LPAREN)
+			p.expr1(x.Fun, token.HighestPrec, depth)
+			p.print(token.RPAREN)
+		} else {
+			p.expr1(x.Fun, token.HighestPrec, depth)
+		}
 		p.print(x.Lparen, token.LPAREN)
 		if x.Ellipsis.IsValid() {
 			p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis)

src/pkg/go/printer/testdata/expressions.golden

--- a/src/pkg/go/printer/testdata/expressions.golden
+++ b/src/pkg/go/printer/testdata/expressions.golden
@@ -647,3 +647,18 @@ func _() {
 		a...,
 	)
 }
+
+// Literal function types in conversions must be parenthesized;
+// for now go/parser accepts the unparenthesized form where it
+// is non-ambiguous.
+func _() {
+	// these conversions should be rewritten to look
+	// the same as the parenthesized conversions below
+	_ = (func())(nil)
+	_ = (func(x int) float)(nil)
+	_ = (func() func() func())(nil)
+
+	_ = (func())(nil)
+	_ = (func(x int) float)(nil)
+	_ = (func() func() func())(nil)
+}

src/pkg/go/printer/testdata/expressions.input

--- a/src/pkg/go/printer/testdata/expressions.input
+++ b/src/pkg/go/printer/testdata/expressions.input
@@ -676,3 +676,18 @@ func _() {
 		a...,
 	)
 }
+
+// Literal function types in conversions must be parenthesized;
+// for now go/parser accepts the unparenthesized form where it
+// is non-ambiguous.
+func _() {
+	// these conversions should be rewritten to look
+	// the same as the parenthesized conversions below
+	_ = func()()(nil)
+	_ = func(x int)(float)(nil)
+	_ = func() func() func()()(nil)
+
+	_ = (func()())(nil)
+	_ = (func(x int)(float))(nil)
+	_ = (func() func() func()())(nil)
+}

src/pkg/reflect/all_test.go

--- a/src/pkg/reflect/all_test.go
+++ b/src/pkg/reflect/all_test.go
@@ -1494,7 +1494,7 @@ func TestMethod(t *testing.T) {
 	}
 
 	// Curried method of value.
-	tfunc := TypeOf(func(int) int(nil))
+	tfunc := TypeOf((func(int) int)(nil))
 	v := ValueOf(p).Method(1)
 	if tt := v.Type(); tt != tfunc {
 		t.Errorf("Value Method Type is %s; want %s", tt, tfunc)

コアとなるコードの解説

src/pkg/go/printer/nodes.goの変更は、printer構造体のexpr1メソッド内にあります。このメソッドは、GoのASTノードを整形する際の中心的なロジックを含んでいます。

変更の核心は、型変換(ast.CallExprで表現される)の対象となる関数(x.Fun)が*ast.FuncType(リテラル関数型)であるかどうかをif _, ok := x.Fun.(*ast.FuncType); okでチェックしている点です。

  • x.Fun.(*ast.FuncType): これは型アサーションです。x.Funast.FuncType型に変換可能であれば、oktrueになり、変換された値が最初のアンダースコア(_)に代入されます(ここでは値自体は使用しないため破棄)。
  • if _, ok := ...; ok: このイディオムは、Goで型アサーションが成功したかどうかを安全にチェックするためによく使われます。

もしx.Funがリテラル関数型であれば、以下の処理が実行されます。

  1. p.print(token.LPAREN): 左括弧 ( を出力します。
  2. p.expr1(x.Fun, token.HighestPrec, depth): リテラル関数型自体を再帰的に整形して出力します。
  3. p.print(token.RPAREN): 右括弧 ) を出力します。

これにより、例えば func()() というリテラル関数型が型変換の文脈で使われる場合、go/printer(func()()) と出力するようになります。

それ以外の場合(x.Funがリテラル関数型ではない場合)は、従来の動作通りp.expr1(x.Fun, token.HighestPrec, depth)が実行され、括弧は追加されません。

この修正により、go/printerはGoの言語仕様における型変換の曖昧さを解消し、より明確で一貫性のあるコードを生成できるようになりました。特に、gofmtを通じてこの変更が適用されることで、Goのコードベース全体の可読性と保守性が向上します。

関連リンク

参考にした情報源リンク

  • Go言語の関数リテラルと型変換に関するStack Overflowの議論: https://stackoverflow.com/questions/tagged/go+type-conversion+function-literal
  • Go言語の型変換に関するMediumの記事: https://medium.com/ (具体的な記事は特定できませんでしたが、Goの型変換に関する一般的な解説記事が参考になりました)
  • Go言語の関数型に関するStudyTonightの記事: https://www.studytonight.com/ (具体的な記事は特定できませんでしたが、Goの関数型に関する一般的な解説記事が参考になりました)
  • Go言語の公式ブログやリリースノート(過去の変更履歴を追う際に参照)