Skip to content

Commit 0721940

Browse files
xieyuschengopherbot
authored andcommittedMar 5, 2025
gopls/internal/analysis/modernize: strings.Fields -> FieldsSeq
This CL enhances the existing modernizer to support calls to strings.Fields and bytes.Fields, that offers a fix to instead use go1.24's FieldsSeq, which avoids allocating an array. Fixes golang/go#72033 Change-Id: I2059f66f38a639c5a264b650137ced7b4f84550e Reviewed-on: https://go-review.googlesource.com/c/tools/+/654535 Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Junyang Shao <shaojunyang@google.com>
1 parent 8d38122 commit 0721940

File tree

9 files changed

+111
-16
lines changed

9 files changed

+111
-16
lines changed
 

‎gopls/doc/analyzers.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ existing code by using more modern features of Go, such as:
498498
- replacing a 3-clause for i := 0; i < n; i++ {} loop by
499499
for i := range n {}, added in go1.22;
500500
- replacing Split in "for range strings.Split(...)" by go1.24's
501-
more efficient SplitSeq;
501+
more efficient SplitSeq, or Fields with FieldSeq;
502502

503503
To apply all modernization fixes en masse, you can use the
504504
following command:

‎gopls/internal/analysis/modernize/doc.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
// - replacing a 3-clause for i := 0; i < n; i++ {} loop by
3232
// for i := range n {}, added in go1.22;
3333
// - replacing Split in "for range strings.Split(...)" by go1.24's
34-
// more efficient SplitSeq;
34+
// more efficient SplitSeq, or Fields with FieldSeq;
3535
//
3636
// To apply all modernization fixes en masse, you can use the
3737
// following command:

‎gopls/internal/analysis/modernize/modernize.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func run(pass *analysis.Pass) (any, error) {
7272
rangeint(pass)
7373
slicescontains(pass)
7474
slicesdelete(pass)
75-
splitseq(pass)
75+
stringsseq(pass)
7676
sortslice(pass)
7777
testingContext(pass)
7878

‎gopls/internal/analysis/modernize/modernize_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func Test(t *testing.T) {
2424
"slicescontains",
2525
"slicesdelete",
2626
"splitseq",
27+
"fieldsseq",
2728
"sortslice",
2829
"testingcontext",
2930
)

‎gopls/internal/analysis/modernize/splitseq.go ‎gopls/internal/analysis/modernize/stringsseq.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package modernize
66

77
import (
8+
"fmt"
89
"go/ast"
910
"go/token"
1011
"go/types"
@@ -17,8 +18,9 @@ import (
1718
"golang.org/x/tools/internal/astutil/edge"
1819
)
1920

20-
// splitseq offers a fix to replace a call to strings.Split with
21-
// SplitSeq when it is the operand of a range loop, either directly:
21+
// stringsseq offers a fix to replace a call to strings.Split with
22+
// SplitSeq or strings.Fields with FieldsSeq
23+
// when it is the operand of a range loop, either directly:
2224
//
2325
// for _, line := range strings.Split() {...}
2426
//
@@ -29,7 +31,8 @@ import (
2931
//
3032
// Variants:
3133
// - bytes.SplitSeq
32-
func splitseq(pass *analysis.Pass) {
34+
// - bytes.FieldsSeq
35+
func stringsseq(pass *analysis.Pass) {
3336
if !analysisinternal.Imports(pass.Pkg, "strings") &&
3437
!analysisinternal.Imports(pass.Pkg, "bytes") {
3538
return
@@ -88,21 +91,27 @@ func splitseq(pass *analysis.Pass) {
8891
})
8992
}
9093

91-
if sel, ok := call.Fun.(*ast.SelectorExpr); ok &&
92-
(analysisinternal.IsFunctionNamed(typeutil.Callee(info, call), "strings", "Split") ||
93-
analysisinternal.IsFunctionNamed(typeutil.Callee(info, call), "bytes", "Split")) {
94+
sel, ok := call.Fun.(*ast.SelectorExpr)
95+
if !ok {
96+
continue
97+
}
98+
99+
obj := typeutil.Callee(info, call)
100+
if analysisinternal.IsFunctionNamed(obj, "strings", "Split", "Fields") ||
101+
analysisinternal.IsFunctionNamed(obj, "bytes", "Split", "Fields") {
102+
oldFnName := obj.Name()
103+
seqFnName := fmt.Sprintf("%sSeq", oldFnName)
94104
pass.Report(analysis.Diagnostic{
95105
Pos: sel.Pos(),
96106
End: sel.End(),
97-
Category: "splitseq",
98-
Message: "Ranging over SplitSeq is more efficient",
107+
Category: "stringsseq",
108+
Message: fmt.Sprintf("Ranging over %s is more efficient", seqFnName),
99109
SuggestedFixes: []analysis.SuggestedFix{{
100-
Message: "Replace Split with SplitSeq",
110+
Message: fmt.Sprintf("Replace %s with %s", oldFnName, seqFnName),
101111
TextEdits: append(edits, analysis.TextEdit{
102-
// Split -> SplitSeq
103112
Pos: sel.Sel.Pos(),
104113
End: sel.Sel.End(),
105-
NewText: []byte("SplitSeq")}),
114+
NewText: []byte(seqFnName)}),
106115
}},
107116
})
108117
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//go:build go1.24
2+
3+
package fieldsseq
4+
5+
import (
6+
"bytes"
7+
"strings"
8+
)
9+
10+
func _() {
11+
for _, line := range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
12+
println(line)
13+
}
14+
for i, line := range strings.Fields("") { // nope: uses index var
15+
println(i, line)
16+
}
17+
for i, _ := range strings.Fields("") { // nope: uses index var
18+
println(i)
19+
}
20+
for i := range strings.Fields("") { // nope: uses index var
21+
println(i)
22+
}
23+
for _ = range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
24+
}
25+
for range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
26+
}
27+
for range bytes.Fields(nil) { // want "Ranging over FieldsSeq is more efficient"
28+
}
29+
{
30+
lines := strings.Fields("") // want "Ranging over FieldsSeq is more efficient"
31+
for _, line := range lines {
32+
println(line)
33+
}
34+
}
35+
{
36+
lines := strings.Fields("") // nope: lines is used not just by range
37+
for _, line := range lines {
38+
println(line)
39+
}
40+
println(lines)
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//go:build go1.24
2+
3+
package fieldsseq
4+
5+
import (
6+
"bytes"
7+
"strings"
8+
)
9+
10+
func _() {
11+
for line := range strings.FieldsSeq("") { // want "Ranging over FieldsSeq is more efficient"
12+
println(line)
13+
}
14+
for i, line := range strings.Fields( "") { // nope: uses index var
15+
println(i, line)
16+
}
17+
for i, _ := range strings.Fields( "") { // nope: uses index var
18+
println(i)
19+
}
20+
for i := range strings.Fields( "") { // nope: uses index var
21+
println(i)
22+
}
23+
for range strings.FieldsSeq("") { // want "Ranging over FieldsSeq is more efficient"
24+
}
25+
for range strings.FieldsSeq("") { // want "Ranging over FieldsSeq is more efficient"
26+
}
27+
for range bytes.FieldsSeq(nil) { // want "Ranging over FieldsSeq is more efficient"
28+
}
29+
{
30+
lines := strings.FieldsSeq("") // want "Ranging over FieldsSeq is more efficient"
31+
for line := range lines {
32+
println(line)
33+
}
34+
}
35+
{
36+
lines := strings.Fields( "") // nope: lines is used not just by range
37+
for _, line := range lines {
38+
println(line)
39+
}
40+
println(lines)
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package fieldsseq

‎gopls/internal/doc/api.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@
514514
},
515515
{
516516
"Name": "\"modernize\"",
517-
"Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go, such as:\n\n - replacing an if/else conditional assignment by a call to the\n built-in min or max functions added in go1.21;\n - replacing sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21;\n - replacing interface{} by the 'any' type added in go1.18;\n - replacing append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21;\n - replacing a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions\n from the maps package, added in go1.21;\n - replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19;\n - replacing uses of context.WithCancel in tests with t.Context, added in\n go1.24;\n - replacing omitempty by omitzero on structs, added in go1.24;\n - replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1),\n added in go1.21\n - replacing a 3-clause for i := 0; i \u003c n; i++ {} loop by\n for i := range n {}, added in go1.22;\n - replacing Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq;\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.",
517+
"Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go, such as:\n\n - replacing an if/else conditional assignment by a call to the\n built-in min or max functions added in go1.21;\n - replacing sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21;\n - replacing interface{} by the 'any' type added in go1.18;\n - replacing append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21;\n - replacing a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions\n from the maps package, added in go1.21;\n - replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19;\n - replacing uses of context.WithCancel in tests with t.Context, added in\n go1.24;\n - replacing omitempty by omitzero on structs, added in go1.24;\n - replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1),\n added in go1.21\n - replacing a 3-clause for i := 0; i \u003c n; i++ {} loop by\n for i := range n {}, added in go1.22;\n - replacing Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq, or Fields with FieldSeq;\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.",
518518
"Default": "true"
519519
},
520520
{
@@ -1228,7 +1228,7 @@
12281228
},
12291229
{
12301230
"Name": "modernize",
1231-
"Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go, such as:\n\n - replacing an if/else conditional assignment by a call to the\n built-in min or max functions added in go1.21;\n - replacing sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21;\n - replacing interface{} by the 'any' type added in go1.18;\n - replacing append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21;\n - replacing a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions\n from the maps package, added in go1.21;\n - replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19;\n - replacing uses of context.WithCancel in tests with t.Context, added in\n go1.24;\n - replacing omitempty by omitzero on structs, added in go1.24;\n - replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1),\n added in go1.21\n - replacing a 3-clause for i := 0; i \u003c n; i++ {} loop by\n for i := range n {}, added in go1.22;\n - replacing Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq;\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.",
1231+
"Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go, such as:\n\n - replacing an if/else conditional assignment by a call to the\n built-in min or max functions added in go1.21;\n - replacing sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21;\n - replacing interface{} by the 'any' type added in go1.18;\n - replacing append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21;\n - replacing a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions\n from the maps package, added in go1.21;\n - replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19;\n - replacing uses of context.WithCancel in tests with t.Context, added in\n go1.24;\n - replacing omitempty by omitzero on structs, added in go1.24;\n - replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1),\n added in go1.21\n - replacing a 3-clause for i := 0; i \u003c n; i++ {} loop by\n for i := range n {}, added in go1.22;\n - replacing Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq, or Fields with FieldSeq;\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.",
12321232
"URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize",
12331233
"Default": true
12341234
},

0 commit comments

Comments
 (0)
Please sign in to comment.