internal/lsp/analysis: add a useany analyzer
Add an analyzer that checks for empty interfaces in constraint position,
that could instead use the new predeclared "any" type.
Change-Id: I6c11f74c479c2cba64b3b12e61d70d157f94393b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/351549
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index c3e19ac..8215f1b 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -554,6 +554,12 @@
**Disabled by default. Enable it by setting `"analyses": {"unusedwrite": true}`.**
+## **useany**
+
+check for constraints that could be simplified to "any"
+
+**Enabled by default.**
+
## **fillreturns**
suggested fixes for "wrong number of return values (want %d, got %d)"
diff --git a/internal/lsp/analysis/useany/testdata/src/a/a.go b/internal/lsp/analysis/useany/testdata/src/a/a.go
new file mode 100644
index 0000000..22d6931
--- /dev/null
+++ b/internal/lsp/analysis/useany/testdata/src/a/a.go
@@ -0,0 +1,25 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains tests for the useany checker.
+
+package a
+
+type Any interface{}
+
+func _[T interface{}]() {} // want "could use \"any\" for this empty interface"
+func _[X any, T interface{}]() {} // want "could use \"any\" for this empty interface"
+func _[any interface{}]() {} // want "could use \"any\" for this empty interface"
+func _[T Any]() {} // want "could use \"any\" for this empty interface"
+func _[T interface{ int | interface{} }]() {} // want "could use \"any\" for this empty interface"
+func _[T interface{ int | Any }]() {} // want "could use \"any\" for this empty interface"
+func _[T any]() {}
+
+type _[T interface{}] int // want "could use \"any\" for this empty interface"
+type _[X any, T interface{}] int // want "could use \"any\" for this empty interface"
+type _[any interface{}] int // want "could use \"any\" for this empty interface"
+type _[T Any] int // want "could use \"any\" for this empty interface"
+type _[T interface{ int | interface{} }] int // want "could use \"any\" for this empty interface"
+type _[T interface{ int | Any }] int // want "could use \"any\" for this empty interface"
+type _[T any] int
diff --git a/internal/lsp/analysis/useany/testdata/src/a/a.go.golden b/internal/lsp/analysis/useany/testdata/src/a/a.go.golden
new file mode 100644
index 0000000..efd8fd6
--- /dev/null
+++ b/internal/lsp/analysis/useany/testdata/src/a/a.go.golden
@@ -0,0 +1,25 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains tests for the useany checker.
+
+package a
+
+type Any interface{}
+
+func _[T any]() {} // want "could use \"any\" for this empty interface"
+func _[X any, T any]() {} // want "could use \"any\" for this empty interface"
+func _[any interface{}]() {} // want "could use \"any\" for this empty interface"
+func _[T any]() {} // want "could use \"any\" for this empty interface"
+func _[T any]() {} // want "could use \"any\" for this empty interface"
+func _[T any]() {} // want "could use \"any\" for this empty interface"
+func _[T any]() {}
+
+type _[T any] int // want "could use \"any\" for this empty interface"
+type _[X any, T any] int // want "could use \"any\" for this empty interface"
+type _[any interface{}] int // want "could use \"any\" for this empty interface"
+type _[T any] int // want "could use \"any\" for this empty interface"
+type _[T any] int // want "could use \"any\" for this empty interface"
+type _[T any] int // want "could use \"any\" for this empty interface"
+type _[T any] int
diff --git a/internal/lsp/analysis/useany/useany.go b/internal/lsp/analysis/useany/useany.go
new file mode 100644
index 0000000..73e2f76
--- /dev/null
+++ b/internal/lsp/analysis/useany/useany.go
@@ -0,0 +1,102 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package useany defines an Analyzer that checks for usage of interface{} in
+// constraints, rather than the predeclared any.
+package useany
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/typeparams"
+)
+
+const Doc = `check for constraints that could be simplified to "any"`
+
+var Analyzer = &analysis.Analyzer{
+ Name: "useany",
+ Doc: Doc,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+ universeAny := types.Universe.Lookup("any")
+ if universeAny == nil {
+ // Go <= 1.17. Nothing to check.
+ return nil, nil
+ }
+
+ nodeFilter := []ast.Node{
+ (*ast.TypeSpec)(nil),
+ (*ast.FuncType)(nil),
+ }
+
+ inspect.Preorder(nodeFilter, func(node ast.Node) {
+ var tparams *ast.FieldList
+ switch node := node.(type) {
+ case *ast.TypeSpec:
+ tparams = typeparams.ForTypeSpec(node)
+ case *ast.FuncType:
+ tparams = typeparams.ForFuncType(node)
+ default:
+ panic(fmt.Sprintf("unexpected node type %T", node))
+ }
+ if tparams.NumFields() == 0 {
+ return
+ }
+
+ for _, field := range tparams.List {
+ typ := pass.TypesInfo.Types[field.Type].Type
+ if typ == nil {
+ continue // something is wrong, but not our concern
+ }
+ iface, ok := typ.Underlying().(*types.Interface)
+ if !ok {
+ continue // invalid constraint
+ }
+
+ // If the constraint is the empty interface, offer a fix to use 'any'
+ // instead.
+ if iface.Empty() {
+ id, _ := field.Type.(*ast.Ident)
+ if id != nil && pass.TypesInfo.Uses[id] == universeAny {
+ continue
+ }
+
+ diag := analysis.Diagnostic{
+ Pos: field.Type.Pos(),
+ End: field.Type.End(),
+ Message: `could use "any" for this empty interface`,
+ }
+
+ // Only suggest a fix to 'any' if we actually resolve the predeclared
+ // any in this scope.
+ if scope := pass.TypesInfo.Scopes[node]; scope != nil {
+ if _, any := scope.LookupParent("any", token.NoPos); any == universeAny {
+ diag.SuggestedFixes = []analysis.SuggestedFix{{
+ Message: `use "any"`,
+ TextEdits: []analysis.TextEdit{{
+ Pos: field.Type.Pos(),
+ End: field.Type.End(),
+ NewText: []byte("any"),
+ }},
+ }}
+ }
+ }
+
+ pass.Report(diag)
+ }
+ }
+ })
+ return nil, nil
+}
diff --git a/internal/lsp/analysis/useany/useany_test.go b/internal/lsp/analysis/useany/useany_test.go
new file mode 100644
index 0000000..535d915
--- /dev/null
+++ b/internal/lsp/analysis/useany/useany_test.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package useany_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/go/analysis/analysistest"
+ "golang.org/x/tools/internal/lsp/analysis/useany"
+ "golang.org/x/tools/internal/typeparams"
+)
+
+func Test(t *testing.T) {
+ if !typeparams.Enabled {
+ t.Skip("type params are not enabled")
+ }
+ testdata := analysistest.TestData()
+ analysistest.RunWithSuggestedFixes(t, testdata, useany.Analyzer, "a")
+}
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index 4b67341..b78b57d 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -552,6 +552,11 @@
Default: "false",
},
{
+ Name: "\"useany\"",
+ Doc: "check for constraints that could be simplified to \"any\"",
+ Default: "true",
+ },
+ {
Name: "\"fillreturns\"",
Doc: "suggested fixes for \"wrong number of return values (want %d, got %d)\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\nwill turn into\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.\n",
Default: "true",
@@ -1125,6 +1130,11 @@
Default: false,
},
{
+ Name: "useany",
+ Doc: "check for constraints that could be simplified to \"any\"",
+ Default: true,
+ },
+ {
Name: "fillreturns",
Doc: "suggested fixes for \"wrong number of return values (want %d, got %d)\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\nwill turn into\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.\n",
Default: true,
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 19c9911..1efe177 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -57,6 +57,7 @@
"golang.org/x/tools/internal/lsp/analysis/simplifyslice"
"golang.org/x/tools/internal/lsp/analysis/undeclaredname"
"golang.org/x/tools/internal/lsp/analysis/unusedparams"
+ "golang.org/x/tools/internal/lsp/analysis/useany"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/diff/myers"
@@ -1245,6 +1246,7 @@
testinggoroutine.Analyzer.Name: {Analyzer: testinggoroutine.Analyzer, Enabled: true},
unusedparams.Analyzer.Name: {Analyzer: unusedparams.Analyzer, Enabled: false},
unusedwrite.Analyzer.Name: {Analyzer: unusedwrite.Analyzer, Enabled: false},
+ useany.Analyzer.Name: {Analyzer: useany.Analyzer, Enabled: true},
// gofmt -s suite:
simplifycompositelit.Analyzer.Name: {
diff --git a/internal/typeparams/typeparams_go117.go b/internal/typeparams/typeparams_go117.go
index 6ff07ea..479b556 100644
--- a/internal/typeparams/typeparams_go117.go
+++ b/internal/typeparams/typeparams_go117.go
@@ -30,15 +30,15 @@
return nil
}
-// ForTypeDecl returns an empty field list, as type parameters on not supported
+// ForTypeSpec returns an empty field list, as type parameters on not supported
// at this Go version.
-func ForTypeDecl(*ast.TypeSpec) *ast.FieldList {
+func ForTypeSpec(*ast.TypeSpec) *ast.FieldList {
return nil
}
-// ForFuncDecl returns an empty field list, as type parameters are not
+// ForFuncType returns an empty field list, as type parameters are not
// supported at this Go version.
-func ForFuncDecl(*ast.FuncDecl) *ast.FieldList {
+func ForFuncType(*ast.FuncType) *ast.FieldList {
return nil
}
diff --git a/internal/typeparams/typeparams_go118.go b/internal/typeparams/typeparams_go118.go
index 953e5c6..16c6c0d 100644
--- a/internal/typeparams/typeparams_go118.go
+++ b/internal/typeparams/typeparams_go118.go
@@ -36,17 +36,20 @@
return nil
}
-// ForTypeDecl returns n.TypeParams.
-func ForTypeDecl(n *ast.TypeSpec) *ast.FieldList {
+// ForTypeSpec returns n.TypeParams.
+func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList {
+ if n == nil {
+ return nil
+ }
return n.TypeParams
}
-// ForFuncDecl returns n.Type.TypeParams.
-func ForFuncDecl(n *ast.FuncDecl) *ast.FieldList {
- if n.Type != nil {
- return n.Type.TypeParams
+// ForFuncType returns n.TypeParams.
+func ForFuncType(n *ast.FuncType) *ast.FieldList {
+ if n == nil {
+ return nil
}
- return nil
+ return n.TypeParams
}
// TypeParam is an alias for types.TypeParam