blob: 5f07ed3ffde9f44fe0d1c7454dce6f37e7b3f135 [file] [log] [blame]
// Copyright 2020 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 ifaceassert
import (
_ "embed"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "ifaceassert",
Doc: analysisutil.MustExtractDoc(doc, "ifaceassert"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
// assertableTo checks whether interface v can be asserted into t. It returns
// nil on success, or the first conflicting method on failure.
func assertableTo(free *typeparams.Free, v, t types.Type) *types.Func {
if t == nil || v == nil {
// not assertable to, but there is no missing method
return nil
}
// ensure that v and t are interfaces
V, _ := v.Underlying().(*types.Interface)
T, _ := t.Underlying().(*types.Interface)
if V == nil || T == nil {
return nil
}
// Mitigations for interface comparisons and generics.
// TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion.
if free.Has(V) || free.Has(T) {
return nil
}
if f, wrongType := types.MissingMethod(V, T, false); wrongType {
return f
}
return nil
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.TypeAssertExpr)(nil),
(*ast.TypeSwitchStmt)(nil),
}
var free typeparams.Free
inspect.Preorder(nodeFilter, func(n ast.Node) {
var (
assert *ast.TypeAssertExpr // v.(T) expression
targets []ast.Expr // interfaces T in v.(T)
)
switch n := n.(type) {
case *ast.TypeAssertExpr:
// take care of v.(type) in *ast.TypeSwitchStmt
if n.Type == nil {
return
}
assert = n
targets = append(targets, n.Type)
case *ast.TypeSwitchStmt:
// retrieve type assertion from type switch's 'assign' field
switch t := n.Assign.(type) {
case *ast.ExprStmt:
assert = t.X.(*ast.TypeAssertExpr)
case *ast.AssignStmt:
assert = t.Rhs[0].(*ast.TypeAssertExpr)
}
// gather target types from case clauses
for _, c := range n.Body.List {
targets = append(targets, c.(*ast.CaseClause).List...)
}
}
V := pass.TypesInfo.TypeOf(assert.X)
for _, target := range targets {
T := pass.TypesInfo.TypeOf(target)
if f := assertableTo(&free, V, T); f != nil {
pass.Reportf(
target.Pos(),
"impossible type assertion: no type can implement both %v and %v (conflicting types for %v method)",
V, T, f.Name(),
)
}
}
})
return nil, nil
}