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