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

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

このコミットは、Go言語の公式フォーマッタであるgofmtおよびその基盤となるgo/printerパッケージにおける、パラメータ型を囲む不要な括弧の出力を抑制するための変更です。これにより、生成されるGoコードの可読性とGoの慣用的なスタイルへの準拠が向上します。

コミット

commit 0141c92a5331c9aa7c1ac35f54bb3082a38520a3
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Jan 9 11:32:16 2013 -0800

    go/printer, gofmt: don't print unneeded parentheses around parameter types
    
    Fixes #4624.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/7058052

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

https://github.com/golang/go/commit/0141c92a5331c9aa7c1ac35f54bb3082a38520a3

元コミット内容

go/printer, gofmt: don't print unneeded parentheses around parameter types

このコミットは、Goのプリンタとフォーマッタが、関数やメソッドのパラメータ型、および戻り値の型宣言において、不必要に付加される括弧を出力しないように修正します。これにより、生成されるコードがより簡潔で、Goの標準的なコーディングスタイルに沿ったものになります。

変更の背景

Go言語には、コードのフォーマットを自動的に行うgofmtというツールがあります。このツールは、Goのコードベース全体で一貫したスタイルを強制し、可読性を高めることを目的としています。gofmtは内部的にgo/printerパッケージを利用して、抽象構文木(AST)からGoコードを再構築します。

このコミット以前は、特定の状況下で、go/printerがパラメータ型や戻り値の型に不要な括弧を付加してしまう問題がありました。例えば、(int)のように単一の型を括弧で囲むことは、Goの文法上は許容されますが、慣用的ではありません。特に、((((((int))))))のように多重に括弧がネストされた場合、コードの可読性が著しく損なわれます。

この問題は、Goのコードベース全体で一貫したフォーマットを維持するというgofmtの目標に反していました。そのため、不要な括弧を自動的に除去し、よりクリーンなコードを生成することが求められていました。コミットメッセージにあるFixes #4624は、この問題が報告されたイシュートラッカーのIDを示していますが、コミットが2013年のものであるため、現在のGitHubのイシュートラッカーとは異なる古いシステムでのIDである可能性が高いです。

前提知識の解説

  • go/printerパッケージ: Go言語のソースコードを整形し、出力するためのパッケージです。Goの抽象構文木(AST)を受け取り、それを整形されたGoコードとしてバイトストリームに書き出します。gofmtツールはこのパッケージを内部的に利用しています。
  • gofmtツール: Go言語のソースコードを自動的にフォーマットするコマンドラインツールです。Goの標準的なコーディングスタイルに準拠させることで、コードの可読性と一貫性を高めます。
  • 抽象構文木(AST: Abstract Syntax Tree): プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやリンタ、フォーマッタなどのツールがコードを解析・操作する際に利用します。Go言語ではgo/astパッケージでASTを扱います。
  • ast.Expr: GoのASTにおける「式」を表すインターフェースです。変数、リテラル、関数呼び出し、型など、様々なものが式として表現されます。
  • ast.ParenExpr: ast.Exprの一種で、括弧で囲まれた式を表します。例えば、(a + b)という式はast.ParenExprとしてASTに表現されます。
  • 慣用的なGoのスタイル: Go言語には、公式ドキュメントやコミュニティによって推奨される特定のコーディングスタイルがあります。gofmtはこのスタイルを自動的に適用することで、Goコードの一貫性を保ちます。不要な括弧を避けることは、この慣用的なスタイルの一部です。

技術的詳細

このコミットの主要な変更点は、go/printerパッケージがASTを走査し、コードを整形して出力する際に、パラメータ型や戻り値の型から不要なast.ParenExpr(括弧で囲まれた式)を再帰的に取り除く新しいヘルパー関数stripParensAlwaysを導入したことです。

以前のgo/printerは、ast.FieldList(関数のパラメータや戻り値のリスト)を処理する際に、各フィールドの型(par.Typeresult.List[0].Type)を直接p.expr()メソッドに渡していました。p.expr()はASTノードを整形して出力する役割を担いますが、この際にast.ParenExprがネストされていると、そのまま括弧が出力されてしまっていました。

新しいstripParensAlways関数は、引数としてast.Exprを受け取り、それがast.ParenExprである限り、その内部の式(x.X)を再帰的に辿っていきます。最終的にast.ParenExprではない最も内側の式を返します。これにより、((((((int))))))のような多重にネストされた括弧も、intという本来の型のみが抽出されるようになります。

このstripParensAlways関数が、nodes.go内のparameters関数とsignature関数で、パラメータ型や戻り値の型を処理する直前に適用されるようになりました。これにより、プリンタが実際に型を出力する前に、不要な括弧が取り除かれるため、整形されたコードには簡潔な型宣言のみが含まれるようになります。

また、printer.goの変更は、tabwriter.Writerの型アサーションに関するもので、これは直接的な括弧の除去とは関係なく、Goの型アサーションの構文の改善(tw, _ := (output).(*tabwriter.Writer)からtw, _ := output.(*tabwriter.Writer)への変更)であり、このコミットの主要な目的とは異なりますが、同時に行われたクリーンアップの一部と考えられます。

テストデータ(declarations.inputdeclarations.golden)の追加は、この変更が意図通りに機能し、様々なケースで不要な括弧が除去されることを検証するために行われました。特に、単一の型、複数の型、チャネル型など、括弧が問題となりうる多様なシナリオがカバーされています。

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

src/pkg/go/printer/nodes.go

--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -307,7 +307,7 @@ func (p *printer) parameters(fields *ast.FieldList) {
 			p.print(blank)
 		}
 		// parameter type
-		p.expr(par.Type)
+		p.expr(stripParensAlways(par.Type))
 		prevLine = parLineEnd
 	}
 	// if the closing ")" is on a separate line from the last parameter,
@@ -336,7 +336,7 @@ func (p *printer) signature(params, result *ast.FieldList) {
 		p.print(blank)
 		if n == 1 && result.List[0].Names == nil {
 			// single anonymous result; no ()'s
-			p.expr(result.List[0].Type)
+			p.expr(stripParensAlways(result.List[0].Type))
 			return
 		}
 		p.parameters(result)
@@ -959,6 +959,13 @@ func stripParens(x ast.Expr) ast.Expr {
 	return x
 }
 
+func stripParensAlways(x ast.Expr) ast.Expr {
+	if x, ok := x.(*ast.ParenExpr); ok {
+		return stripParensAlways(x.X)
+	}
+	return x
+}
+
 func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) {
 	p.print(blank)
 	needsBlank := false

src/pkg/go/printer/printer.go

--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -1230,7 +1230,7 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{
 	}
 
 	// flush tabwriter, if any
-	if tw, _ := (output).(*tabwriter.Writer); tw != nil {
+	if tw, _ := output.(*tabwriter.Writer); tw != nil {
 		err = tw.Flush()
 	}
 

テストデータ (src/pkg/go/printer/testdata/declarations.inputdeclarations.golden)

新しいテストケースが追加され、入力ファイルで不要な括弧を含む型宣言が定義され、ゴールデンファイルでそれらの括弧が除去された正しい出力が期待されるようになっています。

コアとなるコードの解説

  1. stripParensAlways関数の追加: src/pkg/go/printer/nodes.goに新しくstripParensAlways関数が追加されました。

    func stripParensAlways(x ast.Expr) ast.Expr {
        if x, ok := x.(*ast.ParenExpr); ok {
            return stripParensAlways(x.X)
        }
        return x
    }
    

    この関数は、与えられたast.Exprast.ParenExpr(括弧で囲まれた式)であるかどうかをチェックします。もしそうであれば、その内部の式(x.X)に対して再帰的にstripParensAlwaysを呼び出します。これにより、((int))のような多重にネストされた括弧も、最終的に最も内側のintという型(ast.Identなどの非ParenExpr)が返されるようになります。ok変数は型アサーションが成功したかどうかを示し、成功した場合にのみ再帰処理が続行されます。

  2. parameters関数でのstripParensAlwaysの利用: src/pkg/go/printer/nodes.goparameters関数は、関数のパラメータリストを処理します。この関数内で、各パラメータの型を出力する際に、以前はp.expr(par.Type)と直接型を渡していましたが、変更後はp.expr(stripParensAlways(par.Type))となりました。これにより、パラメータ型がast.ParenExprで囲まれていても、出力時には不要な括弧が除去されます。

  3. signature関数でのstripParensAlwaysの利用: src/pkg/go/printer/nodes.gosignature関数は、関数のシグネチャ(パラメータと戻り値)を処理します。特に、単一の匿名戻り値の型を処理する部分で、以前はp.expr(result.List[0].Type)でしたが、これもp.expr(stripParensAlways(result.List[0].Type))に変更されました。これにより、単一の匿名戻り値の型に不要な括弧が付いている場合も、適切に除去されます。

  4. printer.goの変更: src/pkg/go/printer/printer.goの変更は、tabwriter.Writerへの型アサーションの構文をtw, _ := (output).(*tabwriter.Writer)からtw, _ := output.(*tabwriter.Writer)へと修正したものです。これは、Goの言語仕様の進化に伴う構文の簡略化であり、このコミットの主要な目的である括弧の除去とは直接関係ありませんが、コードベース全体のクリーンアップの一環として行われたと考えられます。

これらの変更により、go/printerはよりGoの慣用的なスタイルに沿ったコードを生成するようになり、gofmtを通じて開発者が意識することなく、整形されたコードの品質が向上しました。

関連リンク

  • Gerrit Change-ID: https://golang.org/cl/7058052 (Goプロジェクトの旧コードレビューシステムであるGerritのリンク)
  • Go言語公式ドキュメント: https://go.dev/
  • gofmtに関するドキュメント: https://go.dev/blog/gofmt

参考にした情報源リンク

  • Go言語のソースコード(特にgo/printerパッケージ)
  • Go言語のASTに関するドキュメント
  • Go言語の型アサーションに関する情報
  • gofmtの動作原理に関する一般的な知識
  • GitHubのコミット履歴
  • Go言語の古いイシュートラッカーに関する一般的な情報(Fixes #4624の背景を理解するため)
    • 注: Fixes #4624の具体的なイシューページは、コミットが古い(2013年)ため、現在のGitHubイシュートラッカーでは直接見つけることができませんでした。これは、Goプロジェクトがイシュートラッカーシステムを移行したためと考えられます。