blob: b3fff4e8408569e705467b7633f2ca8397ac5810 [file] [log] [blame]
// 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.
//go:build go1.18
// +build go1.18
package infertypeargs
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/versions"
)
// DiagnoseInferableTypeArgs reports diagnostics describing simplifications to type
// arguments overlapping with the provided start and end position.
//
// If start or end is token.NoPos, the corresponding bound is not checked
// (i.e. if both start and end are NoPos, all call expressions are considered).
func DiagnoseInferableTypeArgs(fset *token.FileSet, inspect *inspector.Inspector, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
var diags []analysis.Diagnostic
nodeFilter := []ast.Node{(*ast.CallExpr)(nil)}
inspect.Preorder(nodeFilter, func(node ast.Node) {
call := node.(*ast.CallExpr)
x, lbrack, indices, rbrack := typeparams.UnpackIndexExpr(call.Fun)
ident := calledIdent(x)
if ident == nil || len(indices) == 0 {
return // no explicit args, nothing to do
}
if (start.IsValid() && call.End() < start) || (end.IsValid() && call.Pos() > end) {
return // non-overlapping
}
// Confirm that instantiation actually occurred at this ident.
idata, ok := info.Instances[ident]
if !ok {
return // something went wrong, but fail open
}
instance := idata.Type
// Start removing argument expressions from the right, and check if we can
// still infer the call expression.
required := len(indices) // number of type expressions that are required
for i := len(indices) - 1; i >= 0; i-- {
var fun ast.Expr
if i == 0 {
// No longer an index expression: just use the parameterized operand.
fun = x
} else {
fun = typeparams.PackIndexExpr(x, lbrack, indices[:i], indices[i-1].End())
}
newCall := &ast.CallExpr{
Fun: fun,
Lparen: call.Lparen,
Args: call.Args,
Ellipsis: call.Ellipsis,
Rparen: call.Rparen,
}
info := &types.Info{
Instances: make(map[*ast.Ident]types.Instance),
}
versions.InitFileVersions(info)
if err := types.CheckExpr(fset, pkg, call.Pos(), newCall, info); err != nil {
// Most likely inference failed.
break
}
newIData := info.Instances[ident]
newInstance := newIData.Type
if !types.Identical(instance, newInstance) {
// The inferred result type does not match the original result type, so
// this simplification is not valid.
break
}
required = i
}
if required < len(indices) {
var s, e token.Pos
var edit analysis.TextEdit
if required == 0 {
s, e = lbrack, rbrack+1 // erase the entire index
edit = analysis.TextEdit{Pos: s, End: e}
} else {
s = indices[required].Pos()
e = rbrack
// erase from end of last arg to include last comma & white-spaces
edit = analysis.TextEdit{Pos: indices[required-1].End(), End: e}
}
// Recheck that our (narrower) fixes overlap with the requested range.
if (start.IsValid() && e < start) || (end.IsValid() && s > end) {
return // non-overlapping
}
diags = append(diags, analysis.Diagnostic{
Pos: s,
End: e,
Message: "unnecessary type arguments",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "simplify type arguments",
TextEdits: []analysis.TextEdit{edit},
}},
})
}
})
return diags
}
func calledIdent(x ast.Expr) *ast.Ident {
switch x := x.(type) {
case *ast.Ident:
return x
case *ast.SelectorExpr:
return x.Sel
}
return nil
}