-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinterface_detect.go
More file actions
133 lines (124 loc) · 3.2 KB
/
interface_detect.go
File metadata and controls
133 lines (124 loc) · 3.2 KB
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package repomap
import (
"slices"
"strings"
)
// DetectImplementations scans parsed files and populates Symbol.Implements
// on struct types whose exported method sets satisfy an interface declared
// in the same project.
//
// Matching is by exported method name only — a struct implements an
// interface if its methods are a superset of the interface's method names.
// This is a proxy for Go's structural typing; it is tight enough for LLM
// signal but not a full type checker (signature compatibility is not
// verified, and embedded interfaces are treated as opaque method names).
func DetectImplementations(files []*FileSymbols) {
if len(files) == 0 {
return
}
interfaces := collectInterfaces(files)
if len(interfaces) == 0 {
return
}
typeMethods := collectTypeMethods(files)
if len(typeMethods) == 0 {
return
}
for fi := range files {
f := files[fi]
for si := range f.Symbols {
s := &f.Symbols[si]
if s.Kind != "struct" {
continue
}
methods, has := typeMethods[s.Name]
if !has {
continue
}
var impl []string
for name, required := range interfaces {
if name == s.Name {
continue
}
if isMethodSubset(required, methods) {
impl = append(impl, name)
}
}
if len(impl) > 0 {
slices.Sort(impl)
s.Implements = impl
}
}
}
}
// collectInterfaces builds a map of interface name → required exported method names.
// Skips empty interfaces and any interface sharing its name with a struct
// (ambiguous; avoid self-match noise).
func collectInterfaces(files []*FileSymbols) map[string][]string {
ifaces := make(map[string][]string)
for _, f := range files {
if f == nil {
continue
}
for _, s := range f.Symbols {
if s.Kind != "interface" {
continue
}
methods := parseMemberList(s.Signature)
if len(methods) == 0 {
continue
}
ifaces[s.Name] = methods
}
}
return ifaces
}
// collectTypeMethods maps receiver type name → set of exported method names.
// Strips leading '*' from receivers so value and pointer receivers unify.
func collectTypeMethods(files []*FileSymbols) map[string]map[string]bool {
m := make(map[string]map[string]bool)
for _, f := range files {
if f == nil {
continue
}
for _, s := range f.Symbols {
if s.Kind != "method" || s.Receiver == "" || !s.Exported {
continue
}
recv := strings.TrimPrefix(s.Receiver, "*")
if m[recv] == nil {
m[recv] = make(map[string]bool)
}
m[recv][s.Name] = true
}
}
return m
}
// parseMemberList parses a signature like "{A, B, C}" into ["A", "B", "C"].
// Returns nil for empty or malformed input.
func parseMemberList(sig string) []string {
if len(sig) < 2 || sig[0] != '{' || sig[len(sig)-1] != '}' {
return nil
}
inner := strings.TrimSpace(sig[1 : len(sig)-1])
if inner == "" {
return nil
}
parts := strings.Split(inner, ",")
names := make([]string, 0, len(parts))
for _, p := range parts {
if n := strings.TrimSpace(p); n != "" {
names = append(names, n)
}
}
return names
}
// isMethodSubset reports whether all required method names are present in have.
func isMethodSubset(required []string, have map[string]bool) bool {
for _, name := range required {
if !have[name] {
return false
}
}
return true
}