Skip to content

Commit dc9353b

Browse files
committed
gopls/internal/analysis/modernize: appendclipped: unclip
The appendclipped pass must ascertain that the first argument to append(x, y...) is clipped, so that we don't eliminate possible intended side effects on x, but in some cases: - append(x[:len(x):len(x)], y...) - append(slices.Clip(x), y...) we can further simplify the first argument to its unclipped version (just x in both cases), so that the result is: slices.Concat(x, y) + test Fixes golang/go#71296 Change-Id: I89cc4350b5dbd57c88c35c0b4459b23347814441 Reviewed-on: https://go-review.googlesource.com/c/tools/+/647796 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent a886a1c commit dc9353b

File tree

3 files changed

+34
-24
lines changed

3 files changed

+34
-24
lines changed

gopls/internal/analysis/modernize/modernize.go

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ var (
130130
builtinAppend = types.Universe.Lookup("append")
131131
builtinBool = types.Universe.Lookup("bool")
132132
builtinFalse = types.Universe.Lookup("false")
133+
builtinLen = types.Universe.Lookup("len")
133134
builtinMake = types.Universe.Lookup("make")
134135
builtinNil = types.Universe.Lookup("nil")
135136
builtinTrue = types.Universe.Lookup("true")

gopls/internal/analysis/modernize/slices.go

+31-22
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,21 @@ func appendclipped(pass *analysis.Pass) {
5252
// Only appends whose base is a clipped slice can be simplified:
5353
// We must conservatively assume an append to an unclipped slice
5454
// such as append(y[:0], x...) is intended to have effects on y.
55-
clipped, empty := isClippedSlice(info, base)
56-
if !clipped {
55+
clipped, empty := clippedSlice(info, base)
56+
if clipped == nil {
5757
return
5858
}
5959

6060
// If the (clipped) base is empty, it may be safely ignored.
61-
// Otherwise treat it as just another arg (the first) to Concat.
61+
// Otherwise treat it (or its unclipped subexpression, if possible)
62+
// as just another arg (the first) to Concat.
6263
if !empty {
63-
sliceArgs = append(sliceArgs, base)
64+
sliceArgs = append(sliceArgs, clipped)
6465
}
6566
slices.Reverse(sliceArgs)
6667

68+
// TODO(adonovan): simplify sliceArgs[0] further: slices.Clone(s) -> s
69+
6770
// Concat of a single (non-trivial) slice degenerates to Clone.
6871
if len(sliceArgs) == 1 {
6972
s := sliceArgs[0]
@@ -111,11 +114,6 @@ func appendclipped(pass *analysis.Pass) {
111114
}
112115

113116
// append(append(append(base, a...), b..., c...) -> slices.Concat(base, a, b, c)
114-
//
115-
// TODO(adonovan): simplify sliceArgs[0] further:
116-
// - slices.Clone(s) -> s
117-
// - s[:len(s):len(s)] -> s
118-
// - slices.Clip(s) -> s
119117
_, prefix, importEdits := analysisinternal.AddImport(info, file, "slices", "slices", "Concat", call.Pos())
120118
pass.Report(analysis.Diagnostic{
121119
Pos: call.Pos(),
@@ -172,46 +170,57 @@ func appendclipped(pass *analysis.Pass) {
172170
}
173171
}
174172

175-
// isClippedSlice reports whether e denotes a slice that is definitely
176-
// clipped, that is, its len(s)==cap(s).
173+
// clippedSlice returns res != nil if e denotes a slice that is
174+
// definitely clipped, that is, its len(s)==cap(s).
175+
//
176+
// The value of res is either the same as e or is a subexpression of e
177+
// that denotes the same slice but without the clipping operation.
177178
//
178-
// In addition, it reports whether the slice is definitely empty.
179+
// In addition, it reports whether the slice is definitely empty,
179180
//
180181
// Examples of clipped slices:
181182
//
182183
// x[:0:0] (empty)
183184
// []T(nil) (empty)
184185
// Slice{} (empty)
185-
// x[:len(x):len(x)] (nonempty)
186+
// x[:len(x):len(x)] (nonempty) res=x
186187
// x[:k:k] (nonempty)
187-
// slices.Clip(x) (nonempty)
188-
func isClippedSlice(info *types.Info, e ast.Expr) (clipped, empty bool) {
188+
// slices.Clip(x) (nonempty) res=x
189+
func clippedSlice(info *types.Info, e ast.Expr) (res ast.Expr, empty bool) {
189190
switch e := e.(type) {
190191
case *ast.SliceExpr:
191-
// x[:0:0], x[:len(x):len(x)], x[:k:k], x[:0]
192-
clipped = e.Slice3 && e.High != nil && e.Max != nil && equalSyntax(e.High, e.Max) // x[:k:k]
193-
empty = e.High != nil && isZeroLiteral(e.High) // x[:0:*]
192+
// x[:0:0], x[:len(x):len(x)], x[:k:k]
193+
if e.Slice3 && e.High != nil && e.Max != nil && equalSyntax(e.High, e.Max) { // x[:k:k]
194+
res = e
195+
empty = isZeroLiteral(e.High) // x[:0:0]
196+
if call, ok := e.High.(*ast.CallExpr); ok &&
197+
typeutil.Callee(info, call) == builtinLen &&
198+
equalSyntax(call.Args[0], e.X) {
199+
res = e.X // x[:len(x):len(x)] -> x
200+
}
201+
return
202+
}
194203
return
195204

196205
case *ast.CallExpr:
197206
// []T(nil)?
198207
if info.Types[e.Fun].IsType() &&
199208
is[*ast.Ident](e.Args[0]) &&
200209
info.Uses[e.Args[0].(*ast.Ident)] == builtinNil {
201-
return true, true
210+
return e, true
202211
}
203212

204213
// slices.Clip(x)?
205214
obj := typeutil.Callee(info, e)
206215
if analysisinternal.IsFunctionNamed(obj, "slices", "Clip") {
207-
return true, false
216+
return e.Args[0], false // slices.Clip(x) -> x
208217
}
209218

210219
case *ast.CompositeLit:
211220
// Slice{}?
212221
if len(e.Elts) == 0 {
213-
return true, true
222+
return e, true
214223
}
215224
}
216-
return false, false
225+
return nil, false
217226
}

gopls/internal/analysis/modernize/testdata/src/appendclipped/appendclipped.go.golden

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func _(s, other []string) {
2020
print(slices.Concat(Bytes{1, 2, 3}, Bytes{4, 5, 6})) // want "Replace append with slices.Concat"
2121
print(slices.Concat(s, other, other)) // want "Replace append with slices.Concat"
2222
print(slices.Concat(os.Environ(), other, other)) // want "Replace append with slices.Concat"
23-
print(slices.Concat(other[:len(other):len(other)], s, other)) // want "Replace append with slices.Concat"
24-
print(slices.Concat(slices.Clip(other), s, other)) // want "Replace append with slices.Concat"
23+
print(slices.Concat(other, s, other)) // want "Replace append with slices.Concat"
24+
print(slices.Concat(other, s, other)) // want "Replace append with slices.Concat"
2525
print(append(append(append(other[:0], s...), other...), other...)) // nope: intent may be to mutate other
2626
}

0 commit comments

Comments
 (0)