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

[インデックス 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} の場合、12の型はintであることが推論されます。

ポインタ型とアドレス演算子 (*&)

Go言語におけるポインタは、変数のメモリアドレスを保持する変数です。

  • *Type: ポインタ型を宣言します。例: *intint型へのポインタです。
  • &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)の要素を処理する部分に集中しています。既存のロジックでは、内側の複合リテラルの型が外側の要素型と一致する場合に、内側の型を省略する機能がありました。このコミットは、その機能をポインタ型に拡張します。

新しいロジックは以下の条件をチェックします。

  1. 外側の複合リテラルの要素型がポインタ型であること: eltType.(*ast.StarExpr)で、外側の複合リテラルの要素型が*Tのようなポインタ型であるかを確認します。ptrはそのポインタ型を表すast.StarExprノードになります。

  2. 現在の要素がアドレス演算子 (&) を伴う単項式であること: x.(*ast.UnaryExpr)で、現在の要素が&のような単項演算子を持つ式であるかを確認します。さらに、addr.Op == token.ANDで、その演算子が具体的にアドレス演算子&であることを確認します。

  3. アドレス演算子のオペランドが複合リテラルであること: addr.X.(*ast.CompositeLit)で、&の直後にある式が複合リテラル(T{...})であるかを確認します。innerはその複合リテラルを表すast.CompositeLitノードになります。

  4. 内側の複合リテラルの型が、外側のポインタ型の基底型と一致すること: 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の変更点

  1. go/tokenパッケージのインポート追加: token.AND定数を使用するために、go/tokenパッケージがインポートされました。これは、アドレス演算子&を識別するために必要です。

  2. 要素のイテレーションとポインタの導入:

    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のノードをインプレースで置き換えることが可能になります。

  3. 新しい簡略化ロジックの追加: このコミットの主要な変更は、以下の新しい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): &のオペランド(&の直後の式)が複合リテラルであるかをチェックします。innerT{...}のASTノードです。
    • match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)): ここが最も重要な部分で、型の一致を検証します。
      • ptr.X: *TTの部分(ポインタの基底型)のreflect.Value
      • inner.Type: T{...}Tの部分(複合リテラルの型)のreflect.Valuematch関数は、これら二つの型が同じであるか、または互換性があるかを判断します。
    • 条件がすべて満たされた場合:
      • inner.Type = nil: 内側の複合リテラルから明示的な型情報を削除します。これにより、T{...}{...}に変わります。
      • *px = inner: &T{...}という元の要素全体を、型情報が削除された{...}という複合リテラルに置き換えます。pxが要素へのポインタであるため、この代入がASTに直接反映されます。

この一連の処理により、gofmt[]*T{&T{1, 2}}のような冗長な記述を、よりGoらしい簡潔な[]*T{{1, 2}}へと自動的に整形できるようになります。これは、gofmtが単なる字句的な整形だけでなく、Go言語のセマンティクスを理解し、コードの表現を改善する能力を持っていることを示しています。

関連リンク

参考にした情報源リンク