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>
This commit is contained in:
Robert Findley 2021-09-22 11:06:05 -04:00
parent efaec4e631
commit 284795867f
9 changed files with 205 additions and 11 deletions

View File

@ -554,6 +554,12 @@ Another example is about non-pointer receiver:
**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)"

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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")
}

View File

@ -551,6 +551,11 @@ var GeneratedAPIJSON = &APIJSON{
Doc: "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}\n",
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",
@ -1124,6 +1129,11 @@ var GeneratedAPIJSON = &APIJSON{
Doc: "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}\n",
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",

View File

@ -57,6 +57,7 @@ import (
"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 @@ func defaultAnalyzers() map[string]*Analyzer {
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: {

View File

@ -30,15 +30,15 @@ func GetIndexExprData(n ast.Node) *IndexExprData {
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
}

View File

@ -36,17 +36,20 @@ func GetIndexExprData(n ast.Node) *IndexExprData {
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