[インデックス 10600] ファイルの概要
このコミットは、Go言語のコードフォーマッタであるgofmt
の改善に関するものです。具体的には、複合リテラル(Composite Literal)内でポインタ型(*T
)の要素を持つ場合に、その要素が&T{...}
のような形式で記述されている際に、gofmt
が&
演算子と明示的な型T
を省略して{...}
と簡略化できるようにする変更を導入しています。これにより、コードの冗長性が減り、より簡潔な記述が促進されます。
コミット
commit 0dab624b70273d4c32b70a5076c2a054c5a274dd
Author: Russ Cox <rsc@golang.org>
Date: Fri Dec 2 14:14:04 2011 -0500
gofmt: handle &T in composite literal simplify
R=gri
CC=golang-dev
https://golang.org/cl/5448086
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0dab624b70273d4c32b70a5076c2a054c5a274dd
元コミット内容
gofmt: handle &T in composite literal simplify
R=gri
CC=golang-dev
https://golang.org/cl/5448086
変更の背景
Go言語にはgofmt
という標準のコードフォーマッタが存在します。gofmt
の主な目的は、Goのソースコードを標準的なスタイルに自動的に整形し、コードの一貫性を保ち、可読性を向上させることです。これにより、開発者はスタイルに関する議論に時間を費やすことなく、本質的なロジックに集中できます。
このコミットが行われた背景には、gofmt
が複合リテラルを処理する際の特定の冗長な記述に対する改善の必要性がありました。Go言語では、複合リテラルを使用して構造体、配列、スライス、マップなどの複合型の値を初期化できます。例えば、[]T{{...}, {...}}
のように記述します。
しかし、要素がポインタ型である場合、例えば[]*T{&T{...}, &T{...}}
のように、要素の型が*T
であるにもかかわらず、&T{...}
と明示的にアドレス演算子&
と型T
を記述することがありました。この&T
の部分は、外側の複合リテラルの要素型から推論可能であり、冗長であると見なされていました。
このコミットは、このような冗長な記述をgofmt
が自動的に検出し、&T{...}
を{...}
へと簡略化することで、よりクリーンで簡潔なコードを生成することを目的としています。これは、gofmt
が単なる整形ツールではなく、コードのセマンティクスを維持しつつ、よりGoらしい(idiomatic Go)記述へと変換する役割も担っていることを示しています。
前提知識の解説
gofmt
gofmt
は、Go言語のソースコードを自動的に整形するためのツールです。Goのインストールに含まれており、Goコミュニティ全体で広く利用されています。gofmt
は、インデント、スペース、改行、コメントの配置など、コードのレイアウトに関する多くの側面を自動的に処理します。これにより、異なる開発者が書いたコードでも一貫したスタイルが保たれ、コードレビューの効率化や可読性の向上に貢献します。
複合リテラル (Composite Literals)
複合リテラルは、Go言語で構造体、配列、スライス、マップなどの複合型の値を初期化するための構文です。
基本的な形式はType{element1, element2, ...}
です。
例:
- 構造体リテラル:
Point{X: 10, Y: 20}
- スライスリテラル:
[]int{1, 2, 3}
- マップリテラル:
map[string]int{"a": 1, "b": 2}
複合リテラルでは、要素の型が外側の複合リテラルの要素型から推論できる場合、内側の要素の型を省略できます。
例: []int{1, 2, 3}
の場合、1
や2
の型はint
であることが推論されます。
ポインタ型とアドレス演算子 (*
と &
)
Go言語におけるポインタは、変数のメモリアドレスを保持する変数です。
*Type
: ポインタ型を宣言します。例:*int
はint
型へのポインタです。&variable
: 変数のメモリアドレスを取得するアドレス演算子です。
例:
var x int = 10
p := &x // pはxのメモリアドレスを保持するポインタ
fmt.Println(*p) // *pはpが指す値(xの値)を取得するデリファレンス演算子
go/ast
パッケージ
go/ast
パッケージは、Go言語のソースコードの抽象構文木(Abstract Syntax Tree: AST)を表現するためのデータ構造を提供します。gofmt
のようなツールは、このASTを解析し、変更を加えてから、整形されたコードを再生成します。
ast.Node
: AST内のすべてのノードが実装するインターフェース。ast.CompositeLit
: 複合リテラルを表すASTノード。ast.UnaryExpr
: 単項演算子(例:&
,*
,-
,+
)を含む式を表すASTノード。ast.StarExpr
: ポインタ型(例:*T
)を表すASTノード。ast.KeyValueExpr
: マップリテラルや構造体リテラルにおけるキーと値のペア(例:Key: Value
)を表すASTノード。
go/token
パッケージ
go/token
パッケージは、Go言語のトークン(キーワード、識別子、演算子など)を定義します。このコミットでは、アドレス演算子&
を表すtoken.AND
が使用されています。
技術的詳細
このコミットの技術的な核心は、gofmt
のコード簡略化ロジック、特にsrc/cmd/gofmt/simplify.go
ファイル内のsimplifier
構造体のVisit
メソッドにあります。Visit
メソッドは、ASTを走査し、特定のパターンに合致するノードを簡略化する役割を担っています。
変更は、複合リテラル(ast.CompositeLit
)の要素を処理する部分に集中しています。既存のロジックでは、内側の複合リテラルの型が外側の要素型と一致する場合に、内側の型を省略する機能がありました。このコミットは、その機能をポインタ型に拡張します。
新しいロジックは以下の条件をチェックします。
-
外側の複合リテラルの要素型がポインタ型であること:
eltType.(*ast.StarExpr)
で、外側の複合リテラルの要素型が*T
のようなポインタ型であるかを確認します。ptr
はそのポインタ型を表すast.StarExpr
ノードになります。 -
現在の要素がアドレス演算子 (
&
) を伴う単項式であること:x.(*ast.UnaryExpr)
で、現在の要素が&
のような単項演算子を持つ式であるかを確認します。さらに、addr.Op == token.AND
で、その演算子が具体的にアドレス演算子&
であることを確認します。 -
アドレス演算子のオペランドが複合リテラルであること:
addr.X.(*ast.CompositeLit)
で、&
の直後にある式が複合リテラル(T{...}
)であるかを確認します。inner
はその複合リテラルを表すast.CompositeLit
ノードになります。 -
内側の複合リテラルの型が、外側のポインタ型の基底型と一致すること:
match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type))
というmatch
関数呼び出しで、この重要な型の一致を検証します。ptr.X
は、外側のポインタ型*T
の基底型T
を表します。inner.Type
は、内側の複合リテラルT{...}
の明示的な型T
を表します。 このmatch
関数は、2つの型が同じであるか、または互換性があるかをチェックします。
これらの条件がすべて満たされた場合、gofmt
は以下の簡略化を実行します。
inner.Type = nil
: 内側の複合リテラルの明示的な型(T
)を削除します。これにより、T{...}
が{...}
になります。*px = inner
: 現在の要素(&T{...}
全体)を、簡略化された内側の複合リテラル({...}
)で置き換えます。px
は要素へのポインタであるため、この代入によりASTが直接変更されます。
この変更により、gofmt
は[]*T{&T{1, 2}}
のようなコードを[]*T{{1, 2}}
のように整形できるようになります。これは、Goの型推論の能力をgofmt
が活用し、より簡潔なコードを生成する一例です。
コアとなるコードの変更箇所
変更はsrc/cmd/gofmt/simplify.go
ファイルに集中しています。
--- a/src/cmd/gofmt/simplify.go
+++ b/src/cmd/gofmt/simplify.go
@@ -6,6 +6,7 @@ package main
import (
"go/ast"
+ "go/token"
"reflect"
)
@@ -26,10 +27,12 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor {
if eltType != nil {
typ := reflect.ValueOf(eltType)
- for _, x := range outer.Elts {
+ for i, x := range outer.Elts {
+ px := &outer.Elts[i]
// look at value of indexed/named elements
if t, ok := x.(*ast.KeyValueExpr); ok {
x = t.Value
+ px = &t.Value
}
simplify(x)
// if the element is a composite literal and its literal type
@@ -40,6 +43,19 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor {\n inner.Type = nil\n }\n }\n+ // if the outer literal's element type is a pointer type *T\n+ // and the element is & of a composite literal of type T,\n+ // the inner &T may be omitted.\n+ if ptr, ok := eltType.(*ast.StarExpr); ok {\n+ if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {\n+ if inner, ok := addr.X.(*ast.CompositeLit); ok {\n+ if match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {\n+ inner.Type = nil // drop T\n+ *px = inner // drop &\n+ }\n+ }\n+ }\n+ }\n }\
// node was simplified - stop walk (there are no subnodes to simplify)
また、テストデータも追加されています。
src/cmd/gofmt/testdata/composites.golden
src/cmd/gofmt/testdata/composites.input
これらのテストデータは、&T{}
形式の複合リテラルがgofmt
によってどのように簡略化されるかを示しています。
コアとなるコードの解説
src/cmd/gofmt/simplify.go
の変更点
-
go/token
パッケージのインポート追加:token.AND
定数を使用するために、go/token
パッケージがインポートされました。これは、アドレス演算子&
を識別するために必要です。 -
要素のイテレーションとポインタの導入:
for i, x := range outer.Elts { px := &outer.Elts[i] // ... if t, ok := x.(*ast.KeyValueExpr); ok { x = t.Value px = &t.Value }
for
ループがrange outer.Elts
からfor i, x := range outer.Elts
に変更され、インデックスi
が取得されるようになりました。これは、要素を直接変更するために、outer.Elts[i]
へのポインタpx
を導入するためです。KeyValueExpr
の場合も、値の部分へのポインタをpx
に設定しています。これにより、後で*px = inner
のように代入することで、ASTのノードをインプレースで置き換えることが可能になります。 -
新しい簡略化ロジックの追加: このコミットの主要な変更は、以下の新しい
if
ブロックです。if ptr, ok := eltType.(*ast.StarExpr); ok { if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND { if inner, ok := addr.X.(*ast.CompositeLit); ok { if match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) { inner.Type = nil // drop T *px = inner // drop & } } } }
eltType.(*ast.StarExpr)
: 外側の複合リテラルの要素型がポインタ型(例:*T
)であるかをチェックします。ptr
は*T
のASTノードです。x.(*ast.UnaryExpr) && addr.Op == token.AND
: 現在処理している要素x
が単項式であり、その演算子がアドレス演算子&
であるかをチェックします。addr
は&
を含む単項式のASTノードです。addr.X.(*ast.CompositeLit)
:&
のオペランド(&
の直後の式)が複合リテラルであるかをチェックします。inner
はT{...}
のASTノードです。match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type))
: ここが最も重要な部分で、型の一致を検証します。ptr.X
:*T
のT
の部分(ポインタの基底型)のreflect.Value
。inner.Type
:T{...}
のT
の部分(複合リテラルの型)のreflect.Value
。match
関数は、これら二つの型が同じであるか、または互換性があるかを判断します。
- 条件がすべて満たされた場合:
inner.Type = nil
: 内側の複合リテラルから明示的な型情報を削除します。これにより、T{...}
が{...}
に変わります。*px = inner
:&T{...}
という元の要素全体を、型情報が削除された{...}
という複合リテラルに置き換えます。px
が要素へのポインタであるため、この代入がASTに直接反映されます。
この一連の処理により、gofmt
は[]*T{&T{1, 2}}
のような冗長な記述を、よりGoらしい簡潔な[]*T{{1, 2}}
へと自動的に整形できるようになります。これは、gofmt
が単なる字句的な整形だけでなく、Go言語のセマンティクスを理解し、コードの表現を改善する能力を持っていることを示しています。
関連リンク
- Go CL (Code Review) へのリンク: https://golang.org/cl/5448086
参考にした情報源リンク
- Go言語公式ドキュメント: https://golang.org/doc/
gofmt
に関する情報: https://golang.org/cmd/gofmt/- Go言語の複合リテラルに関する情報: https://go.dev/ref/spec#Composite_literals
- Go言語のポインタに関する情報: https://go.dev/ref/spec#Address_operators
go/ast
パッケージのドキュメント: https://pkg.go.dev/go/astgo/token
パッケージのドキュメント: https://pkg.go.dev/go/token