[インデックス 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
メソッドにあります。
-
simplifier
構造体の変更:simplifier
構造体にhasDotImport bool
フィールドが追加されました。これは、現在処理中のファイルがドットインポートを含んでいるかどうかを追跡するためのフラグです。 -
simplify
関数の変更:simplify
関数は、これまではast.Node
を受け取っていましたが、*ast.File
を受け取るように変更されました。これにより、ファイル全体のASTにアクセスできるようになります。simplify
関数内で、まずファイルのインポート宣言を走査し、ドットインポートが存在するかどうかをs.hasDotImport
に設定するロジックが追加されました。
-
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
--- 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.
このコードは、以下のステップで簡略化を試みます。
- ドットインポートの確認:
s.hasDotImport
がtrue
の場合、len
が組み込み関数である保証がないため、処理を中断します。 - スライス対象の確認: スライスされている式(
n.X
)がast.Ident
(識別子、例:mySlice
)であり、かつその識別子が解決済み(s.Obj != nil
)であることを確認します。これにより、複雑な式(例:f().Slice[a:len(f().Slice)]
)の簡略化を避けます。 high
部分の確認: スライス式のhigh
部分(n.High
)がast.CallExpr
(関数呼び出し、例:len(mySlice)
)であることを確認します。len
関数の確認: 呼び出されている関数がlen
という名前のast.Ident
であり、かつそのlen
がローカルで定義されていない(fun.Obj == nil
)ことを確認します。これにより、組み込みのlen
関数であることを推測します。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
であることを明示的に示すことが望ましい場合があるためです。
関連リンク
- Go Issue #4314: cmd/gofmt: simplify s[a:len(s)] to s[a:]
- Gerrit Change-ID: https://golang.org/cl/6822059
参考にした情報源リンク
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- The Go Programming Language Specification - Slice expressions: https://go.dev/ref/spec#Slice_expressions
- Go AST package documentation: https://pkg.go.dev/go/ast
- Go
gofmt
documentation: https://pkg.go.dev/cmd/gofmt - Effective Go - Dot imports: https://go.dev/doc/effective_go#dot-imports
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.
このコードは、以下のステップで簡略化を試みます。
- ドットインポートの確認:
s.hasDotImport
がtrue
の場合、len
が組み込み関数である保証がないため、処理を中断します。 - スライス対象の確認: スライスされている式(
n.X
)がast.Ident
(識別子、例:mySlice
)であり、かつその識別子が解決済み(s.Obj != nil
)であることを確認します。これにより、複雑な式(例:f().Slice[a:len(f().Slice)]
)の簡略化を避けます。 high
部分の確認: スライス式のhigh
部分(n.High
)がast.CallExpr
(関数呼び出し、例:len(mySlice)
)であることを確認します。len
関数の確認: 呼び出されている関数がlen
という名前のast.Ident
であり、かつそのlen
がローカルで定義されていない(fun.Obj == nil
)ことを確認します。これにより、組み込みのlen
関数であることを推測します。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
であることを明示的に示すことが望ましい場合があるためです。
関連リンク
- Go Issue #4314: cmd/gofmt: simplify s[a:len(s)] to s[a:]
- Gerrit Change-ID: https://golang.org/cl/6822059
参考にした情報源リンク
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- The Go Programming Language Specification - Slice expressions: https://go.dev/ref/spec#Slice_expressions
- Go AST package documentation: https://pkg.go.dev/go/ast
- Go
gofmt
documentation: https://pkg.go.dev/cmd/gofmt - Effective Go - Dot imports: https://go.dev/doc/effective_go#dot-imports