Skip to content

Commit 1e70e11

Browse files
mpywclaude
andcommitted
refactor: consolidate duplicate FuncSpec and ExtractFunc implementations
- Create internal/funcspec package with shared Spec, Parse, Matches, ExtractFunc - Update deriver to use funcspec.Spec and funcspec.ExtractFunc - Update spawner to use funcspec.Spec and delegate GetFuncFromCall to funcspec.ExtractFunc - Update context.CheckContext.ExtractCallFunc to use funcspec.ExtractFunc - Update ssa.Tracer to use funcspec.Spec.Matches, remove duplicate matchesSpec/matchesMethod 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent bc134be commit 1e70e11

File tree

5 files changed

+141
-293
lines changed

5 files changed

+141
-293
lines changed

internal/context/context.go

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"golang.org/x/tools/go/analysis"
1010

1111
"github.com/mpyw/goroutinectx/internal/directives/carrier"
12+
"github.com/mpyw/goroutinectx/internal/funcspec"
1213
internalssa "github.com/mpyw/goroutinectx/internal/ssa"
1314
"github.com/mpyw/goroutinectx/internal/typeutil"
1415
)
@@ -84,26 +85,7 @@ func (c *CheckContext) FuncLitUsesContext(lit *ast.FuncLit) bool {
8485

8586
// ExtractCallFunc extracts the types.Func from a call expression.
8687
func (c *CheckContext) ExtractCallFunc(call *ast.CallExpr) *types.Func {
87-
switch fun := call.Fun.(type) {
88-
case *ast.Ident:
89-
if f, ok := c.Pass.TypesInfo.ObjectOf(fun).(*types.Func); ok {
90-
return f
91-
}
92-
93-
case *ast.SelectorExpr:
94-
sel := c.Pass.TypesInfo.Selections[fun]
95-
if sel != nil {
96-
if f, ok := sel.Obj().(*types.Func); ok {
97-
return f
98-
}
99-
} else {
100-
if f, ok := c.Pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func); ok {
101-
return f
102-
}
103-
}
104-
}
105-
106-
return nil
88+
return funcspec.ExtractFunc(c.Pass, call)
10789
}
10890

10991
// ArgUsesContext checks if an expression references a context variable.

internal/directives/deriver/directive.go

Lines changed: 10 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,15 @@ import (
77
"strings"
88

99
"golang.org/x/tools/go/analysis"
10-
)
1110

12-
// FuncSpec holds parsed components of a derive function specification.
13-
type FuncSpec struct {
14-
PkgPath string
15-
TypeName string // empty for package-level functions
16-
FuncName string
17-
}
11+
"github.com/mpyw/goroutinectx/internal/funcspec"
12+
)
1813

1914
// Matcher provides OR/AND matching for derive function specifications.
2015
// The check passes if ANY group is fully satisfied (OR semantics).
2116
// A group is satisfied if ALL functions in that group are called (AND semantics).
2217
type Matcher struct {
23-
OrGroups [][]FuncSpec
18+
OrGroups [][]funcspec.Spec
2419
Original string
2520
}
2621

@@ -40,15 +35,15 @@ func NewMatcher(deriveFuncsStr string) *Matcher {
4035
}
4136

4237
// Split by plus (AND within group)
43-
var andGroup []FuncSpec
38+
var andGroup []funcspec.Spec
4439

4540
for andPart := range strings.SplitSeq(orPart, "+") {
4641
andPart = strings.TrimSpace(andPart)
4742
if andPart == "" {
4843
continue
4944
}
5045

51-
spec := parseFunc(andPart)
46+
spec := funcspec.Parse(andPart)
5247
andGroup = append(andGroup, spec)
5348
}
5449

@@ -60,41 +55,6 @@ func NewMatcher(deriveFuncsStr string) *Matcher {
6055
return m
6156
}
6257

63-
// parseFunc parses a single derive function string into components.
64-
// Format: "pkg/path.Func" or "pkg/path.Type.Method".
65-
func parseFunc(s string) FuncSpec {
66-
spec := FuncSpec{}
67-
68-
lastDot := strings.LastIndex(s, ".")
69-
if lastDot == -1 {
70-
spec.FuncName = s
71-
72-
return spec
73-
}
74-
75-
spec.FuncName = s[lastDot+1:]
76-
prefix := s[:lastDot]
77-
78-
// Check if there's another dot (indicating Type.Method)
79-
// Type names start with uppercase in Go.
80-
secondLastDot := strings.LastIndex(prefix, ".")
81-
if secondLastDot != -1 {
82-
potentialTypeName := prefix[secondLastDot+1:]
83-
if len(potentialTypeName) > 0 && potentialTypeName[0] >= 'A' && potentialTypeName[0] <= 'Z' {
84-
spec.PkgPath = prefix[:secondLastDot]
85-
spec.TypeName = potentialTypeName
86-
} else {
87-
spec.PkgPath = prefix
88-
spec.TypeName = ""
89-
}
90-
} else {
91-
spec.PkgPath = prefix
92-
spec.TypeName = ""
93-
}
94-
95-
return spec
96-
}
97-
9858
// SatisfiesAnyGroup checks if the AST node satisfies ANY of the OR groups.
9959
func (m *Matcher) SatisfiesAnyGroup(pass *analysis.Pass, node ast.Node) bool {
10060
calledFuncs := collectCalledFuncs(pass, node)
@@ -118,7 +78,7 @@ func (m *Matcher) IsEmpty() bool {
11878
func (m *Matcher) MatchesFunc(fn *types.Func) bool {
11979
for _, andGroup := range m.OrGroups {
12080
for _, spec := range andGroup {
121-
if matchesSpec(fn, spec) {
81+
if spec.Matches(fn) {
12282
return true
12383
}
12484
}
@@ -142,7 +102,7 @@ func collectCalledFuncs(pass *analysis.Pass, node ast.Node) []*types.Func {
142102
return true
143103
}
144104

145-
if fn := extractFunc(pass, call); fn != nil {
105+
if fn := funcspec.ExtractFunc(pass, call); fn != nil {
146106
funcs = append(funcs, fn)
147107
}
148108

@@ -152,34 +112,8 @@ func collectCalledFuncs(pass *analysis.Pass, node ast.Node) []*types.Func {
152112
return funcs
153113
}
154114

155-
// extractFunc extracts the types.Func from a call expression.
156-
func extractFunc(pass *analysis.Pass, call *ast.CallExpr) *types.Func {
157-
switch fun := call.Fun.(type) {
158-
case *ast.Ident:
159-
obj := pass.TypesInfo.ObjectOf(fun)
160-
if f, ok := obj.(*types.Func); ok {
161-
return f
162-
}
163-
164-
case *ast.SelectorExpr:
165-
sel := pass.TypesInfo.Selections[fun]
166-
if sel != nil {
167-
if f, ok := sel.Obj().(*types.Func); ok {
168-
return f
169-
}
170-
} else {
171-
obj := pass.TypesInfo.ObjectOf(fun.Sel)
172-
if f, ok := obj.(*types.Func); ok {
173-
return f
174-
}
175-
}
176-
}
177-
178-
return nil
179-
}
180-
181115
// groupSatisfied checks if ALL specs in the AND group are satisfied.
182-
func groupSatisfied(calledFuncs []*types.Func, andGroup []FuncSpec) bool {
116+
func groupSatisfied(calledFuncs []*types.Func, andGroup []funcspec.Spec) bool {
183117
for _, spec := range andGroup {
184118
if !specSatisfied(calledFuncs, spec) {
185119
return false
@@ -190,54 +124,12 @@ func groupSatisfied(calledFuncs []*types.Func, andGroup []FuncSpec) bool {
190124
}
191125

192126
// specSatisfied checks if the spec is satisfied by any of the called functions.
193-
func specSatisfied(calledFuncs []*types.Func, spec FuncSpec) bool {
127+
func specSatisfied(calledFuncs []*types.Func, spec funcspec.Spec) bool {
194128
for _, fn := range calledFuncs {
195-
if matchesSpec(fn, spec) {
129+
if spec.Matches(fn) {
196130
return true
197131
}
198132
}
199133

200134
return false
201135
}
202-
203-
// matchesSpec checks if a types.Func matches the given derive function spec.
204-
func matchesSpec(fn *types.Func, spec FuncSpec) bool {
205-
if fn.Name() != spec.FuncName {
206-
return false
207-
}
208-
209-
if fn.Pkg() == nil || fn.Pkg().Path() != spec.PkgPath {
210-
return false
211-
}
212-
213-
if spec.TypeName != "" {
214-
return matchesMethod(fn, spec.TypeName)
215-
}
216-
217-
return true
218-
}
219-
220-
// matchesMethod checks if a types.Func is a method on the expected type.
221-
func matchesMethod(fn *types.Func, typeName string) bool {
222-
sig, ok := fn.Type().(*types.Signature)
223-
if !ok {
224-
return false
225-
}
226-
227-
recv := sig.Recv()
228-
if recv == nil {
229-
return false
230-
}
231-
232-
recvType := recv.Type()
233-
if ptr, ok := recvType.(*types.Pointer); ok {
234-
recvType = ptr.Elem()
235-
}
236-
237-
named, ok := recvType.(*types.Named)
238-
if !ok {
239-
return false
240-
}
241-
242-
return named.Obj().Name() == typeName
243-
}

0 commit comments

Comments
 (0)