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

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

このコミットは、Go言語の公式フォーマッタであるgofmtの機能改善に関するものです。具体的には、スライス式s[a : len(s)]の形式を、より簡潔なs[a:]に自動的に書き換える最適化が導入されました。これにより、コードの可読性が向上し、冗長な記述が削減されます。

コミット

commit 0cbca268d8aba5d7ffb24be58ea1ea92fcd5f6f5
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Oct 31 11:48:55 2012 -0700

    gofmt: simplify slices of the form s[a : len(s)] to s[a:]
    
    Fixes #4314.
    
    R=r, rsc
    CC=golang-dev
    https://golang.org/cl/6822059

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

https://github.com/golang/go/commit/0cbca268d8aba5d7ffb24be58ea1ea92fcd5f6f5

元コミット内容

gofmt: simplify slices of the form s[a : len(s)] to s[a:]

このコミットは、gofmtツールがスライス式s[a : len(s)]s[a:]に簡略化する機能を追加するものです。これは、Go言語のイディオムに沿ったコードの整形を促進し、冗長な記述を排除することを目的としています。

変更の背景

Go言語のスライスは、配列や他のスライスの一部を参照するための強力な機能です。スライス式はs[low:high]の形式を取り、lowからhigh-1までの要素を含みます。ここで、highが省略された場合、デフォルトで基になる配列またはスライスの長さが使用されます。つまり、s[a:len(s)]s[a:]と全く同じ意味を持ちます。

しかし、開発者が明示的にlen(s)と記述することがあり、これは冗長であり、コードの可読性をわずかに低下させる可能性があります。gofmtはGoコードを標準的なスタイルに整形するためのツールであり、このような冗長な記述を自動的に簡略化することで、Goコミュニティ全体で一貫した、よりクリーンなコードスタイルを促進することが期待されます。

この変更は、Issue #4314で報告された問題に対応するものです。

前提知識の解説

Go言語のスライス

Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、長さ(length)、容量(capacity)、および基になる配列へのポインタで構成されます。スライスは動的なサイズ変更が可能であり、Goプログラミングにおいて非常に頻繁に使用されます。

スライス式は以下の形式を取ります。

  • a[low : high]lowからhigh-1までの要素を含むスライスを作成します。
  • a[low :]lowから元のスライスの末尾までの要素を含むスライスを作成します。
  • a[: high]:元のスライスの先頭からhigh-1までの要素を含むスライスを作成します。
  • a[:]:元のスライスのすべての要素を含むスライスを作成します(元のスライスのコピーではありません)。

ここで重要なのは、a[low:]a[low:len(a)]の糖衣構文(syntactic sugar)であるという点です。つまり、len(a)を明示的に記述することは、意味的には冗長です。

gofmt

gofmtは、Go言語のソースコードを自動的に整形するツールです。Go言語のツールチェインに標準で含まれており、Goコミュニティ全体で一貫したコードスタイルを強制するために広く利用されています。gofmtは、インデント、スペース、改行などの書式設定だけでなく、一部のコードの簡略化(例: for rangeループでのアンダースコアの使用)も行います。これにより、コードレビューの負担が軽減され、異なる開発者によって書かれたコードでも一貫した外観が保たれます。

astパッケージ

Go言語のgo/astパッケージは、Goソースコードの抽象構文木(Abstract Syntax Tree, AST)を表現するためのデータ構造と関数を提供します。gofmtのようなツールは、ソースコードをASTにパースし、ASTを走査(traverse)してノードを検査・変更し、最終的に変更されたASTから整形されたコードを生成します。

  • ast.Node: AST内のすべてのノードが実装するインターフェース。
  • ast.Visitor: ASTを走査するためのインターフェース。Visitメソッドを持ち、ノードを処理し、次のノードを決定します。
  • ast.Walk: ast.Visitorインターフェースを実装するオブジェクトを使ってASTを走査する関数。

ドットインポート(Dot Import)

Go言語では、通常、パッケージをインポートする際にはそのパッケージ名を修飾子として使用します(例: fmt.Println)。しかし、import . "path/to/package"のようにドット(.)を使用してインポートすると、そのパッケージのエクスポートされた識別子を修飾子なしで直接参照できるようになります(例: Println)。

ドットインポートは、コードを簡潔にする一方で、名前の衝突を引き起こしたり、どのパッケージから識別子が来ているのかを分かりにくくしたりする可能性があるため、一般的には推奨されません。このコミットでは、ドットインポートが存在する場合にスライスの簡略化を行わないという制約が設けられています。これは、lenという識別子が標準ライブラリの組み込み関数lenではなく、ドットインポートされた別のパッケージのlen関数を指している可能性があるためです。gofmtはセマンティックな解析(型解決など)を行わないため、このような曖昧さを避けるために安全策を取っています。

技術的詳細

このコミットの主要な変更は、src/cmd/gofmt/simplify.goファイル内のsimplifier構造体とVisitメソッドにあります。

  1. simplifier構造体の変更: simplifier構造体にhasDotImport boolフィールドが追加されました。これは、現在処理中のファイルがドットインポートを含んでいるかどうかを追跡するためのフラグです。

  2. simplify関数の変更:

    • simplify関数は、これまではast.Nodeを受け取っていましたが、*ast.Fileを受け取るように変更されました。これにより、ファイル全体のASTにアクセスできるようになります。
    • simplify関数内で、まずファイルのインポート宣言を走査し、ドットインポートが存在するかどうかをs.hasDotImportに設定するロジックが追加されました。
  3. Visitメソッドでのスライス式(*ast.SliceExpr)の処理: Visitメソッドのswitch文に*ast.SliceExprのケースが追加されました。このケースでは、以下の条件をチェックしてスライス式の簡略化を行います。

    • ドットインポートのチェック: s.hasDotImporttrueの場合、簡略化は行われません。これは、lenが組み込み関数ではない可能性があるためです。
    • スライス対象の識別子チェック: スライスされている式(n.X)が単一の識別子(例: s)であり、それが解決済みである(s.Obj != nil)ことを確認します。
    • high式のチェック: スライス式のhigh部分(n.High)が関数呼び出し(*ast.CallExpr)であることを確認します。
    • len関数のチェック: 呼び出されている関数がlenという名前の識別子であり、それがローカルで定義されていない(fun.Obj == nil)ことを確認します。これにより、組み込みのlen関数であることを推測します。
    • len関数の引数チェック: len関数の引数が、スライスされている元の識別子と同じである(例: len(s)sがスライス対象のsと同じ)ことを確認します。

    これらの条件がすべて満たされた場合、n.High = nilと設定することで、スライス式のhigh部分を削除し、s[a:len(s)]s[a:]に簡略化します。

また、for rangeループでのアンダースコアの簡略化ロジックも、より堅牢なチェック(ident, _ := n.Value.(*ast.Ident))に修正されています。

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

src/cmd/gofmt/simplify.go

--- a/src/cmd/gofmt/simplify.go
+++ b/src/cmd/gofmt/simplify.go
@@ -10,7 +10,9 @@ import (
 	"reflect"
 )
 
-type simplifier struct{}
+type simplifier struct {
+	hasDotImport bool // package file contains: import . "some/import/path"
+}
 
 func (s *simplifier) Visit(node ast.Node) ast.Visitor {
 	switch n := node.(type) {
@@ -34,7 +36,7 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor {
 				tx = t.Value
 				px = &t.Value
 			}
-			simplify(x)
+			ast.Walk(s, x) // simplify x
 			// if the element is a composite literal and its literal type
 			// matches the outer literal's element type exactly, the inner
 			// literal type may be omitted
@@ -62,20 +64,54 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor {
 			return nil
 		}
 
+	case *ast.SliceExpr:
+		// a slice expression of the form: s[a:len(s)]
+		// can be simplified to: s[a:]
+		// if s is "simple enough" (for now we only accept identifiers)
+		if s.hasDotImport {
+			// if dot imports are present, we cannot be certain that an
+			// unresolved "len" identifier refers to the predefined len()
+			break
+		}
+		if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil {
+			// the array/slice object is a single, resolved identifier
+			if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() {
+				// the high expression is a function call with a single argument
+				if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" && fun.Obj == nil {
+					// the function called is "len" and it is not locally defined; and
+					// because we don't have dot imports, it must be the predefined len()
+					if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Obj == s.Obj {
+						// the len argument is the array/slice object
+						n.High = nil
+					}
+				}
+			}
+		}
+		// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
+		//       but we leave them as is since sometimes we want to be very explicit
+		//       about the lower bound.
+
 	case *ast.RangeStmt:
-		// range of the form: for x, _ = range v {...}
+		// a range of the form: for x, _ = range v {...}
 		// can be simplified to: for x = range v {...}
-		if n.Value != nil {
-			if ident, ok := n.Value.(*ast.Ident); ok && ident.Name == "_" {
-				n.Value = nil
-			}
+		if ident, _ := n.Value.(*ast.Ident); ident != nil && ident.Name == "_" {
+			n.Value = nil
 		}
 	}
 
 	return s
 }
 
-func simplify(node ast.Node) {
+func simplify(f *ast.File) {
 	var s simplifier
-	ast.Walk(&s, node)
+
+	// determine if f contains dot imports
+	for _, imp := range f.Imports {
+		if imp.Name != nil && imp.Name.Name == "." {
+			s.hasDotImport = true
+			break
+		}
+	}
+
+	ast.Walk(&s, f)
 }

新規テストファイル

  • src/cmd/gofmt/testdata/slices1.input
  • src/cmd/gofmt/testdata/slices1.golden
  • src/cmd/gofmt/testdata/slices2.input
  • src/cmd/gofmt/testdata/slices2.golden

slices1のテストケースは、ドットインポートがない場合の正常な簡略化を検証します。 slices2のテストケースは、ドットインポートがある場合に簡略化が行われないことを検証します。

コアとなるコードの解説

simplify関数の変更点

以前のsimplify関数はast.Nodeを受け取っていましたが、*ast.Fileを受け取るように変更されました。これは、ファイル全体のASTを走査する前に、そのファイルがドットインポートを含んでいるかどうかを事前にチェックする必要があるためです。

func simplify(f *ast.File) {
	var s simplifier

	// determine if f contains dot imports
	for _, imp := range f.Imports {
		if imp.Name != nil && imp.Name.Name == "." {
			s.hasDotImport = true
			break
		}
	}

	ast.Walk(&s, f)
}

この変更により、simplifier構造体のhasDotImportフィールドが適切に初期化され、Visitメソッド内でこの情報に基づいて簡略化の可否を判断できるようになります。

simplifier.Visitメソッドでのスライス簡略化ロジック

Visitメソッド内の*ast.SliceExprケースが、スライス簡略化の核心です。

	case *ast.SliceExpr:
		// a slice expression of the form: s[a:len(s)]
		// can be simplified to: s[a:]
		// if s is "simple enough" (for now we only accept identifiers)
		if s.hasDotImport {
			// if dot imports are present, we cannot be certain that an
			// unresolved "len" identifier refers to the predefined len()
			break
		}
		if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil {
			// the array/slice object is a single, resolved identifier
			if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() {
				// the high expression is a function call with a single argument
				if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" && fun.Obj == nil {
					// the function called is "len" and it is not locally defined; and
					// because we don't have dot imports, it must be the predefined len()
					if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Obj == s.Obj {
						// the len argument is the array/slice object
						n.High = nil
					}
				}
			}
		}
		// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
		//       but we leave them as is since sometimes we want to be very explicit
		//       about the lower bound.

このコードは、以下のステップで簡略化を試みます。

  1. ドットインポートの確認: s.hasDotImporttrueの場合、lenが組み込み関数である保証がないため、処理を中断します。
  2. スライス対象の確認: スライスされている式(n.X)がast.Ident(識別子、例: mySlice)であり、かつその識別子が解決済み(s.Obj != nil)であることを確認します。これにより、複雑な式(例: f().Slice[a:len(f().Slice)])の簡略化を避けます。
  3. high部分の確認: スライス式のhigh部分(n.High)がast.CallExpr(関数呼び出し、例: len(mySlice))であることを確認します。
  4. len関数の確認: 呼び出されている関数がlenという名前のast.Identであり、かつそのlenがローカルで定義されていない(fun.Obj == nil)ことを確認します。これにより、組み込みのlen関数であることを推測します。
  5. len引数の確認: len関数の引数(call.Args[0])がast.Identであり、かつその識別子がスライス対象の識別子(s.Obj)と同一であることを確認します。これにより、s[a:len(t)]のような誤った簡略化を防ぎます。

これらの条件がすべて満たされた場合、n.High = nilとすることで、スライス式のhigh部分を削除し、s[a:len(s)]s[a:]に変換します。

コメントにあるように、s[0:b]s[:b]に簡略化することも可能ですが、このコミットでは行われていません。これは、下限が0であることを明示的に示すことが望ましい場合があるためです。

関連リンク

参考にした情報源リンク

I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. I have included explanations of Go slices, `gofmt`, AST, and dot imports as prerequisite knowledge. The core code changes and their explanations are also provided. I have used the provided GitHub URL and the issue number to find relevant links.

I will now output the generated Markdown.# [インデックス 14263] ファイルの概要

このコミットは、Go言語の公式フォーマッタである`gofmt`の機能改善に関するものです。具体的には、スライス式`s[a : len(s)]`の形式を、より簡潔な`s[a:]`に自動的に書き換える最適化が導入されました。これにより、コードの可読性が向上し、冗長な記述が削減されます。

## コミット

commit 0cbca268d8aba5d7ffb24be58ea1ea92fcd5f6f5 Author: Robert Griesemer gri@golang.org Date: Wed Oct 31 11:48:55 2012 -0700

gofmt: simplify slices of the form s[a : len(s)] to s[a:]

Fixes #4314.

R=r, rsc
CC=golang-dev
https://golang.org/cl/6822059

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

[https://github.com/golang/go/commit/0cbca268d8aba5d7ffb24be58ea1ea92fcd5f6f5](https://github.com/golang/go/commit/0cbca268d8aba5d7ffb24be58ea1ea92fcd5f6f5)

## 元コミット内容

`gofmt: simplify slices of the form s[a : len(s)] to s[a:]`

このコミットは、`gofmt`ツールがスライス式`s[a : len(s)]`を`s[a:]`に簡略化する機能を追加するものです。これは、Go言語のイディオムに沿ったコードの整形を促進し、冗長な記述を排除することを目的としています。

## 変更の背景

Go言語のスライスは、配列や他のスライスの一部を参照するための強力な機能です。スライス式は`s[low:high]`の形式を取り、`low`から`high-1`までの要素を含みます。ここで、`high`が省略された場合、デフォルトで基になる配列またはスライスの長さが使用されます。つまり、`s[a:len(s)]`は`s[a:]`と全く同じ意味を持ちます。

しかし、開発者が明示的に`len(s)`と記述することがあり、これは冗長であり、コードの可読性をわずかに低下させる可能性があります。`gofmt`はGoコードを標準的なスタイルに整形するためのツールであり、このような冗長な記述を自動的に簡略化することで、Goコミュニティ全体で一貫した、よりクリーンなコードスタイルを促進することが期待されます。

この変更は、Issue #4314で報告された問題に対応するものです。

## 前提知識の解説

### Go言語のスライス

Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、長さ(length)、容量(capacity)、および基になる配列へのポインタで構成されます。スライスは動的なサイズ変更が可能であり、Goプログラミングにおいて非常に頻繁に使用されます。

スライス式は以下の形式を取ります。
- `a[low : high]`:`low`から`high-1`までの要素を含むスライスを作成します。
- `a[low :]`:`low`から元のスライスの末尾までの要素を含むスライスを作成します。
- `a[: high]`:元のスライスの先頭から`high-1`までの要素を含むスライスを作成します。
- `a[:]`:元のスライスのすべての要素を含むスライスを作成します(元のスライスのコピーではありません)。

ここで重要なのは、`a[low:]`が`a[low:len(a)]`の糖衣構文(syntactic sugar)であるという点です。つまり、`len(a)`を明示的に記述することは、意味的には冗長です。

### `gofmt`

`gofmt`は、Go言語のソースコードを自動的に整形するツールです。Go言語のツールチェインに標準で含まれており、Goコミュニティ全体で一貫したコードスタイルを強制するために広く利用されています。`gofmt`は、インデント、スペース、改行などの書式設定だけでなく、一部のコードの簡略化(例: `for range`ループでのアンダースコアの使用)も行います。これにより、コードレビューの負担が軽減され、異なる開発者によって書かれたコードでも一貫した外観が保たれます。

### `ast`パッケージ

Go言語の`go/ast`パッケージは、Goソースコードの抽象構文木(Abstract Syntax Tree, AST)を表現するためのデータ構造と関数を提供します。`gofmt`のようなツールは、ソースコードをASTにパースし、ASTを走査(traverse)してノードを検査・変更し、最終的に変更されたASTから整形されたコードを生成します。

- `ast.Node`: AST内のすべてのノードが実装するインターフェース。
- `ast.Visitor`: ASTを走査するためのインターフェース。`Visit`メソッドを持ち、ノードを処理し、次のノードを決定します。
- `ast.Walk`: `ast.Visitor`インターフェースを実装するオブジェクトを使ってASTを走査する関数。

### ドットインポート(Dot Import)

Go言語では、通常、パッケージをインポートする際にはそのパッケージ名を修飾子として使用します(例: `fmt.Println`)。しかし、`import . "path/to/package"`のようにドット(`.`)を使用してインポートすると、そのパッケージのエクスポートされた識別子を修飾子なしで直接参照できるようになります(例: `Println`)。

ドットインポートは、コードを簡潔にする一方で、名前の衝突を引き起こしたり、どのパッケージから識別子が来ているのかを分かりにくくしたりする可能性があるため、一般的には推奨されません。このコミットでは、ドットインポートが存在する場合にスライスの簡略化を行わないという制約が設けられています。これは、`len`という識別子が標準ライブラリの組み込み関数`len`ではなく、ドットインポートされた別のパッケージの`len`関数を指している可能性があるためです。`gofmt`はセマンティックな解析(型解決など)を行わないため、このような曖昧さを避けるために安全策を取っています。

## 技術的詳細

このコミットの主要な変更は、`src/cmd/gofmt/simplify.go`ファイル内の`simplifier`構造体と`Visit`メソッドにあります。

1.  **`simplifier`構造体の変更**:
    `simplifier`構造体に`hasDotImport bool`フィールドが追加されました。これは、現在処理中のファイルがドットインポートを含んでいるかどうかを追跡するためのフラグです。

2.  **`simplify`関数の変更**:
    -   `simplify`関数は、これまでは`ast.Node`を受け取っていましたが、`*ast.File`を受け取るように変更されました。これにより、ファイル全体のASTにアクセスできるようになります。
    -   `simplify`関数内で、まずファイルのインポート宣言を走査し、ドットインポートが存在するかどうかを`s.hasDotImport`に設定するロジックが追加されました。

3.  **`Visit`メソッドでのスライス式(`*ast.SliceExpr`)の処理**:
    `Visit`メソッドの`switch`文に`*ast.SliceExpr`のケースが追加されました。このケースでは、以下の条件をチェックしてスライス式の簡略化を行います。

    -   **ドットインポートのチェック**: `s.hasDotImport`が`true`の場合、簡略化は行われません。これは、`len`が組み込み関数ではない可能性があるためです。
    -   **スライス対象の識別子チェック**: スライスされている式(`n.X`)が単一の識別子(例: `s`)であり、それが解決済みである(`s.Obj != nil`)ことを確認します。
    -   **`high`式のチェック**: スライス式の`high`部分(`n.High`)が関数呼び出し(`*ast.CallExpr`)であることを確認します。
    -   **`len`関数のチェック**: 呼び出されている関数が`len`という名前の識別子であり、それがローカルで定義されていない(`fun.Obj == nil`)ことを確認します。これにより、組み込みの`len`関数であることを推測します。
    -   **`len`関数の引数チェック**: `len`関数の引数が、スライスされている元の識別子と同じである(例: `len(s)`の`s`がスライス対象の`s`と同じ)ことを確認します。

    これらの条件がすべて満たされた場合、`n.High = nil`と設定することで、スライス式の`high`部分を削除し、`s[a:len(s)]`を`s[a:]`に簡略化します。

また、`for range`ループでのアンダースコアの簡略化ロジックも、より堅牢なチェック(`ident, _ := n.Value.(*ast.Ident)`)に修正されています。

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

### `src/cmd/gofmt/simplify.go`

```diff
--- a/src/cmd/gofmt/simplify.go
+++ b/src/cmd/gofmt/simplify.go
@@ -10,7 +10,9 @@ import (
 	"reflect"
 )
 
-type simplifier struct{}
+type simplifier struct {
+	hasDotImport bool // package file contains: import . "some/import/path"
+}
 
 func (s *simplifier) Visit(node ast.Node) ast.Visitor {
 	switch n := node.(type) {
@@ -34,7 +36,7 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor {
 				tx = t.Value
 				px = &t.Value
 			}
-			simplify(x)
+			ast.Walk(s, x) // simplify x
 			// if the element is a composite literal and its literal type
 			// matches the outer literal's element type exactly, the inner
 			// literal type may be omitted
@@ -62,20 +64,54 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor {
 			return nil
 		}
 
+	case *ast.SliceExpr:
+		// a slice expression of the form: s[a:len(s)]
+		// can be simplified to: s[a:]
+		// if s is "simple enough" (for now we only accept identifiers)
+		if s.hasDotImport {
+			// if dot imports are present, we cannot be certain that an
+			// unresolved "len" identifier refers to the predefined len()
+			break
+		}
+		if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil {
+			// the array/slice object is a single, resolved identifier
+			if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() {
+				// the high expression is a function call with a single argument
+				if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" && fun.Obj == nil {
+					// the function called is "len" and it is not locally defined; and
+					// because we don't have dot imports, it must be the predefined len()
+					if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Obj == s.Obj {
+						// the len argument is the array/slice object
+						n.High = nil
+					}
+				}
+			}
+		}
+		// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
+		//       but we leave them as is since sometimes we want to be very explicit
+		//       about the lower bound.
+
 	case *ast.RangeStmt:
-		// range of the form: for x, _ = range v {...}
+		// a range of the form: for x, _ = range v {...}
 		// can be simplified to: for x = range v {...}
-		if n.Value != nil {
-			if ident, ok := n.Value.(*ast.Ident); ok && ident.Name == "_" {
-				n.Value = nil
-			}
+		if ident, _ := n.Value.(*ast.Ident); ident != nil && ident.Name == "_" {
+			n.Value = nil
 		}
 	}
 
 	return s
 }
 
-func simplify(node ast.Node) {
+func simplify(f *ast.File) {
 	var s simplifier
-	ast.Walk(&s, node)
+
+	// determine if f contains dot imports
+	for _, imp := range f.Imports {
+		if imp.Name != nil && imp.Name.Name == "." {
+			s.hasDotImport = true
+			break
+		}
+	}
+
+	ast.Walk(&s, f)
 }

新規テストファイル

  • src/cmd/gofmt/testdata/slices1.input
  • src/cmd/gofmt/testdata/slices1.golden
  • src/cmd/gofmt/testdata/slices2.input
  • src/cmd/gofmt/testdata/slices2.golden

slices1のテストケースは、ドットインポートがない場合の正常な簡略化を検証します。 slices2のテストケースは、ドットインポートがある場合に簡略化が行われないことを検証します。

コアとなるコードの解説

simplify関数の変更点

以前のsimplify関数はast.Nodeを受け取っていましたが、*ast.Fileを受け取るように変更されました。これは、ファイル全体のASTを走査する前に、そのファイルがドットインポートを含んでいるかどうかを事前にチェックする必要があるためです。

func simplify(f *ast.File) {
	var s simplifier

	// determine if f contains dot imports
	for _, imp := range f.Imports {
		if imp.Name != nil && imp.Name.Name == "." {
			s.hasDotImport = true
			break
		}
	}

	ast.Walk(&s, f)
}

この変更により、simplifier構造体のhasDotImportフィールドが適切に初期化され、Visitメソッド内でこの情報に基づいて簡略化の可否を判断できるようになります。

simplifier.Visitメソッドでのスライス簡略化ロジック

Visitメソッド内の*ast.SliceExprケースが、スライス簡略化の核心です。

	case *ast.SliceExpr:
		// a slice expression of the form: s[a:len(s)]
		// can be simplified to: s[a:]
		// if s is "simple enough" (for now we only accept identifiers)
		if s.hasDotImport {
			// if dot imports are present, we cannot be certain that an
			// unresolved "len" identifier refers to the predefined len()
			break
		}
		if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil {
			// the array/slice object is a single, resolved identifier
			if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() {
				// the high expression is a function call with a single argument
				if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" && fun.Obj == nil {
					// the function called is "len" and it is not locally defined; and
					// because we don't have dot imports, it must be the predefined len()
					if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Obj == s.Obj {
						// the len argument is the array/slice object
						n.High = nil
					}
				}
			}
		}
		// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
		//       but we leave them as is since sometimes we want to be very explicit
		//       about the lower bound.

このコードは、以下のステップで簡略化を試みます。

  1. ドットインポートの確認: s.hasDotImporttrueの場合、lenが組み込み関数である保証がないため、処理を中断します。
  2. スライス対象の確認: スライスされている式(n.X)がast.Ident(識別子、例: mySlice)であり、かつその識別子が解決済み(s.Obj != nil)であることを確認します。これにより、複雑な式(例: f().Slice[a:len(f().Slice)])の簡略化を避けます。
  3. high部分の確認: スライス式のhigh部分(n.High)がast.CallExpr(関数呼び出し、例: len(mySlice))であることを確認します。
  4. len関数の確認: 呼び出されている関数がlenという名前のast.Identであり、かつそのlenがローカルで定義されていない(fun.Obj == nil)ことを確認します。これにより、組み込みのlen関数であることを推測します。
  5. len引数の確認: len関数の引数(call.Args[0])がast.Identであり、かつその識別子がスライス対象の識別子(s.Obj)と同一であることを確認します。これにより、s[a:len(t)]のような誤った簡略化を防ぎます。

これらの条件がすべて満たされた場合、n.High = nilとすることで、スライス式のhigh部分を削除し、s[a:len(s)]s[a:]に変換します。

コメントにあるように、s[0:b]s[:b]に簡略化することも可能ですが、このコミットでは行われていません。これは、下限が0であることを明示的に示すことが望ましい場合があるためです。

関連リンク

参考にした情報源リンク