| 1 | // Copyright 2020 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 ifaceassert defines an Analyzer that flags |
| 6 | // impossible interface-interface type assertions. |
| 7 | package ifaceassert |
| 8 | |
| 9 | import ( |
| 10 | "go/ast" |
| 11 | "go/types" |
| 12 | |
| 13 | "golang.org/x/tools/go/analysis" |
| 14 | "golang.org/x/tools/go/analysis/passes/inspect" |
| 15 | "golang.org/x/tools/go/ast/inspector" |
| 16 | ) |
| 17 | |
| 18 | const Doc = `detect impossible interface-to-interface type assertions |
| 19 | |
| 20 | This checker flags type assertions v.(T) and corresponding type-switch cases |
| 21 | in which the static type V of v is an interface that cannot possibly implement |
| 22 | the target interface T. This occurs when V and T contain methods with the same |
| 23 | name but different signatures. Example: |
| 24 | |
| 25 | var v interface { |
| 26 | Read() |
| 27 | } |
| 28 | _ = v.(io.Reader) |
| 29 | |
| 30 | The Read method in v has a different signature than the Read method in |
| 31 | io.Reader, so this assertion cannot succeed. |
| 32 | ` |
| 33 | |
| 34 | var Analyzer = &analysis.Analyzer{ |
| 35 | Name: "ifaceassert", |
| 36 | Doc: Doc, |
| 37 | Requires: []*analysis.Analyzer{inspect.Analyzer}, |
| 38 | Run: run, |
| 39 | } |
| 40 | |
| 41 | // assertableTo checks whether interface v can be asserted into t. It returns |
| 42 | // nil on success, or the first conflicting method on failure. |
| 43 | func assertableTo(v, t types.Type) *types.Func { |
| 44 | if t == nil || v == nil { |
| 45 | // not assertable to, but there is no missing method |
| 46 | return nil |
| 47 | } |
| 48 | // ensure that v and t are interfaces |
| 49 | V, _ := v.Underlying().(*types.Interface) |
| 50 | T, _ := t.Underlying().(*types.Interface) |
| 51 | if V == nil || T == nil { |
| 52 | return nil |
| 53 | } |
| 54 | |
| 55 | // Mitigations for interface comparisons and generics. |
| 56 | // TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion. |
| 57 | if isParameterized(V) || isParameterized(T) { |
| 58 | return nil |
| 59 | } |
| 60 | if f, wrongType := types.MissingMethod(V, T, false); wrongType { |
| 61 | return f |
| 62 | } |
| 63 | return nil |
| 64 | } |
| 65 | |
| 66 | func run(pass *analysis.Pass) (interface{}, error) { |
| 67 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
| 68 | nodeFilter := []ast.Node{ |
| 69 | (*ast.TypeAssertExpr)(nil), |
| 70 | (*ast.TypeSwitchStmt)(nil), |
| 71 | } |
| 72 | inspect.Preorder(nodeFilter, func(n ast.Node) { |
| 73 | var ( |
| 74 | assert *ast.TypeAssertExpr // v.(T) expression |
| 75 | targets []ast.Expr // interfaces T in v.(T) |
| 76 | ) |
| 77 | switch n := n.(type) { |
| 78 | case *ast.TypeAssertExpr: |
| 79 | // take care of v.(type) in *ast.TypeSwitchStmt |
| 80 | if n.Type == nil { |
| 81 | return |
| 82 | } |
| 83 | assert = n |
| 84 | targets = append(targets, n.Type) |
| 85 | case *ast.TypeSwitchStmt: |
| 86 | // retrieve type assertion from type switch's 'assign' field |
| 87 | switch t := n.Assign.(type) { |
| 88 | case *ast.ExprStmt: |
| 89 | assert = t.X.(*ast.TypeAssertExpr) |
| 90 | case *ast.AssignStmt: |
| 91 | assert = t.Rhs[0].(*ast.TypeAssertExpr) |
| 92 | } |
| 93 | // gather target types from case clauses |
| 94 | for _, c := range n.Body.List { |
| 95 | targets = append(targets, c.(*ast.CaseClause).List...) |
| 96 | } |
| 97 | } |
| 98 | V := pass.TypesInfo.TypeOf(assert.X) |
| 99 | for _, target := range targets { |
| 100 | T := pass.TypesInfo.TypeOf(target) |
| 101 | if f := assertableTo(V, T); f != nil { |
| 102 | pass.Reportf( |
| 103 | target.Pos(), |
| 104 | "impossible type assertion: no type can implement both %v and %v (conflicting types for %v method)", |
| 105 | V, T, f.Name(), |
| 106 | ) |
| 107 | } |
| 108 | } |
| 109 | }) |
| 110 | return nil, nil |
| 111 | } |
| 112 |
Members