/
function_lookup.go
113 lines (94 loc) · 3.58 KB
/
function_lookup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package analysis
import (
"go/ast"
"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/go/types/typeutil"
)
// funcLookup allows the performant lookup of function and method declarations in the current package by name,
// and the lookup of cached error codes and affectors for function declarations.
type funcLookup struct {
functions map[string]*ast.FuncDecl // Mapping Function Names to Declarations
methods map[string][]*ast.FuncDecl // Mapping Method Names to Declarations (Multiple Possible per Name)
methodSet typeutil.MethodSetCache
foundCodes map[funcDeclOrLit]CodeSet // Mapping Function Declarations and Function Literals to cached error codes
}
func newFuncLookup() *funcLookup {
return &funcLookup{
map[string]*ast.FuncDecl{},
map[string][]*ast.FuncDecl{},
typeutil.MethodSetCache{},
map[funcDeclOrLit]CodeSet{},
}
}
// collectFunctions creates a funcLookup using the given analysis object.
func collectFunctions(pass *analysis.Pass) *funcLookup {
result := newFuncLookup()
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
// We only need to see function declarations at first; we'll recurse ourselves within there.
nodeFilter := []ast.Node{
(*ast.FuncDecl)(nil),
}
inspect.Nodes(nodeFilter, func(node ast.Node, _ bool) bool {
funcDecl := node.(*ast.FuncDecl)
// Check if it's a function or a method and add accordingly.
if !isMethod(funcDecl) {
result.functions[funcDecl.Name.Name] = funcDecl
} else {
result.methods[funcDecl.Name.Name] = append(result.methods[funcDecl.Name.Name], funcDecl)
}
// Never recurse into the function bodies
return false
})
return result
}
// forEach traverses all the functions and methods in the lookup,
// and applies the given function f to every ast.FuncDecl.
func (lookup *funcLookup) forEach(f func(*ast.FuncDecl)) {
for _, funcDecl := range lookup.functions {
f(funcDecl)
}
for _, methods := range lookup.methods {
for _, funcDecl := range methods {
f(funcDecl)
}
}
}
// searchMethodType searches for method in the type information using receiver type and method name.
func (lookup *funcLookup) searchMethodType(pass *analysis.Pass, receiver types.Type, methodName string) *types.Selection {
methodSet := lookup.methodSet.MethodSet(receiver)
searchedMethodType := methodSet.Lookup(pass.Pkg, methodName)
if searchedMethodType == nil {
// No methods were found for T
// Search methods for *T if T is not already a pointer.
_, ok := receiver.(*types.Pointer)
if !ok {
methodSet = lookup.methodSet.MethodSet(types.NewPointer(receiver))
searchedMethodType = methodSet.Lookup(pass.Pkg, methodName)
}
}
return searchedMethodType
}
// searchMethod tries to find the correct function declaration for a method given the receiver type and method name.
func (lookup *funcLookup) searchMethod(pass *analysis.Pass, receiver types.Type, methodName string) *ast.FuncDecl {
methods, ok := lookup.methods[methodName]
if !ok || len(methods) == 0 {
// Return early if there is no method in the current package with the given name
return nil
}
searchedMethodType := lookup.searchMethodType(pass, receiver, methodName)
if searchedMethodType == nil {
// Return early if there is no method matching receiver and name
return nil
}
// Method we're looking for exists in the current package, we only need to find the right declaration
for _, method := range methods {
methodObj := pass.TypesInfo.ObjectOf(method.Name)
if searchedMethodType.Obj() == methodObj {
return method
}
}
return nil
}