Skip to content

Commit 1c35f2a

Browse files
committed
internal/lsp/analysis: quick-fix to remove unnecessary type arguments
This CL adds a new infertypeargs analyzer, which finds call exprs where type arguments could be inferred, and suggests a quick fix to simplify them. Along the way, may two changes to the supporting frameworks: - Initialized types.Info.Instances in go/packages - Fail analysis tests run with suggested fixes if formatting the resulting source fails. Change-Id: Ib15e5bd7c26aa293c5fc18a4cff6bc047e9e31d2 Reviewed-on: https://go-review.googlesource.com/c/tools/+/351552 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 7d467dc commit 1c35f2a

18 files changed

+386
-2
lines changed

go/analysis/analysistest/analysistest.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,13 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
196196
want := string(bytes.TrimRight(vf.Data, "\n")) + "\n"
197197
formatted, err := format.Source([]byte(out))
198198
if err != nil {
199+
t.Errorf("%s: error formatting edited source: %v\n%s", file.Name(), err, out)
199200
continue
200201
}
201202
if want != string(formatted) {
202203
d, err := myers.ComputeEdits("", want, string(formatted))
203204
if err != nil {
204-
t.Errorf("failed to compute suggested fixes: %v", err)
205+
t.Errorf("failed to compute suggested fix diff: %v", err)
205206
}
206207
t.Errorf("suggested fixes failed for %s:\n%s", file.Name(), diff.ToUnified(fmt.Sprintf("%s.golden [%s]", file.Name(), sf), "actual", want, d))
207208
}
@@ -225,12 +226,13 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
225226

226227
formatted, err := format.Source([]byte(out))
227228
if err != nil {
229+
t.Errorf("%s: error formatting resulting source: %v\n%s", file.Name(), err, out)
228230
continue
229231
}
230232
if want != string(formatted) {
231233
d, err := myers.ComputeEdits("", want, string(formatted))
232234
if err != nil {
233-
t.Errorf("failed to compute edits: %s", err)
235+
t.Errorf("%s: failed to compute suggested fix diff: %s", file.Name(), err)
234236
}
235237
t.Errorf("suggested fixes failed for %s:\n%s", file.Name(), diff.ToUnified(file.Name()+".golden", "actual", want, d))
236238
}

go/packages/packages.go

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"golang.org/x/tools/go/gcexportdata"
2727
"golang.org/x/tools/internal/gocommand"
2828
"golang.org/x/tools/internal/packagesinternal"
29+
"golang.org/x/tools/internal/typeparams"
2930
"golang.org/x/tools/internal/typesinternal"
3031
)
3132

@@ -910,6 +911,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
910911
Scopes: make(map[ast.Node]*types.Scope),
911912
Selections: make(map[*ast.SelectorExpr]*types.Selection),
912913
}
914+
typeparams.InitInstanceInfo(lpkg.TypesInfo)
913915
lpkg.TypesSizes = ld.sizes
914916

915917
importer := importerFunc(func(path string) (*types.Package, error) {

gopls/doc/analyzers.md

+16
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,22 @@ The Read method in v has a different signature than the Read method in
182182
io.Reader, so this assertion cannot succeed.
183183

184184

185+
**Enabled by default.**
186+
187+
## **infertypeargs**
188+
189+
check for unnecessary type arguments in call expressions
190+
191+
Explicit type arguments may be omitted from call expressions if they can be
192+
inferred from function arguments, or from other type arguments:
193+
194+
func f[T any](T) {}
195+
196+
func _() {
197+
f[string]("foo") // string could be inferred
198+
}
199+
200+
185201
**Enabled by default.**
186202

187203
## **loopclosure**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package infertypeargs defines an analyzer that checks for explicit function
6+
// arguments that could be inferred.
7+
package infertypeargs
8+
9+
import (
10+
"golang.org/x/tools/go/analysis"
11+
"golang.org/x/tools/go/analysis/passes/inspect"
12+
)
13+
14+
const Doc = `check for unnecessary type arguments in call expressions
15+
16+
Explicit type arguments may be omitted from call expressions if they can be
17+
inferred from function arguments, or from other type arguments:
18+
19+
func f[T any](T) {}
20+
21+
func _() {
22+
f[string]("foo") // string could be inferred
23+
}
24+
`
25+
26+
var Analyzer = &analysis.Analyzer{
27+
Name: "infertypeargs",
28+
Doc: Doc,
29+
Requires: []*analysis.Analyzer{inspect.Analyzer},
30+
Run: run,
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package infertypeargs_test
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis/analysistest"
11+
"golang.org/x/tools/internal/lsp/analysis/infertypeargs"
12+
"golang.org/x/tools/internal/testenv"
13+
"golang.org/x/tools/internal/typeparams"
14+
)
15+
16+
func Test(t *testing.T) {
17+
testenv.NeedsGo1Point(t, 13)
18+
if !typeparams.Enabled {
19+
t.Skip("type params are not enabled")
20+
}
21+
testdata := analysistest.TestData()
22+
analysistest.RunWithSuggestedFixes(t, testdata, infertypeargs.Analyzer, "a")
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !go1.18
6+
// +build !go1.18
7+
8+
package infertypeargs
9+
10+
import "golang.org/x/tools/go/analysis"
11+
12+
// This analyzer only relates to go1.18+, and uses the types.CheckExpr API that
13+
// was added in Go 1.13.
14+
func run(pass *analysis.Pass) (interface{}, error) {
15+
return nil, nil
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.18
6+
// +build go1.18
7+
8+
package infertypeargs
9+
10+
import (
11+
"go/ast"
12+
"go/token"
13+
"go/types"
14+
15+
"golang.org/x/tools/go/analysis"
16+
"golang.org/x/tools/go/analysis/passes/inspect"
17+
"golang.org/x/tools/go/ast/inspector"
18+
"golang.org/x/tools/internal/typeparams"
19+
)
20+
21+
func run(pass *analysis.Pass) (interface{}, error) {
22+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
23+
24+
nodeFilter := []ast.Node{
25+
(*ast.CallExpr)(nil),
26+
}
27+
28+
inspect.Preorder(nodeFilter, func(node ast.Node) {
29+
call := node.(*ast.CallExpr)
30+
ident, ix := instanceData(call)
31+
if ix == nil || len(ix.Indices) == 0 {
32+
return // no explicit args, nothing to do
33+
}
34+
35+
// Confirm that instantiation actually occurred at this ident.
36+
_, instance := typeparams.GetInstance(pass.TypesInfo, ident)
37+
if instance == nil {
38+
return // something went wrong, but fail open
39+
}
40+
41+
// Start removing argument expressions from the right, and check if we can
42+
// still infer the call expression.
43+
required := len(ix.Indices) // number of type expressions that are required
44+
for i := len(ix.Indices) - 1; i >= 0; i-- {
45+
var fun ast.Expr
46+
if i == 0 {
47+
// No longer an index expression: just use the parameterized operand.
48+
fun = ix.X
49+
} else {
50+
fun = typeparams.PackIndexExpr(ix.X, ix.Lbrack, ix.Indices[:i], ix.Indices[i-1].End())
51+
}
52+
newCall := &ast.CallExpr{
53+
Fun: fun,
54+
Lparen: call.Lparen,
55+
Args: call.Args,
56+
Ellipsis: call.Ellipsis,
57+
Rparen: call.Rparen,
58+
}
59+
info := new(types.Info)
60+
typeparams.InitInstanceInfo(info)
61+
if err := types.CheckExpr(pass.Fset, pass.Pkg, call.Pos(), newCall, info); err != nil {
62+
// Most likely inference failed.
63+
break
64+
}
65+
_, newInstance := typeparams.GetInstance(info, ident)
66+
if !types.Identical(instance, newInstance) {
67+
// The inferred result type does not match the original result type, so
68+
// this simplification is not valid.
69+
break
70+
}
71+
required = i
72+
}
73+
if required < len(ix.Indices) {
74+
var start, end token.Pos
75+
if required == 0 {
76+
start, end = ix.Lbrack, ix.Rbrack+1 // erase the entire index
77+
} else {
78+
start = ix.Indices[required-1].End()
79+
end = ix.Rbrack
80+
}
81+
pass.Report(analysis.Diagnostic{
82+
Pos: start,
83+
End: end,
84+
Message: "unnecessary type arguments",
85+
SuggestedFixes: []analysis.SuggestedFix{{
86+
Message: "simplify type arguments",
87+
TextEdits: []analysis.TextEdit{{
88+
Pos: start,
89+
End: end,
90+
}},
91+
}},
92+
})
93+
}
94+
})
95+
96+
return nil, nil
97+
}
98+
99+
// instanceData returns the instantiated identifier and index data.
100+
func instanceData(call *ast.CallExpr) (*ast.Ident, *typeparams.IndexExprData) {
101+
ix := typeparams.GetIndexExprData(call.Fun)
102+
if ix == nil {
103+
return nil, nil
104+
}
105+
var id *ast.Ident
106+
switch x := ix.X.(type) {
107+
case *ast.SelectorExpr:
108+
id = x.Sel
109+
case *ast.Ident:
110+
id = x
111+
default:
112+
return nil, nil
113+
}
114+
return id, ix
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This file contains tests for the infertyepargs checker.
6+
7+
package a
8+
9+
func f[T any](T) {}
10+
11+
func g[T any]() T { var x T; return x }
12+
13+
func h[P interface{ ~*T }, T any]() {}
14+
15+
func _() {
16+
f[string]("hello") // want "unnecessary type arguments"
17+
f[int](2) // want "unnecessary type arguments"
18+
_ = g[int]()
19+
h[*int, int]() // want "unnecessary type arguments"
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This file contains tests for the infertyepargs checker.
6+
7+
package a
8+
9+
func f[T any](T) {}
10+
11+
func g[T any]() T { var x T; return x }
12+
13+
func h[P interface{ ~*T }, T any]() {}
14+
15+
func _() {
16+
f("hello") // want "unnecessary type arguments"
17+
f(2) // want "unnecessary type arguments"
18+
_ = g[int]()
19+
h[*int]() // want "unnecessary type arguments"
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package a
6+
7+
import "a/imported"
8+
9+
func _() {
10+
var x int
11+
imported.F[int](x) // want "unnecessary type arguments"
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package a
6+
7+
import "a/imported"
8+
9+
func _() {
10+
var x int
11+
imported.F(x) // want "unnecessary type arguments"
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package imported
6+
7+
func F[T any](T) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// We should not suggest removing type arguments if doing so would change the
6+
// resulting type.
7+
8+
package a
9+
10+
func id[T any](t T) T { return t }
11+
12+
var _ = id[int](1) // want "unnecessary type arguments"
13+
var _ = id[string]("foo") // want "unnecessary type arguments"
14+
var _ = id[int64](2)
15+
16+
func pair[T any](t T) (T, T) { return t, t }
17+
18+
var _, _ = pair[int](3) // want "unnecessary type arguments"
19+
var _, _ = pair[int64](3)
20+
21+
func noreturn[T any](t T) {}
22+
23+
func _() {
24+
noreturn[int64](4)
25+
noreturn[int](4) // want "unnecessary type arguments"
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// We should not suggest removing type arguments if doing so would change the
6+
// resulting type.
7+
8+
package a
9+
10+
func id[T any](t T) T { return t }
11+
12+
var _ = id(1) // want "unnecessary type arguments"
13+
var _ = id("foo") // want "unnecessary type arguments"
14+
var _ = id[int64](2)
15+
16+
func pair[T any](t T) (T, T) { return t, t }
17+
18+
var _, _ = pair(3) // want "unnecessary type arguments"
19+
var _, _ = pair[int64](3)
20+
21+
func noreturn[T any](t T) {}
22+
23+
func _() {
24+
noreturn[int64](4)
25+
noreturn(4) // want "unnecessary type arguments"
26+
}

0 commit comments

Comments
 (0)