goscript 0.0.60 → 0.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/compiler/analysis.go +974 -369
- package/compiler/assignment.go +72 -0
- package/compiler/compiler.go +74 -15
- package/compiler/composite-lit.go +29 -8
- package/compiler/decl.go +67 -98
- package/compiler/expr-call-async.go +26 -1
- package/compiler/expr-call-builtins.go +60 -4
- package/compiler/expr-call-helpers.go +182 -0
- package/compiler/expr-call-type-conversion.go +37 -5
- package/compiler/expr-call.go +25 -33
- package/compiler/expr-selector.go +71 -1
- package/compiler/expr-type.go +49 -3
- package/compiler/expr.go +37 -28
- package/compiler/index.test.ts +3 -1
- package/compiler/lit.go +13 -4
- package/compiler/spec-struct.go +42 -9
- package/compiler/spec-value.go +2 -2
- package/compiler/spec.go +42 -5
- package/compiler/stmt-assign.go +71 -0
- package/compiler/stmt-range.go +2 -2
- package/compiler/stmt.go +130 -10
- package/compiler/type-utils.go +40 -16
- package/compiler/type.go +50 -12
- package/dist/gs/builtin/builtin.d.ts +8 -1
- package/dist/gs/builtin/builtin.js +26 -1
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/errors.d.ts +1 -0
- package/dist/gs/builtin/errors.js +8 -0
- package/dist/gs/builtin/errors.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +5 -4
- package/dist/gs/builtin/slice.js +51 -21
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +28 -2
- package/dist/gs/builtin/type.js +132 -0
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/bytes/reader.gs.d.ts +1 -1
- package/dist/gs/bytes/reader.gs.js +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/internal/byteorder/index.d.ts +6 -0
- package/dist/gs/internal/byteorder/index.js +34 -0
- package/dist/gs/internal/byteorder/index.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +3 -3
- package/dist/gs/reflect/index.js +2 -2
- package/dist/gs/reflect/index.js.map +1 -1
- package/dist/gs/reflect/map.d.ts +3 -2
- package/dist/gs/reflect/map.js +37 -3
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +55 -8
- package/dist/gs/reflect/type.js +889 -23
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.d.ts +11 -12
- package/dist/gs/reflect/types.js +26 -15
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/value.d.ts +4 -4
- package/dist/gs/reflect/value.js +8 -2
- package/dist/gs/reflect/value.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +32 -0
- package/dist/gs/slices/slices.js +81 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +2 -2
- package/dist/gs/sync/atomic/type.gs.js +12 -2
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
- package/dist/gs/unicode/utf8/utf8.js +10 -6
- package/dist/gs/unicode/utf8/utf8.js.map +1 -1
- package/go.mod +4 -4
- package/go.sum +8 -16
- package/gs/builtin/builtin.ts +27 -2
- package/gs/builtin/errors.ts +12 -0
- package/gs/builtin/slice.ts +77 -14
- package/gs/builtin/type.ts +167 -2
- package/gs/bytes/reader.gs.ts +2 -2
- package/gs/internal/byteorder/index.ts +40 -0
- package/gs/math/hypot.gs.test.ts +3 -1
- package/gs/math/pow10.gs.test.ts +5 -4
- package/gs/reflect/index.ts +6 -3
- package/gs/reflect/map.test.ts +7 -6
- package/gs/reflect/map.ts +49 -7
- package/gs/reflect/type.ts +1139 -43
- package/gs/reflect/types.ts +34 -21
- package/gs/reflect/value.ts +12 -6
- package/gs/slices/slices.ts +92 -0
- package/gs/sync/atomic/type.gs.ts +14 -5
- package/gs/sync/meta.json +1 -1
- package/gs/unicode/utf8/utf8.ts +12 -8
- package/package.json +13 -13
package/compiler/analysis.go
CHANGED
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
"go/token"
|
|
7
7
|
"go/types"
|
|
8
8
|
"path/filepath"
|
|
9
|
+
"slices"
|
|
9
10
|
"strings"
|
|
10
11
|
|
|
11
12
|
"github.com/aperturerobotics/goscript"
|
|
@@ -51,6 +52,10 @@ type ShadowingInfo struct {
|
|
|
51
52
|
ShadowedVariables map[string]types.Object
|
|
52
53
|
// TempVariables maps shadowed variable names to temporary variable names
|
|
53
54
|
TempVariables map[string]string
|
|
55
|
+
// TypeShadowedVars maps variable names that shadow type names to their renamed identifier
|
|
56
|
+
// This happens when a variable name matches a type name used in its initialization
|
|
57
|
+
// e.g., field := field{...} where the variable 'field' shadows the type 'field'
|
|
58
|
+
TypeShadowedVars map[string]string
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
// FunctionTypeInfo represents Go function type information for reflection
|
|
@@ -106,9 +111,8 @@ type MethodKey struct {
|
|
|
106
111
|
|
|
107
112
|
// ImplementationInfo tracks information about a struct that implements an interface method
|
|
108
113
|
type ImplementationInfo struct {
|
|
109
|
-
StructType
|
|
110
|
-
Method
|
|
111
|
-
IsAsyncByFlow bool // Whether this implementation is async based on control flow analysis
|
|
114
|
+
StructType *types.Named // The struct type that implements the interface
|
|
115
|
+
Method *types.Func // The method object
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
// Analysis holds information gathered during the analysis phase of the Go code compilation.
|
|
@@ -122,6 +126,10 @@ type Analysis struct {
|
|
|
122
126
|
// Imports stores the imports for the file
|
|
123
127
|
Imports map[string]*fileImport
|
|
124
128
|
|
|
129
|
+
// SyntheticImports stores imports that need to be written but were not in the source file.
|
|
130
|
+
// These are typically needed for promoted methods from embedded structs.
|
|
131
|
+
SyntheticImports map[string]*fileImport
|
|
132
|
+
|
|
125
133
|
// Cmap stores the comment map for the file
|
|
126
134
|
Cmap ast.CommentMap
|
|
127
135
|
|
|
@@ -141,6 +149,11 @@ type Analysis struct {
|
|
|
141
149
|
// FunctionAssignments tracks which function literals are assigned to which variables
|
|
142
150
|
FunctionAssignments map[types.Object]ast.Node
|
|
143
151
|
|
|
152
|
+
// AsyncReturningVars tracks variables whose function type returns async values
|
|
153
|
+
// This happens when a variable is assigned from a higher-order function (like sync.OnceValue)
|
|
154
|
+
// that receives an async function literal as an argument
|
|
155
|
+
AsyncReturningVars map[types.Object]bool
|
|
156
|
+
|
|
144
157
|
// NamedBasicTypes tracks types that should be implemented as type aliases with standalone functions
|
|
145
158
|
// This includes named types with basic underlying types (like uint32, string) that have methods
|
|
146
159
|
NamedBasicTypes map[types.Type]bool
|
|
@@ -153,10 +166,6 @@ type Analysis struct {
|
|
|
153
166
|
// This is used to determine interface method async status based on implementations
|
|
154
167
|
InterfaceImplementations map[InterfaceMethodKey][]ImplementationInfo
|
|
155
168
|
|
|
156
|
-
// InterfaceMethodAsyncStatus caches the async status determination for interface methods
|
|
157
|
-
// This is computed once during analysis and reused during code generation
|
|
158
|
-
InterfaceMethodAsyncStatus map[InterfaceMethodKey]bool
|
|
159
|
-
|
|
160
169
|
// MethodAsyncStatus stores the async status of all methods analyzed
|
|
161
170
|
// This is computed once during analysis and reused during code generation
|
|
162
171
|
MethodAsyncStatus map[MethodKey]bool
|
|
@@ -179,6 +188,14 @@ type PackageAnalysis struct {
|
|
|
179
188
|
// TypeCalls maps file names to the types they reference from other files
|
|
180
189
|
// Key: filename (without .go extension), Value: map[sourceFile][]typeNames
|
|
181
190
|
TypeCalls map[string]map[string][]string
|
|
191
|
+
|
|
192
|
+
// VariableDefs maps file names to the package-level variables defined in that file
|
|
193
|
+
// Key: filename (without .go extension), Value: list of variable names
|
|
194
|
+
VariableDefs map[string][]string
|
|
195
|
+
|
|
196
|
+
// VariableCalls maps file names to the package-level variables they reference from other files
|
|
197
|
+
// Key: filename (without .go extension), Value: map[sourceFile][]variableNames
|
|
198
|
+
VariableCalls map[string]map[string][]string
|
|
182
199
|
}
|
|
183
200
|
|
|
184
201
|
// NewAnalysis creates a new Analysis instance.
|
|
@@ -188,19 +205,19 @@ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
|
|
|
188
205
|
}
|
|
189
206
|
|
|
190
207
|
return &Analysis{
|
|
191
|
-
VariableUsage:
|
|
192
|
-
Imports:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
MethodAsyncStatus:
|
|
208
|
+
VariableUsage: make(map[types.Object]*VariableUsageInfo),
|
|
209
|
+
Imports: make(map[string]*fileImport),
|
|
210
|
+
SyntheticImports: make(map[string]*fileImport),
|
|
211
|
+
FunctionData: make(map[types.Object]*FunctionInfo),
|
|
212
|
+
NodeData: make(map[ast.Node]*NodeInfo),
|
|
213
|
+
FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
|
|
214
|
+
ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
|
|
215
|
+
FunctionAssignments: make(map[types.Object]ast.Node),
|
|
216
|
+
AsyncReturningVars: make(map[types.Object]bool),
|
|
217
|
+
NamedBasicTypes: make(map[types.Type]bool),
|
|
218
|
+
AllPackages: allPackages,
|
|
219
|
+
InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
|
|
220
|
+
MethodAsyncStatus: make(map[MethodKey]bool),
|
|
204
221
|
}
|
|
205
222
|
}
|
|
206
223
|
|
|
@@ -211,6 +228,8 @@ func NewPackageAnalysis() *PackageAnalysis {
|
|
|
211
228
|
FunctionCalls: make(map[string]map[string][]string),
|
|
212
229
|
TypeDefs: make(map[string][]string),
|
|
213
230
|
TypeCalls: make(map[string]map[string][]string),
|
|
231
|
+
VariableDefs: make(map[string][]string),
|
|
232
|
+
VariableCalls: make(map[string]map[string][]string),
|
|
214
233
|
}
|
|
215
234
|
}
|
|
216
235
|
|
|
@@ -236,6 +255,22 @@ func (a *Analysis) ensureFunctionData(obj types.Object) *FunctionInfo {
|
|
|
236
255
|
return a.FunctionData[obj]
|
|
237
256
|
}
|
|
238
257
|
|
|
258
|
+
// GetFunctionInfoFromContext returns FunctionInfo based on the enclosing function context
|
|
259
|
+
func (a *Analysis) GetFunctionInfoFromContext(nodeInfo *NodeInfo, pkg *packages.Package) *FunctionInfo {
|
|
260
|
+
if nodeInfo == nil {
|
|
261
|
+
return nil
|
|
262
|
+
}
|
|
263
|
+
if nodeInfo.EnclosingFuncDecl != nil {
|
|
264
|
+
if obj := pkg.TypesInfo.ObjectOf(nodeInfo.EnclosingFuncDecl.Name); obj != nil {
|
|
265
|
+
return a.FunctionData[obj]
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if nodeInfo.EnclosingFuncLit != nil {
|
|
269
|
+
return a.FuncLitData[nodeInfo.EnclosingFuncLit]
|
|
270
|
+
}
|
|
271
|
+
return nil
|
|
272
|
+
}
|
|
273
|
+
|
|
239
274
|
// NeedsDefer returns whether the given node needs defer handling.
|
|
240
275
|
func (a *Analysis) NeedsDefer(node ast.Node) bool {
|
|
241
276
|
if node == nil {
|
|
@@ -302,6 +337,15 @@ func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
|
|
|
302
337
|
return false
|
|
303
338
|
}
|
|
304
339
|
|
|
340
|
+
// IsAsyncReturningVar returns whether the given variable holds a function that returns async values.
|
|
341
|
+
// This is true when the variable is assigned from a higher-order function that receives an async function literal.
|
|
342
|
+
func (a *Analysis) IsAsyncReturningVar(obj types.Object) bool {
|
|
343
|
+
if obj == nil {
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
return a.AsyncReturningVars[obj]
|
|
347
|
+
}
|
|
348
|
+
|
|
305
349
|
func (a *Analysis) IsReceiverUsed(obj types.Object) bool {
|
|
306
350
|
if obj == nil {
|
|
307
351
|
return false
|
|
@@ -370,7 +414,7 @@ func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
|
|
|
370
414
|
}
|
|
371
415
|
|
|
372
416
|
// For pointer variables, check if they point to a variable-referenced value
|
|
373
|
-
if
|
|
417
|
+
if _, ok := obj.Type().(*types.Pointer); ok {
|
|
374
418
|
// Check all assignments to this pointer variable
|
|
375
419
|
for varObj, info := range a.VariableUsage {
|
|
376
420
|
if varObj == obj {
|
|
@@ -382,11 +426,6 @@ func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
|
|
|
382
426
|
}
|
|
383
427
|
}
|
|
384
428
|
}
|
|
385
|
-
|
|
386
|
-
// Handle direct pointer initialization like: var p *int = &x
|
|
387
|
-
// Check if the pointer type's element type requires variable referencing
|
|
388
|
-
_ = ptrType.Elem()
|
|
389
|
-
// For now, conservatively return false for untracked cases
|
|
390
429
|
}
|
|
391
430
|
|
|
392
431
|
return false
|
|
@@ -453,9 +492,6 @@ type analysisVisitor struct {
|
|
|
453
492
|
|
|
454
493
|
// currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
|
|
455
494
|
currentFuncLit *ast.FuncLit
|
|
456
|
-
|
|
457
|
-
// visitingMethods tracks methods currently being analyzed to prevent infinite recursion
|
|
458
|
-
visitingMethods map[MethodKey]bool
|
|
459
495
|
}
|
|
460
496
|
|
|
461
497
|
// getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
|
|
@@ -906,9 +942,63 @@ func (v *analysisVisitor) visitAssignStmt(n *ast.AssignStmt) ast.Visitor {
|
|
|
906
942
|
}
|
|
907
943
|
}
|
|
908
944
|
|
|
945
|
+
// NOTE: Async-returning variable tracking (trackAsyncReturningVar) is done in a separate pass
|
|
946
|
+
// after function literals are analyzed for async status. See trackAsyncReturningVarsAllFiles.
|
|
947
|
+
|
|
909
948
|
return v
|
|
910
949
|
}
|
|
911
950
|
|
|
951
|
+
// trackAsyncReturningVar tracks variables that are assigned from higher-order function calls
|
|
952
|
+
// where one of the arguments is an async function literal.
|
|
953
|
+
// Pattern: x := higherOrderFunc(asyncFuncLit)
|
|
954
|
+
// This is needed because when sync.OnceValue(asyncFunc) is called, the result is a function
|
|
955
|
+
// that returns a Promise, and callers of x() need to await the result.
|
|
956
|
+
func (v *analysisVisitor) trackAsyncReturningVar(lhs ast.Expr, rhs ast.Expr) {
|
|
957
|
+
// LHS must be an identifier
|
|
958
|
+
lhsIdent, ok := lhs.(*ast.Ident)
|
|
959
|
+
if !ok {
|
|
960
|
+
return
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// RHS must be a call expression
|
|
964
|
+
callExpr, ok := rhs.(*ast.CallExpr)
|
|
965
|
+
if !ok {
|
|
966
|
+
return
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// The result type of the call must be a function type
|
|
970
|
+
rhsType := v.pkg.TypesInfo.TypeOf(rhs)
|
|
971
|
+
if rhsType == nil {
|
|
972
|
+
return
|
|
973
|
+
}
|
|
974
|
+
_, isFunc := rhsType.Underlying().(*types.Signature)
|
|
975
|
+
if !isFunc {
|
|
976
|
+
return
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// Check if any argument is an async function literal
|
|
980
|
+
// Use containsAsyncOperationsComplete to check the function body directly
|
|
981
|
+
// rather than relying on the InAsyncContext flag which may not be set yet
|
|
982
|
+
hasAsyncArg := false
|
|
983
|
+
for _, arg := range callExpr.Args {
|
|
984
|
+
if funcLit, ok := arg.(*ast.FuncLit); ok {
|
|
985
|
+
if funcLit.Body != nil && v.containsAsyncOperationsComplete(funcLit.Body, v.pkg) {
|
|
986
|
+
hasAsyncArg = true
|
|
987
|
+
break
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if !hasAsyncArg {
|
|
993
|
+
return
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Mark the LHS variable as returning async values
|
|
997
|
+
if obj := v.pkg.TypesInfo.ObjectOf(lhsIdent); obj != nil {
|
|
998
|
+
v.analysis.AsyncReturningVars[obj] = true
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
912
1002
|
// visitReturnStmt handles return statement analysis
|
|
913
1003
|
func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
|
|
914
1004
|
nodeInfo := v.analysis.ensureNodeData(n)
|
|
@@ -922,18 +1012,8 @@ func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
|
|
|
922
1012
|
|
|
923
1013
|
// Check if it's a bare return
|
|
924
1014
|
if len(n.Results) == 0 {
|
|
925
|
-
if v.
|
|
926
|
-
|
|
927
|
-
if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
|
|
928
|
-
if _, ok := v.analysis.FunctionData[obj]; ok {
|
|
929
|
-
nodeInfo.IsBareReturn = true
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
} else if v.currentFuncLit != nil {
|
|
933
|
-
// Check if the enclosing function literal has named returns
|
|
934
|
-
if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
|
|
935
|
-
nodeInfo.IsBareReturn = true
|
|
936
|
-
}
|
|
1015
|
+
if v.analysis.GetFunctionInfoFromContext(nodeInfo, v.pkg) != nil {
|
|
1016
|
+
nodeInfo.IsBareReturn = true
|
|
937
1017
|
}
|
|
938
1018
|
}
|
|
939
1019
|
return v
|
|
@@ -1011,14 +1091,8 @@ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) as
|
|
|
1011
1091
|
// Find the corresponding method in the struct type
|
|
1012
1092
|
structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
|
|
1013
1093
|
if structMethod != nil {
|
|
1014
|
-
// Determine if this struct method is async using unified system
|
|
1015
|
-
isAsync := false
|
|
1016
|
-
if obj := structMethod; obj != nil {
|
|
1017
|
-
isAsync = v.analysis.IsAsyncFunc(obj)
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
1094
|
// Track this interface implementation
|
|
1021
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod
|
|
1095
|
+
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
1022
1096
|
}
|
|
1023
1097
|
}
|
|
1024
1098
|
return v
|
|
@@ -1182,9 +1256,8 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
|
|
|
1182
1256
|
|
|
1183
1257
|
// Create visitor for the entire package
|
|
1184
1258
|
visitor := &analysisVisitor{
|
|
1185
|
-
analysis:
|
|
1186
|
-
pkg:
|
|
1187
|
-
visitingMethods: make(map[MethodKey]bool),
|
|
1259
|
+
analysis: analysis,
|
|
1260
|
+
pkg: pkg,
|
|
1188
1261
|
}
|
|
1189
1262
|
|
|
1190
1263
|
// First pass: analyze all declarations and statements across all files
|
|
@@ -1221,9 +1294,165 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
|
|
|
1221
1294
|
// Interface implementation async status is now updated on-demand in IsInterfaceMethodAsync
|
|
1222
1295
|
visitor.analyzeAllMethodsAsync()
|
|
1223
1296
|
|
|
1297
|
+
// Fourth pass: collect imports needed by promoted methods from embedded structs
|
|
1298
|
+
analysis.addImportsForPromotedMethods(pkg)
|
|
1299
|
+
|
|
1224
1300
|
return analysis
|
|
1225
1301
|
}
|
|
1226
1302
|
|
|
1303
|
+
// addImportsForPromotedMethods scans all struct types in the package for embedded fields
|
|
1304
|
+
// and adds imports for any packages referenced by the promoted methods' parameter/return types.
|
|
1305
|
+
func (a *Analysis) addImportsForPromotedMethods(pkg *packages.Package) {
|
|
1306
|
+
// Collect all package names we need to add
|
|
1307
|
+
packagesToAdd := make(map[string]*types.Package)
|
|
1308
|
+
|
|
1309
|
+
// Iterate through all type definitions in the package
|
|
1310
|
+
scope := pkg.Types.Scope()
|
|
1311
|
+
for _, name := range scope.Names() {
|
|
1312
|
+
obj := scope.Lookup(name)
|
|
1313
|
+
if obj == nil {
|
|
1314
|
+
continue
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// Check if it's a type definition
|
|
1318
|
+
typeName, ok := obj.(*types.TypeName)
|
|
1319
|
+
if !ok {
|
|
1320
|
+
continue
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Get the underlying type
|
|
1324
|
+
namedType, ok := typeName.Type().(*types.Named)
|
|
1325
|
+
if !ok {
|
|
1326
|
+
continue
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// Check if it's a struct
|
|
1330
|
+
structType, ok := namedType.Underlying().(*types.Struct)
|
|
1331
|
+
if !ok {
|
|
1332
|
+
continue
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// Look for embedded fields
|
|
1336
|
+
for i := 0; i < structType.NumFields(); i++ {
|
|
1337
|
+
field := structType.Field(i)
|
|
1338
|
+
if !field.Embedded() {
|
|
1339
|
+
continue
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// Get the type of the embedded field
|
|
1343
|
+
embeddedType := field.Type()
|
|
1344
|
+
|
|
1345
|
+
// Handle pointer to embedded type
|
|
1346
|
+
if ptr, ok := embeddedType.(*types.Pointer); ok {
|
|
1347
|
+
embeddedType = ptr.Elem()
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Use method set to get all promoted methods including pointer receiver methods
|
|
1351
|
+
// This matches Go's behavior where embedding T promotes both T and *T methods
|
|
1352
|
+
methodSetType := embeddedType
|
|
1353
|
+
if _, isPtr := embeddedType.(*types.Pointer); !isPtr {
|
|
1354
|
+
if _, isInterface := embeddedType.Underlying().(*types.Interface); !isInterface {
|
|
1355
|
+
methodSetType = types.NewPointer(embeddedType)
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
embeddedMethodSet := types.NewMethodSet(methodSetType)
|
|
1359
|
+
|
|
1360
|
+
// Scan all methods in the method set
|
|
1361
|
+
for j := 0; j < embeddedMethodSet.Len(); j++ {
|
|
1362
|
+
selection := embeddedMethodSet.At(j)
|
|
1363
|
+
method := selection.Obj()
|
|
1364
|
+
sig, ok := method.Type().(*types.Signature)
|
|
1365
|
+
if !ok {
|
|
1366
|
+
continue
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Scan parameters
|
|
1370
|
+
if sig.Params() != nil {
|
|
1371
|
+
for k := 0; k < sig.Params().Len(); k++ {
|
|
1372
|
+
param := sig.Params().At(k)
|
|
1373
|
+
a.collectPackageFromType(param.Type(), pkg.Types, packagesToAdd)
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Scan results
|
|
1378
|
+
if sig.Results() != nil {
|
|
1379
|
+
for k := 0; k < sig.Results().Len(); k++ {
|
|
1380
|
+
result := sig.Results().At(k)
|
|
1381
|
+
a.collectPackageFromType(result.Type(), pkg.Types, packagesToAdd)
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Add collected packages to imports (both regular and synthetic)
|
|
1389
|
+
// Always add to SyntheticImports - the file compiler will check if
|
|
1390
|
+
// the import was already written from the AST to avoid duplicates
|
|
1391
|
+
for pkgName, pkgObj := range packagesToAdd {
|
|
1392
|
+
tsImportPath := "@goscript/" + pkgObj.Path()
|
|
1393
|
+
fileImp := &fileImport{
|
|
1394
|
+
importPath: tsImportPath,
|
|
1395
|
+
importVars: make(map[string]struct{}),
|
|
1396
|
+
}
|
|
1397
|
+
// Add to SyntheticImports unconditionally
|
|
1398
|
+
a.SyntheticImports[pkgName] = fileImp
|
|
1399
|
+
// Also add to Imports if not already present
|
|
1400
|
+
if _, exists := a.Imports[pkgName]; !exists {
|
|
1401
|
+
a.Imports[pkgName] = fileImp
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// collectPackageFromType recursively collects packages referenced by a type.
|
|
1407
|
+
func (a *Analysis) collectPackageFromType(t types.Type, currentPkg *types.Package, packagesToAdd map[string]*types.Package) {
|
|
1408
|
+
switch typ := t.(type) {
|
|
1409
|
+
case *types.Named:
|
|
1410
|
+
pkg := typ.Obj().Pkg()
|
|
1411
|
+
if pkg != nil && pkg != currentPkg {
|
|
1412
|
+
packagesToAdd[pkg.Name()] = pkg
|
|
1413
|
+
}
|
|
1414
|
+
// Check type arguments for generics
|
|
1415
|
+
if typ.TypeArgs() != nil {
|
|
1416
|
+
for i := 0; i < typ.TypeArgs().Len(); i++ {
|
|
1417
|
+
a.collectPackageFromType(typ.TypeArgs().At(i), currentPkg, packagesToAdd)
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
case *types.Interface:
|
|
1421
|
+
// For interfaces, we need to check embedded interfaces and method signatures
|
|
1422
|
+
for i := 0; i < typ.NumEmbeddeds(); i++ {
|
|
1423
|
+
a.collectPackageFromType(typ.EmbeddedType(i), currentPkg, packagesToAdd)
|
|
1424
|
+
}
|
|
1425
|
+
for i := 0; i < typ.NumExplicitMethods(); i++ {
|
|
1426
|
+
method := typ.ExplicitMethod(i)
|
|
1427
|
+
a.collectPackageFromType(method.Type(), currentPkg, packagesToAdd)
|
|
1428
|
+
}
|
|
1429
|
+
case *types.Pointer:
|
|
1430
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1431
|
+
case *types.Slice:
|
|
1432
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1433
|
+
case *types.Array:
|
|
1434
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1435
|
+
case *types.Map:
|
|
1436
|
+
a.collectPackageFromType(typ.Key(), currentPkg, packagesToAdd)
|
|
1437
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1438
|
+
case *types.Chan:
|
|
1439
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1440
|
+
case *types.Signature:
|
|
1441
|
+
// Collect from parameters
|
|
1442
|
+
if typ.Params() != nil {
|
|
1443
|
+
for i := 0; i < typ.Params().Len(); i++ {
|
|
1444
|
+
a.collectPackageFromType(typ.Params().At(i).Type(), currentPkg, packagesToAdd)
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
// Collect from results
|
|
1448
|
+
if typ.Results() != nil {
|
|
1449
|
+
for i := 0; i < typ.Results().Len(); i++ {
|
|
1450
|
+
a.collectPackageFromType(typ.Results().At(i).Type(), currentPkg, packagesToAdd)
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1227
1456
|
// AnalyzePackageImports performs package-level analysis to collect function definitions
|
|
1228
1457
|
// and calls across all files in the package for auto-import generation
|
|
1229
1458
|
func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
@@ -1235,19 +1464,50 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1235
1464
|
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1236
1465
|
|
|
1237
1466
|
var functions []string
|
|
1238
|
-
var
|
|
1467
|
+
var typeNames []string
|
|
1468
|
+
var variables []string
|
|
1239
1469
|
for _, decl := range syntax.Decls {
|
|
1240
1470
|
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
1241
|
-
//
|
|
1471
|
+
// Collect top-level functions (not methods)
|
|
1242
1472
|
if funcDecl.Recv == nil {
|
|
1243
1473
|
functions = append(functions, funcDecl.Name.Name)
|
|
1474
|
+
} else {
|
|
1475
|
+
// Check if this is a method on a wrapper type (named basic type)
|
|
1476
|
+
// If so, it will be compiled as TypeName_MethodName function
|
|
1477
|
+
if len(funcDecl.Recv.List) > 0 {
|
|
1478
|
+
recvType := funcDecl.Recv.List[0].Type
|
|
1479
|
+
// Handle pointer receiver (*Type)
|
|
1480
|
+
if starExpr, ok := recvType.(*ast.StarExpr); ok {
|
|
1481
|
+
recvType = starExpr.X
|
|
1482
|
+
}
|
|
1483
|
+
if recvIdent, ok := recvType.(*ast.Ident); ok {
|
|
1484
|
+
// Check if this receiver type is a wrapper type
|
|
1485
|
+
if obj := pkg.TypesInfo.Uses[recvIdent]; obj != nil {
|
|
1486
|
+
if typeName, ok := obj.(*types.TypeName); ok {
|
|
1487
|
+
if namedType, ok := typeName.Type().(*types.Named); ok {
|
|
1488
|
+
if _, ok := namedType.Underlying().(*types.Basic); ok {
|
|
1489
|
+
// This is a method on a wrapper type
|
|
1490
|
+
funcName := recvIdent.Name + "_" + funcDecl.Name.Name
|
|
1491
|
+
functions = append(functions, funcName)
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1244
1498
|
}
|
|
1245
1499
|
}
|
|
1246
1500
|
if genDecl, ok := decl.(*ast.GenDecl); ok {
|
|
1247
1501
|
// Collect type declarations
|
|
1248
1502
|
for _, spec := range genDecl.Specs {
|
|
1249
1503
|
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
1250
|
-
|
|
1504
|
+
typeNames = append(typeNames, typeSpec.Name.Name)
|
|
1505
|
+
}
|
|
1506
|
+
// Collect variable/constant declarations
|
|
1507
|
+
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
1508
|
+
for _, name := range valueSpec.Names {
|
|
1509
|
+
variables = append(variables, name.Name)
|
|
1510
|
+
}
|
|
1251
1511
|
}
|
|
1252
1512
|
}
|
|
1253
1513
|
}
|
|
@@ -1256,8 +1516,11 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1256
1516
|
if len(functions) > 0 {
|
|
1257
1517
|
analysis.FunctionDefs[baseFileName] = functions
|
|
1258
1518
|
}
|
|
1259
|
-
if len(
|
|
1260
|
-
analysis.TypeDefs[baseFileName] =
|
|
1519
|
+
if len(typeNames) > 0 {
|
|
1520
|
+
analysis.TypeDefs[baseFileName] = typeNames
|
|
1521
|
+
}
|
|
1522
|
+
if len(variables) > 0 {
|
|
1523
|
+
analysis.VariableDefs[baseFileName] = variables
|
|
1261
1524
|
}
|
|
1262
1525
|
}
|
|
1263
1526
|
|
|
@@ -1276,13 +1539,7 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1276
1539
|
|
|
1277
1540
|
// Check if this function is defined in the current file
|
|
1278
1541
|
currentFileFuncs := analysis.FunctionDefs[baseFileName]
|
|
1279
|
-
isDefinedInCurrentFile :=
|
|
1280
|
-
for _, f := range currentFileFuncs {
|
|
1281
|
-
if f == funcName {
|
|
1282
|
-
isDefinedInCurrentFile = true
|
|
1283
|
-
break
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1542
|
+
isDefinedInCurrentFile := slices.Contains(currentFileFuncs, funcName)
|
|
1286
1543
|
|
|
1287
1544
|
// If not defined in current file, find which file defines it
|
|
1288
1545
|
if !isDefinedInCurrentFile {
|
|
@@ -1290,24 +1547,15 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1290
1547
|
if sourceFile == baseFileName {
|
|
1291
1548
|
continue // Skip current file
|
|
1292
1549
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
if existing == funcName {
|
|
1303
|
-
found = true
|
|
1304
|
-
break
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
if !found {
|
|
1308
|
-
callsFromOtherFiles[sourceFile] = append(callsFromOtherFiles[sourceFile], funcName)
|
|
1309
|
-
}
|
|
1310
|
-
break
|
|
1550
|
+
if slices.Contains(funcs, funcName) {
|
|
1551
|
+
// Found the function in another file
|
|
1552
|
+
if callsFromOtherFiles[sourceFile] == nil {
|
|
1553
|
+
callsFromOtherFiles[sourceFile] = []string{}
|
|
1554
|
+
}
|
|
1555
|
+
// Check if already added to avoid duplicates
|
|
1556
|
+
found := slices.Contains(callsFromOtherFiles[sourceFile], funcName)
|
|
1557
|
+
if !found {
|
|
1558
|
+
callsFromOtherFiles[sourceFile] = append(callsFromOtherFiles[sourceFile], funcName)
|
|
1311
1559
|
}
|
|
1312
1560
|
}
|
|
1313
1561
|
}
|
|
@@ -1340,13 +1588,7 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1340
1588
|
|
|
1341
1589
|
// Check if this type is defined in the current file
|
|
1342
1590
|
currentFileTypes := analysis.TypeDefs[baseFileName]
|
|
1343
|
-
isDefinedInCurrentFile :=
|
|
1344
|
-
for _, t := range currentFileTypes {
|
|
1345
|
-
if t == typeName {
|
|
1346
|
-
isDefinedInCurrentFile = true
|
|
1347
|
-
break
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1591
|
+
isDefinedInCurrentFile := slices.Contains(currentFileTypes, typeName)
|
|
1350
1592
|
|
|
1351
1593
|
// If not defined in current file, find which file defines it
|
|
1352
1594
|
if !isDefinedInCurrentFile {
|
|
@@ -1354,24 +1596,99 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1354
1596
|
if sourceFile == baseFileName {
|
|
1355
1597
|
continue // Skip current file
|
|
1356
1598
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1599
|
+
if slices.Contains(types, typeName) {
|
|
1600
|
+
// Found the type in another file
|
|
1601
|
+
if typeRefsFromOtherFiles[sourceFile] == nil {
|
|
1602
|
+
typeRefsFromOtherFiles[sourceFile] = []string{}
|
|
1603
|
+
}
|
|
1604
|
+
// Check if already added to avoid duplicates
|
|
1605
|
+
found := slices.Contains(typeRefsFromOtherFiles[sourceFile], typeName)
|
|
1606
|
+
if !found {
|
|
1607
|
+
typeRefsFromOtherFiles[sourceFile] = append(typeRefsFromOtherFiles[sourceFile], typeName)
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
return true
|
|
1616
|
+
})
|
|
1617
|
+
|
|
1618
|
+
if len(typeRefsFromOtherFiles) > 0 {
|
|
1619
|
+
analysis.TypeCalls[baseFileName] = typeRefsFromOtherFiles
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Fourth pass: analyze variable references and determine which need imports
|
|
1624
|
+
for i, syntax := range pkg.Syntax {
|
|
1625
|
+
fileName := pkg.CompiledGoFiles[i]
|
|
1626
|
+
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1627
|
+
|
|
1628
|
+
// Find all variable references in this file
|
|
1629
|
+
varRefsFromOtherFiles := make(map[string][]string)
|
|
1630
|
+
|
|
1631
|
+
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1632
|
+
// Look for identifier references
|
|
1633
|
+
if ident, ok := n.(*ast.Ident); ok {
|
|
1634
|
+
// Check if this identifier refers to a package-level variable
|
|
1635
|
+
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
1636
|
+
if varObj, ok := obj.(*types.Var); ok {
|
|
1637
|
+
// Only track package-level variables (not function parameters or local vars)
|
|
1638
|
+
if varObj.Parent() == pkg.Types.Scope() {
|
|
1639
|
+
varName := ident.Name
|
|
1640
|
+
|
|
1641
|
+
// Check if this variable is defined in the current file
|
|
1642
|
+
currentFileVars := analysis.VariableDefs[baseFileName]
|
|
1643
|
+
isDefinedInCurrentFile := slices.Contains(currentFileVars, varName)
|
|
1644
|
+
|
|
1645
|
+
// If not defined in current file, find which file defines it
|
|
1646
|
+
if !isDefinedInCurrentFile {
|
|
1647
|
+
for sourceFile, vars := range analysis.VariableDefs {
|
|
1648
|
+
if sourceFile == baseFileName {
|
|
1649
|
+
continue // Skip current file
|
|
1650
|
+
}
|
|
1651
|
+
if slices.Contains(vars, varName) {
|
|
1652
|
+
// Found the variable in another file
|
|
1653
|
+
if varRefsFromOtherFiles[sourceFile] == nil {
|
|
1654
|
+
varRefsFromOtherFiles[sourceFile] = []string{}
|
|
1362
1655
|
}
|
|
1363
1656
|
// Check if already added to avoid duplicates
|
|
1364
|
-
found :=
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1657
|
+
found := slices.Contains(varRefsFromOtherFiles[sourceFile], varName)
|
|
1658
|
+
if !found {
|
|
1659
|
+
varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], varName)
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
// Also check for constants
|
|
1667
|
+
if constObj, ok := obj.(*types.Const); ok {
|
|
1668
|
+
// Only track package-level constants
|
|
1669
|
+
if constObj.Parent() == pkg.Types.Scope() {
|
|
1670
|
+
constName := ident.Name
|
|
1671
|
+
|
|
1672
|
+
// Check if this constant is defined in the current file
|
|
1673
|
+
currentFileVars := analysis.VariableDefs[baseFileName]
|
|
1674
|
+
isDefinedInCurrentFile := slices.Contains(currentFileVars, constName)
|
|
1675
|
+
|
|
1676
|
+
// If not defined in current file, find which file defines it
|
|
1677
|
+
if !isDefinedInCurrentFile {
|
|
1678
|
+
for sourceFile, vars := range analysis.VariableDefs {
|
|
1679
|
+
if sourceFile == baseFileName {
|
|
1680
|
+
continue // Skip current file
|
|
1681
|
+
}
|
|
1682
|
+
if slices.Contains(vars, constName) {
|
|
1683
|
+
// Found the constant in another file
|
|
1684
|
+
if varRefsFromOtherFiles[sourceFile] == nil {
|
|
1685
|
+
varRefsFromOtherFiles[sourceFile] = []string{}
|
|
1370
1686
|
}
|
|
1687
|
+
// Check if already added to avoid duplicates
|
|
1688
|
+
found := slices.Contains(varRefsFromOtherFiles[sourceFile], constName)
|
|
1371
1689
|
if !found {
|
|
1372
|
-
|
|
1690
|
+
varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], constName)
|
|
1373
1691
|
}
|
|
1374
|
-
break
|
|
1375
1692
|
}
|
|
1376
1693
|
}
|
|
1377
1694
|
}
|
|
@@ -1382,11 +1699,93 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1382
1699
|
return true
|
|
1383
1700
|
})
|
|
1384
1701
|
|
|
1385
|
-
if len(
|
|
1386
|
-
analysis.
|
|
1702
|
+
if len(varRefsFromOtherFiles) > 0 {
|
|
1703
|
+
analysis.VariableCalls[baseFileName] = varRefsFromOtherFiles
|
|
1387
1704
|
}
|
|
1388
1705
|
}
|
|
1389
1706
|
|
|
1707
|
+
// Fifth pass: analyze method calls on wrapper types (named basic types with methods)
|
|
1708
|
+
// These generate TypeName_MethodName function calls that need to be imported
|
|
1709
|
+
for i, syntax := range pkg.Syntax {
|
|
1710
|
+
fileName := pkg.CompiledGoFiles[i]
|
|
1711
|
+
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1712
|
+
|
|
1713
|
+
// Find all method calls on wrapper types in this file
|
|
1714
|
+
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1715
|
+
callExpr, ok := n.(*ast.CallExpr)
|
|
1716
|
+
if !ok {
|
|
1717
|
+
return true
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// Check if this is a method call (selector expression)
|
|
1721
|
+
selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
1722
|
+
if !ok {
|
|
1723
|
+
return true
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// Get the type of the receiver
|
|
1727
|
+
receiverType := pkg.TypesInfo.TypeOf(selectorExpr.X)
|
|
1728
|
+
if receiverType == nil {
|
|
1729
|
+
return true
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// Check if this is a wrapper type (named type with basic underlying type and methods)
|
|
1733
|
+
namedType, ok := receiverType.(*types.Named)
|
|
1734
|
+
if !ok {
|
|
1735
|
+
return true
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// Check if it has a basic underlying type
|
|
1739
|
+
if _, ok := namedType.Underlying().(*types.Basic); !ok {
|
|
1740
|
+
return true
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// Check if this type is defined in the same package
|
|
1744
|
+
obj := namedType.Obj()
|
|
1745
|
+
if obj == nil || obj.Pkg() == nil || obj.Pkg() != pkg.Types {
|
|
1746
|
+
return true // Not from this package
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// Check if this type has the method being called
|
|
1750
|
+
methodName := selectorExpr.Sel.Name
|
|
1751
|
+
found := false
|
|
1752
|
+
for j := 0; j < namedType.NumMethods(); j++ {
|
|
1753
|
+
if namedType.Method(j).Name() == methodName {
|
|
1754
|
+
found = true
|
|
1755
|
+
break
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
if !found {
|
|
1759
|
+
return true
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// Generate the function name: TypeName_MethodName
|
|
1763
|
+
funcName := obj.Name() + "_" + methodName
|
|
1764
|
+
|
|
1765
|
+
// Find which file defines this function
|
|
1766
|
+
for sourceFile, funcs := range analysis.FunctionDefs {
|
|
1767
|
+
if sourceFile == baseFileName {
|
|
1768
|
+
continue // Skip current file
|
|
1769
|
+
}
|
|
1770
|
+
if slices.Contains(funcs, funcName) {
|
|
1771
|
+
// Found the function in another file
|
|
1772
|
+
if analysis.FunctionCalls[baseFileName] == nil {
|
|
1773
|
+
analysis.FunctionCalls[baseFileName] = make(map[string][]string)
|
|
1774
|
+
}
|
|
1775
|
+
if analysis.FunctionCalls[baseFileName][sourceFile] == nil {
|
|
1776
|
+
analysis.FunctionCalls[baseFileName][sourceFile] = []string{}
|
|
1777
|
+
}
|
|
1778
|
+
// Check if already added to avoid duplicates
|
|
1779
|
+
if !slices.Contains(analysis.FunctionCalls[baseFileName][sourceFile], funcName) {
|
|
1780
|
+
analysis.FunctionCalls[baseFileName][sourceFile] = append(analysis.FunctionCalls[baseFileName][sourceFile], funcName)
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
return true
|
|
1786
|
+
})
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1390
1789
|
return analysis
|
|
1391
1790
|
}
|
|
1392
1791
|
|
|
@@ -1590,6 +1989,7 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
|
|
|
1590
1989
|
shadowingInfo := &ShadowingInfo{
|
|
1591
1990
|
ShadowedVariables: make(map[string]types.Object),
|
|
1592
1991
|
TempVariables: make(map[string]string),
|
|
1992
|
+
TypeShadowedVars: make(map[string]string),
|
|
1593
1993
|
}
|
|
1594
1994
|
|
|
1595
1995
|
hasShadowing := false
|
|
@@ -1607,12 +2007,60 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
|
|
|
1607
2007
|
v.findVariableUsageInExpr(rhsExpr, lhsVarNames, shadowingInfo, &hasShadowing)
|
|
1608
2008
|
}
|
|
1609
2009
|
|
|
2010
|
+
// Check for type shadowing: variable name matches a type name used in its initialization
|
|
2011
|
+
// e.g., field := field{...} where the variable 'field' shadows the type 'field'
|
|
2012
|
+
if assignStmt.Tok == token.DEFINE {
|
|
2013
|
+
for i, lhsExpr := range assignStmt.Lhs {
|
|
2014
|
+
if i < len(assignStmt.Rhs) {
|
|
2015
|
+
if lhsIdent, ok := lhsExpr.(*ast.Ident); ok && lhsIdent.Name != "_" {
|
|
2016
|
+
if typeName := v.findTypeShadowing(lhsIdent.Name, assignStmt.Rhs[i]); typeName != "" {
|
|
2017
|
+
shadowingInfo.TypeShadowedVars[lhsIdent.Name] = lhsIdent.Name + "_"
|
|
2018
|
+
hasShadowing = true
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1610
2025
|
if hasShadowing {
|
|
1611
2026
|
return shadowingInfo
|
|
1612
2027
|
}
|
|
1613
2028
|
return nil
|
|
1614
2029
|
}
|
|
1615
2030
|
|
|
2031
|
+
// findTypeShadowing checks if the given variable name matches a type name used in the RHS expression.
|
|
2032
|
+
// Returns the type name if shadowing is detected, empty string otherwise.
|
|
2033
|
+
func (v *analysisVisitor) findTypeShadowing(varName string, rhsExpr ast.Expr) string {
|
|
2034
|
+
// Handle address-of expressions: field := &field{...}
|
|
2035
|
+
if unary, ok := rhsExpr.(*ast.UnaryExpr); ok && unary.Op == token.AND {
|
|
2036
|
+
rhsExpr = unary.X
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// Check if RHS is a composite literal with a type name matching varName
|
|
2040
|
+
compLit, ok := rhsExpr.(*ast.CompositeLit)
|
|
2041
|
+
if !ok {
|
|
2042
|
+
return ""
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
// Get the type name from the composite literal
|
|
2046
|
+
var typeName string
|
|
2047
|
+
switch t := compLit.Type.(type) {
|
|
2048
|
+
case *ast.Ident:
|
|
2049
|
+
typeName = t.Name
|
|
2050
|
+
case *ast.SelectorExpr:
|
|
2051
|
+
// pkg.Type - just use the type name part
|
|
2052
|
+
typeName = t.Sel.Name
|
|
2053
|
+
default:
|
|
2054
|
+
return ""
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// Check if variable name matches type name
|
|
2058
|
+
if typeName == varName {
|
|
2059
|
+
return typeName
|
|
2060
|
+
}
|
|
2061
|
+
return ""
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1616
2064
|
// findVariableUsageInExpr recursively searches for variable usage in an expression
|
|
1617
2065
|
func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map[string]*ast.Ident, shadowingInfo *ShadowingInfo, hasShadowing *bool) {
|
|
1618
2066
|
if expr == nil {
|
|
@@ -1695,16 +2143,15 @@ func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map
|
|
|
1695
2143
|
}
|
|
1696
2144
|
|
|
1697
2145
|
// trackInterfaceImplementation records that a struct type implements an interface method
|
|
1698
|
-
func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func
|
|
2146
|
+
func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func) {
|
|
1699
2147
|
key := InterfaceMethodKey{
|
|
1700
2148
|
InterfaceType: interfaceType.String(),
|
|
1701
2149
|
MethodName: method.Name(),
|
|
1702
2150
|
}
|
|
1703
2151
|
|
|
1704
2152
|
implementation := ImplementationInfo{
|
|
1705
|
-
StructType:
|
|
1706
|
-
Method:
|
|
1707
|
-
IsAsyncByFlow: isAsync,
|
|
2153
|
+
StructType: structType,
|
|
2154
|
+
Method: method,
|
|
1708
2155
|
}
|
|
1709
2156
|
|
|
1710
2157
|
a.InterfaceImplementations[key] = append(a.InterfaceImplementations[key], implementation)
|
|
@@ -1717,49 +2164,25 @@ func (a *Analysis) IsInterfaceMethodAsync(interfaceType *types.Interface, method
|
|
|
1717
2164
|
MethodName: methodName,
|
|
1718
2165
|
}
|
|
1719
2166
|
|
|
1720
|
-
// Check if we've already computed this
|
|
1721
|
-
if result, exists := a.InterfaceMethodAsyncStatus[key]; exists {
|
|
1722
|
-
return result
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
2167
|
// Find all implementations of this interface method
|
|
1726
2168
|
implementations, exists := a.InterfaceImplementations[key]
|
|
1727
2169
|
if !exists {
|
|
1728
|
-
// No implementations found, default to sync
|
|
1729
|
-
a.InterfaceMethodAsyncStatus[key] = false
|
|
1730
2170
|
return false
|
|
1731
2171
|
}
|
|
1732
2172
|
|
|
1733
|
-
//
|
|
1734
|
-
for
|
|
1735
|
-
impl := &implementations[i]
|
|
1736
|
-
|
|
1737
|
-
// Create method key for this implementation
|
|
2173
|
+
// If ANY implementation is async, the interface method is async
|
|
2174
|
+
for _, impl := range implementations {
|
|
1738
2175
|
methodKey := MethodKey{
|
|
1739
2176
|
PackagePath: impl.StructType.Obj().Pkg().Path(),
|
|
1740
2177
|
ReceiverType: impl.StructType.Obj().Name(),
|
|
1741
2178
|
MethodName: impl.Method.Name(),
|
|
1742
2179
|
}
|
|
1743
2180
|
|
|
1744
|
-
|
|
1745
|
-
if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists {
|
|
1746
|
-
impl.IsAsyncByFlow = isAsync
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
// Store the updated implementations back to the map
|
|
1751
|
-
a.InterfaceImplementations[key] = implementations
|
|
1752
|
-
|
|
1753
|
-
// If ANY implementation is async, the interface method is async
|
|
1754
|
-
for _, impl := range implementations {
|
|
1755
|
-
if impl.IsAsyncByFlow {
|
|
1756
|
-
a.InterfaceMethodAsyncStatus[key] = true
|
|
2181
|
+
if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists && isAsync {
|
|
1757
2182
|
return true
|
|
1758
2183
|
}
|
|
1759
2184
|
}
|
|
1760
2185
|
|
|
1761
|
-
// All implementations are sync
|
|
1762
|
-
a.InterfaceMethodAsyncStatus[key] = false
|
|
1763
2186
|
return false
|
|
1764
2187
|
}
|
|
1765
2188
|
|
|
@@ -1844,56 +2267,6 @@ func (a *Analysis) GetIdentifierMapping(ident *ast.Ident) string {
|
|
|
1844
2267
|
return ""
|
|
1845
2268
|
}
|
|
1846
2269
|
|
|
1847
|
-
// trackTypeAssertion analyzes type assertions and records interface implementations
|
|
1848
|
-
func (v *analysisVisitor) trackTypeAssertion(typeAssert *ast.TypeAssertExpr) {
|
|
1849
|
-
// Get the type being asserted to
|
|
1850
|
-
assertedType := v.pkg.TypesInfo.TypeOf(typeAssert.Type)
|
|
1851
|
-
if assertedType == nil {
|
|
1852
|
-
return
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// Check if the asserted type is an interface
|
|
1856
|
-
interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
|
|
1857
|
-
if !isInterface {
|
|
1858
|
-
return
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
// Get the type of the expression being asserted
|
|
1862
|
-
exprType := v.pkg.TypesInfo.TypeOf(typeAssert.X)
|
|
1863
|
-
if exprType == nil {
|
|
1864
|
-
return
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
// Handle pointer types by getting the element type
|
|
1868
|
-
if ptrType, isPtr := exprType.(*types.Pointer); isPtr {
|
|
1869
|
-
exprType = ptrType.Elem()
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
// Check if the expression type is a named struct type
|
|
1873
|
-
namedType, isNamed := exprType.(*types.Named)
|
|
1874
|
-
if !isNamed {
|
|
1875
|
-
return
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
// For each method in the interface, check if the struct implements it
|
|
1879
|
-
for i := 0; i < interfaceType.NumExplicitMethods(); i++ {
|
|
1880
|
-
interfaceMethod := interfaceType.ExplicitMethod(i)
|
|
1881
|
-
|
|
1882
|
-
// Find the corresponding method in the struct type
|
|
1883
|
-
structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
|
|
1884
|
-
if structMethod != nil {
|
|
1885
|
-
// Determine if this struct method is async using unified system
|
|
1886
|
-
isAsync := false
|
|
1887
|
-
if obj := structMethod; obj != nil {
|
|
1888
|
-
isAsync = v.analysis.IsAsyncFunc(obj)
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
// Track this interface implementation
|
|
1892
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
2270
|
// findStructMethod finds a method with the given name on a named type
|
|
1898
2271
|
func (v *analysisVisitor) findStructMethod(namedType *types.Named, methodName string) *types.Func {
|
|
1899
2272
|
// Check methods directly on the type
|
|
@@ -2006,10 +2379,7 @@ func (v *analysisVisitor) trackInterfaceAssignments(assignStmt *ast.AssignStmt)
|
|
|
2006
2379
|
|
|
2007
2380
|
structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
|
|
2008
2381
|
if structMethod != nil {
|
|
2009
|
-
|
|
2010
|
-
isAsync := v.analysis.IsAsyncFunc(structMethod)
|
|
2011
|
-
|
|
2012
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
|
|
2382
|
+
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
2013
2383
|
}
|
|
2014
2384
|
}
|
|
2015
2385
|
}
|
|
@@ -2068,9 +2438,7 @@ func (v *analysisVisitor) trackInterfaceCallArguments(callExpr *ast.CallExpr) {
|
|
|
2068
2438
|
|
|
2069
2439
|
structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
|
|
2070
2440
|
if structMethod != nil {
|
|
2071
|
-
|
|
2072
|
-
// For now, just track the implementation relationship without async status
|
|
2073
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, false)
|
|
2441
|
+
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
2074
2442
|
}
|
|
2075
2443
|
}
|
|
2076
2444
|
}
|
|
@@ -2216,10 +2584,7 @@ func (v *interfaceImplementationVisitor) trackImplementation(interfaceType *type
|
|
|
2216
2584
|
// Find the method in the implementing type
|
|
2217
2585
|
structMethod := v.findMethodInType(namedType, interfaceMethod.Name())
|
|
2218
2586
|
if structMethod != nil {
|
|
2219
|
-
|
|
2220
|
-
isAsync := v.analysis.IsAsyncFunc(structMethod)
|
|
2221
|
-
|
|
2222
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod, isAsync)
|
|
2587
|
+
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
2223
2588
|
}
|
|
2224
2589
|
}
|
|
2225
2590
|
}
|
|
@@ -2235,93 +2600,106 @@ func (v *interfaceImplementationVisitor) findMethodInType(namedType *types.Named
|
|
|
2235
2600
|
return nil
|
|
2236
2601
|
}
|
|
2237
2602
|
|
|
2238
|
-
//
|
|
2239
|
-
func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
|
|
2240
|
-
var namedReturns []string
|
|
2241
|
-
if funcDecl.Type != nil && funcDecl.Type.Results != nil {
|
|
2242
|
-
for _, field := range funcDecl.Type.Results.List {
|
|
2243
|
-
for _, name := range field.Names {
|
|
2244
|
-
namedReturns = append(namedReturns, name.Name)
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
return namedReturns
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
// analyzeAllMethodsAsync performs comprehensive async analysis on all methods in all packages
|
|
2603
|
+
// analyzeAllMethodsAsync performs comprehensive async analysis on all methods in all packages using topological sort
|
|
2252
2604
|
func (v *analysisVisitor) analyzeAllMethodsAsync() {
|
|
2253
|
-
//
|
|
2254
|
-
v.
|
|
2255
|
-
|
|
2256
|
-
//
|
|
2257
|
-
|
|
2605
|
+
// Build the method call graph for all packages
|
|
2606
|
+
methodCalls := v.buildMethodCallGraph()
|
|
2607
|
+
|
|
2608
|
+
// Topologically sort methods by their dependencies
|
|
2609
|
+
sorted, cycles := v.topologicalSortMethods(methodCalls)
|
|
2610
|
+
|
|
2611
|
+
// Mark methods in cycles - check if they contain async operations
|
|
2612
|
+
// We can't rely on the call graph for methods in cycles (circular dependency),
|
|
2613
|
+
// but we can still detect if they call async external methods
|
|
2614
|
+
//
|
|
2615
|
+
// We need to iterate multiple times because methods in cycles can call each other,
|
|
2616
|
+
// and we need to propagate async status until no changes occur
|
|
2258
2617
|
maxIterations := 10
|
|
2259
2618
|
for iteration := 0; iteration < maxIterations; iteration++ {
|
|
2260
|
-
// Clear visitingMethods map for this iteration
|
|
2261
|
-
v.visitingMethods = make(map[MethodKey]bool)
|
|
2262
|
-
|
|
2263
|
-
// Track if anything changed in this iteration
|
|
2264
2619
|
changed := false
|
|
2265
2620
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2621
|
+
for _, methodKey := range cycles {
|
|
2622
|
+
// For methods in cycles, we need to check their body directly for async operations
|
|
2623
|
+
pkg := v.analysis.AllPackages[methodKey.PackagePath]
|
|
2624
|
+
if pkg == nil && methodKey.PackagePath == v.pkg.Types.Path() {
|
|
2625
|
+
pkg = v.pkg
|
|
2626
|
+
}
|
|
2271
2627
|
|
|
2272
|
-
|
|
2273
|
-
|
|
2628
|
+
if pkg != nil {
|
|
2629
|
+
var funcDecl *ast.FuncDecl
|
|
2630
|
+
if methodKey.ReceiverType == "" {
|
|
2631
|
+
funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
|
|
2632
|
+
} else {
|
|
2633
|
+
funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
|
|
2634
|
+
}
|
|
2274
2635
|
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2636
|
+
if funcDecl != nil && funcDecl.Body != nil {
|
|
2637
|
+
// Check if the method contains async operations (including calls to async external methods)
|
|
2638
|
+
isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
|
|
2639
|
+
|
|
2640
|
+
// Check if status changed
|
|
2641
|
+
if oldStatus, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists || oldStatus != isAsync {
|
|
2642
|
+
changed = true
|
|
2643
|
+
}
|
|
2281
2644
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
oldValue, existed := previousState[k]
|
|
2285
|
-
if !existed {
|
|
2286
|
-
// New method added - check if it's async (if sync, no need to re-analyze dependents)
|
|
2287
|
-
if newValue {
|
|
2288
|
-
changed = true
|
|
2645
|
+
v.analysis.MethodAsyncStatus[methodKey] = isAsync
|
|
2646
|
+
continue
|
|
2289
2647
|
}
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
// Fallback: mark as sync if we can't analyze the body
|
|
2651
|
+
if _, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists {
|
|
2652
|
+
v.analysis.MethodAsyncStatus[methodKey] = false
|
|
2292
2653
|
changed = true
|
|
2293
2654
|
}
|
|
2294
2655
|
}
|
|
2295
2656
|
|
|
2296
|
-
// If
|
|
2657
|
+
// If no changes in this iteration, we're done
|
|
2297
2658
|
if !changed {
|
|
2298
2659
|
break
|
|
2299
2660
|
}
|
|
2300
2661
|
}
|
|
2301
2662
|
|
|
2663
|
+
// Analyze methods in dependency order (dependencies analyzed before dependents)
|
|
2664
|
+
for _, methodKey := range sorted {
|
|
2665
|
+
v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
// Track async-returning variables BEFORE analyzing function literals
|
|
2669
|
+
// This detects variables assigned from higher-order functions with async function literal args
|
|
2670
|
+
// e.g., indirect := sync.OnceValue(asyncFunc)
|
|
2671
|
+
// This must happen first so that function literals containing calls to these variables
|
|
2672
|
+
// will be correctly identified as async.
|
|
2673
|
+
v.trackAsyncReturningVarsAllFiles()
|
|
2674
|
+
|
|
2302
2675
|
// Finally, analyze function literals in the current package only
|
|
2303
2676
|
// (external packages' function literals are not accessible)
|
|
2677
|
+
// This must run AFTER trackAsyncReturningVarsAllFiles so that function literals
|
|
2678
|
+
// containing calls to async-returning variables are correctly marked as async.
|
|
2304
2679
|
v.analyzeFunctionLiteralsAsync(v.pkg)
|
|
2305
2680
|
}
|
|
2306
2681
|
|
|
2307
|
-
//
|
|
2308
|
-
func (v *analysisVisitor)
|
|
2309
|
-
// Analyze function declarations
|
|
2682
|
+
// buildMethodCallGraph builds a graph of which methods call which other methods
|
|
2683
|
+
func (v *analysisVisitor) analyzeFunctionLiteralsAsync(pkg *packages.Package) {
|
|
2310
2684
|
for _, file := range pkg.Syntax {
|
|
2311
|
-
|
|
2312
|
-
if
|
|
2313
|
-
v.
|
|
2685
|
+
ast.Inspect(file, func(n ast.Node) bool {
|
|
2686
|
+
if funcLit, ok := n.(*ast.FuncLit); ok {
|
|
2687
|
+
v.analyzeFunctionLiteralAsync(funcLit, pkg)
|
|
2314
2688
|
}
|
|
2315
|
-
|
|
2689
|
+
return true
|
|
2690
|
+
})
|
|
2316
2691
|
}
|
|
2317
2692
|
}
|
|
2318
2693
|
|
|
2319
|
-
//
|
|
2320
|
-
|
|
2321
|
-
|
|
2694
|
+
// trackAsyncReturningVarsAllFiles scans all files for assignment statements
|
|
2695
|
+
// and marks variables that are assigned from higher-order functions with async function literal args
|
|
2696
|
+
func (v *analysisVisitor) trackAsyncReturningVarsAllFiles() {
|
|
2697
|
+
for _, file := range v.pkg.Syntax {
|
|
2322
2698
|
ast.Inspect(file, func(n ast.Node) bool {
|
|
2323
|
-
if
|
|
2324
|
-
|
|
2699
|
+
if assignStmt, ok := n.(*ast.AssignStmt); ok {
|
|
2700
|
+
if len(assignStmt.Lhs) == 1 && len(assignStmt.Rhs) == 1 {
|
|
2701
|
+
v.trackAsyncReturningVar(assignStmt.Lhs[0], assignStmt.Rhs[0])
|
|
2702
|
+
}
|
|
2325
2703
|
}
|
|
2326
2704
|
return true
|
|
2327
2705
|
})
|
|
@@ -2350,55 +2728,320 @@ func (v *analysisVisitor) analyzeFunctionLiteralAsync(funcLit *ast.FuncLit, pkg
|
|
|
2350
2728
|
nodeInfo.InAsyncContext = isAsync
|
|
2351
2729
|
}
|
|
2352
2730
|
|
|
2353
|
-
//
|
|
2354
|
-
func (v *analysisVisitor)
|
|
2355
|
-
|
|
2731
|
+
// buildMethodCallGraph builds a graph of which methods call which other methods
|
|
2732
|
+
func (v *analysisVisitor) buildMethodCallGraph() map[MethodKey][]MethodKey {
|
|
2733
|
+
methodCalls := make(map[MethodKey][]MethodKey)
|
|
2356
2734
|
|
|
2357
|
-
//
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
v.
|
|
2361
|
-
|
|
2735
|
+
// Iterate through all packages
|
|
2736
|
+
allPkgs := []*packages.Package{v.pkg}
|
|
2737
|
+
for _, pkg := range v.analysis.AllPackages {
|
|
2738
|
+
if pkg != v.pkg {
|
|
2739
|
+
allPkgs = append(allPkgs, pkg)
|
|
2740
|
+
}
|
|
2362
2741
|
}
|
|
2363
2742
|
|
|
2364
|
-
|
|
2365
|
-
|
|
2743
|
+
for _, pkg := range allPkgs {
|
|
2744
|
+
for _, file := range pkg.Syntax {
|
|
2745
|
+
for _, decl := range file.Decls {
|
|
2746
|
+
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
2747
|
+
methodKey := v.getMethodKey(funcDecl, pkg)
|
|
2366
2748
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2749
|
+
// Initialize the entry for this method
|
|
2750
|
+
if _, exists := methodCalls[methodKey]; !exists {
|
|
2751
|
+
methodCalls[methodKey] = []MethodKey{}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// Extract method calls from the function body
|
|
2755
|
+
if funcDecl.Body != nil {
|
|
2756
|
+
callees := v.extractMethodCalls(funcDecl.Body, pkg)
|
|
2757
|
+
methodCalls[methodKey] = callees
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
return methodCalls
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// extractMethodCalls extracts all method and function calls from a node
|
|
2768
|
+
func (v *analysisVisitor) extractMethodCalls(node ast.Node, pkg *packages.Package) []MethodKey {
|
|
2769
|
+
var calls []MethodKey
|
|
2770
|
+
seen := make(map[MethodKey]bool)
|
|
2771
|
+
|
|
2772
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
2773
|
+
if n == nil {
|
|
2774
|
+
return false
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
if callExpr, ok := n.(*ast.CallExpr); ok {
|
|
2778
|
+
methodKeys := v.extractMethodKeysFromCall(callExpr, pkg)
|
|
2779
|
+
for _, methodKey := range methodKeys {
|
|
2780
|
+
if !seen[methodKey] {
|
|
2781
|
+
seen[methodKey] = true
|
|
2782
|
+
calls = append(calls, methodKey)
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
return true
|
|
2788
|
+
})
|
|
2789
|
+
|
|
2790
|
+
return calls
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// extractMethodKeysFromCall extracts MethodKeys from a call expression
|
|
2794
|
+
// Returns multiple keys for interface method calls (one for each implementation)
|
|
2795
|
+
func (v *analysisVisitor) extractMethodKeysFromCall(callExpr *ast.CallExpr, pkg *packages.Package) []MethodKey {
|
|
2796
|
+
singleKey := v.extractMethodKeyFromCall(callExpr, pkg)
|
|
2797
|
+
if singleKey != nil {
|
|
2798
|
+
return []MethodKey{*singleKey}
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
// Check if this is an interface method call
|
|
2802
|
+
if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
|
|
2803
|
+
if receiverType := pkg.TypesInfo.TypeOf(selExpr.X); receiverType != nil {
|
|
2804
|
+
if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
|
|
2805
|
+
methodName := selExpr.Sel.Name
|
|
2806
|
+
// Find all implementations of this interface method
|
|
2807
|
+
key := InterfaceMethodKey{
|
|
2808
|
+
InterfaceType: interfaceType.String(),
|
|
2809
|
+
MethodName: methodName,
|
|
2810
|
+
}
|
|
2811
|
+
if implementations, exists := v.analysis.InterfaceImplementations[key]; exists {
|
|
2812
|
+
var keys []MethodKey
|
|
2813
|
+
for _, impl := range implementations {
|
|
2814
|
+
keys = append(keys, MethodKey{
|
|
2815
|
+
PackagePath: impl.StructType.Obj().Pkg().Path(),
|
|
2816
|
+
ReceiverType: impl.StructType.Obj().Name(),
|
|
2817
|
+
MethodName: impl.Method.Name(),
|
|
2818
|
+
})
|
|
2819
|
+
}
|
|
2820
|
+
return keys
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
return nil
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// extractMethodKeyFromCall extracts a MethodKey from a call expression
|
|
2830
|
+
func (v *analysisVisitor) extractMethodKeyFromCall(callExpr *ast.CallExpr, pkg *packages.Package) *MethodKey {
|
|
2831
|
+
switch fun := callExpr.Fun.(type) {
|
|
2832
|
+
case *ast.Ident:
|
|
2833
|
+
// Direct function call
|
|
2834
|
+
if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
|
|
2835
|
+
if funcObj, ok := obj.(*types.Func); ok {
|
|
2836
|
+
pkgPath := pkg.Types.Path()
|
|
2837
|
+
if funcObj.Pkg() != nil {
|
|
2838
|
+
pkgPath = funcObj.Pkg().Path()
|
|
2839
|
+
}
|
|
2840
|
+
return &MethodKey{
|
|
2841
|
+
PackagePath: pkgPath,
|
|
2842
|
+
ReceiverType: "",
|
|
2843
|
+
MethodName: funcObj.Name(),
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
case *ast.SelectorExpr:
|
|
2849
|
+
// Package-level function call (e.g., time.Sleep)
|
|
2850
|
+
if ident, ok := fun.X.(*ast.Ident); ok {
|
|
2851
|
+
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
2852
|
+
if pkgName, isPkg := obj.(*types.PkgName); isPkg {
|
|
2853
|
+
return &MethodKey{
|
|
2854
|
+
PackagePath: pkgName.Imported().Path(),
|
|
2855
|
+
ReceiverType: "",
|
|
2856
|
+
MethodName: fun.Sel.Name,
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
// Check if this is an interface method call - if so, return nil
|
|
2863
|
+
// so extractMethodKeysFromCall can expand it to all implementations
|
|
2864
|
+
if receiverType := pkg.TypesInfo.TypeOf(fun.X); receiverType != nil {
|
|
2865
|
+
if _, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
|
|
2866
|
+
// This is an interface method call - return nil to let
|
|
2867
|
+
// extractMethodKeysFromCall handle expanding to implementations
|
|
2868
|
+
return nil
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
// Method call on concrete objects
|
|
2873
|
+
if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
|
|
2874
|
+
if methodObj := selection.Obj(); methodObj != nil {
|
|
2875
|
+
receiverType := ""
|
|
2876
|
+
methodPkgPath := ""
|
|
2877
|
+
|
|
2878
|
+
// Get receiver type
|
|
2879
|
+
switch x := fun.X.(type) {
|
|
2880
|
+
case *ast.Ident:
|
|
2881
|
+
if obj := pkg.TypesInfo.Uses[x]; obj != nil {
|
|
2882
|
+
if varObj, ok := obj.(*types.Var); ok {
|
|
2883
|
+
receiverType = v.getTypeName(varObj.Type())
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
case *ast.SelectorExpr:
|
|
2887
|
+
if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
|
|
2888
|
+
receiverType = v.getTypeName(typeExpr)
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
// Get method's package path
|
|
2893
|
+
if methodFunc, ok := methodObj.(*types.Func); ok {
|
|
2894
|
+
if methodFunc.Pkg() != nil {
|
|
2895
|
+
methodPkgPath = methodFunc.Pkg().Path()
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
if methodPkgPath == "" {
|
|
2900
|
+
methodPkgPath = pkg.Types.Path()
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
return &MethodKey{
|
|
2904
|
+
PackagePath: methodPkgPath,
|
|
2905
|
+
ReceiverType: receiverType,
|
|
2906
|
+
MethodName: methodObj.Name(),
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2369
2911
|
|
|
2370
|
-
|
|
2371
|
-
|
|
2912
|
+
return nil
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// topologicalSortMethods performs a topological sort of methods based on their call dependencies
|
|
2916
|
+
// Returns sorted methods and methods involved in cycles
|
|
2917
|
+
func (v *analysisVisitor) topologicalSortMethods(methodCalls map[MethodKey][]MethodKey) ([]MethodKey, []MethodKey) {
|
|
2918
|
+
// Kahn's algorithm for topological sorting
|
|
2919
|
+
inDegree := make(map[MethodKey]int)
|
|
2920
|
+
graph := make(map[MethodKey][]MethodKey)
|
|
2921
|
+
|
|
2922
|
+
// Initialize in-degree counts and reverse graph
|
|
2923
|
+
for method := range methodCalls {
|
|
2924
|
+
inDegree[method] = 0
|
|
2925
|
+
graph[method] = []MethodKey{}
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
// Build reverse graph and count in-degrees
|
|
2929
|
+
// graph[dependency] = methods that depend on it
|
|
2930
|
+
for method, callees := range methodCalls {
|
|
2931
|
+
for _, callee := range callees {
|
|
2932
|
+
// Only create dependency edges for callees that exist in the call graph
|
|
2933
|
+
// This automatically excludes handwritten packages that aren't being compiled
|
|
2934
|
+
if _, exists := inDegree[callee]; exists {
|
|
2935
|
+
// Additionally, skip if the callee is from a handwritten package
|
|
2936
|
+
// This prevents our code from being blocked by unresolved handwritten package dependencies
|
|
2937
|
+
if !v.analysis.isHandwrittenPackage(callee.PackagePath) {
|
|
2938
|
+
graph[callee] = append(graph[callee], method)
|
|
2939
|
+
inDegree[method]++
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
// Find methods with no dependencies (in-degree == 0)
|
|
2946
|
+
var queue []MethodKey
|
|
2947
|
+
for method, degree := range inDegree {
|
|
2948
|
+
if degree == 0 {
|
|
2949
|
+
queue = append(queue, method)
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
var sorted []MethodKey
|
|
2954
|
+
|
|
2955
|
+
for len(queue) > 0 {
|
|
2956
|
+
// Remove method from queue
|
|
2957
|
+
current := queue[0]
|
|
2958
|
+
queue = queue[1:]
|
|
2959
|
+
sorted = append(sorted, current)
|
|
2960
|
+
|
|
2961
|
+
// For each method that depends on current
|
|
2962
|
+
for _, dependent := range graph[current] {
|
|
2963
|
+
inDegree[dependent]--
|
|
2964
|
+
if inDegree[dependent] == 0 {
|
|
2965
|
+
queue = append(queue, dependent)
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// Find methods in cycles (not in sorted list)
|
|
2971
|
+
var cycles []MethodKey
|
|
2972
|
+
if len(sorted) != len(methodCalls) {
|
|
2973
|
+
for method := range methodCalls {
|
|
2974
|
+
found := slices.Contains(sorted, method)
|
|
2975
|
+
if !found {
|
|
2976
|
+
cycles = append(cycles, method)
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
return sorted, cycles
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// analyzeMethodAsyncTopological analyzes a single method for async operations in topological order
|
|
2985
|
+
func (v *analysisVisitor) analyzeMethodAsyncTopological(methodKey MethodKey, callees []MethodKey) {
|
|
2986
|
+
// Check if method is from handwritten package
|
|
2372
2987
|
isHandwrittenPackage := v.analysis.isHandwrittenPackage(methodKey.PackagePath)
|
|
2373
2988
|
|
|
2374
2989
|
if isHandwrittenPackage {
|
|
2375
2990
|
// For handwritten packages, check if we have pre-loaded metadata
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2991
|
+
_, hasMetadata := v.analysis.MethodAsyncStatus[methodKey]
|
|
2992
|
+
if hasMetadata {
|
|
2993
|
+
// Already set from metadata, don't override
|
|
2994
|
+
return
|
|
2380
2995
|
}
|
|
2381
|
-
|
|
2996
|
+
// No metadata means assume sync
|
|
2997
|
+
v.analysis.MethodAsyncStatus[methodKey] = false
|
|
2998
|
+
return
|
|
2999
|
+
}
|
|
2382
3000
|
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
isAsync = false
|
|
3001
|
+
// Find the method declaration
|
|
3002
|
+
pkg := v.analysis.AllPackages[methodKey.PackagePath]
|
|
3003
|
+
if pkg == nil {
|
|
3004
|
+
if methodKey.PackagePath == v.pkg.Types.Path() {
|
|
3005
|
+
pkg = v.pkg
|
|
2389
3006
|
}
|
|
2390
|
-
} else if funcDecl.Body != nil {
|
|
2391
|
-
// Not a handwritten package and has body: always analyze for async operations
|
|
2392
|
-
// This allows fixed-point iteration to update results
|
|
2393
|
-
isAsync = v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
|
|
2394
3007
|
}
|
|
2395
|
-
// Otherwise leave isAsync as false
|
|
2396
3008
|
|
|
2397
|
-
|
|
2398
|
-
|
|
3009
|
+
if pkg == nil {
|
|
3010
|
+
// Can't find package, assume sync
|
|
3011
|
+
v.analysis.MethodAsyncStatus[methodKey] = false
|
|
3012
|
+
return
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
var funcDecl *ast.FuncDecl
|
|
3016
|
+
if methodKey.ReceiverType == "" {
|
|
3017
|
+
// Package-level function
|
|
3018
|
+
funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
|
|
3019
|
+
} else {
|
|
3020
|
+
// Method with receiver
|
|
3021
|
+
funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
if funcDecl == nil || funcDecl.Body == nil {
|
|
3025
|
+
// No body to analyze, assume sync
|
|
3026
|
+
v.analysis.MethodAsyncStatus[methodKey] = false
|
|
3027
|
+
return
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
// Check if method contains async operations (including calls to async external methods)
|
|
3031
|
+
isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
|
|
3032
|
+
|
|
3033
|
+
// If not directly async, check if any callee from the call graph is async
|
|
3034
|
+
// (This catches calls to other methods in the same codebase)
|
|
3035
|
+
if !isAsync {
|
|
3036
|
+
for _, callee := range callees {
|
|
3037
|
+
if calleeAsync, exists := v.analysis.MethodAsyncStatus[callee]; exists && calleeAsync {
|
|
3038
|
+
isAsync = true
|
|
3039
|
+
break
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2399
3043
|
|
|
2400
|
-
|
|
2401
|
-
delete(v.visitingMethods, methodKey)
|
|
3044
|
+
v.analysis.MethodAsyncStatus[methodKey] = isAsync
|
|
2402
3045
|
}
|
|
2403
3046
|
|
|
2404
3047
|
// getMethodKey creates a unique key for a method
|
|
@@ -2408,14 +3051,19 @@ func (v *analysisVisitor) getMethodKey(funcDecl *ast.FuncDecl, pkg *packages.Pac
|
|
|
2408
3051
|
receiverType := ""
|
|
2409
3052
|
|
|
2410
3053
|
if funcDecl.Recv != nil && len(funcDecl.Recv.List) > 0 {
|
|
2411
|
-
//
|
|
2412
|
-
if len(funcDecl.Recv.List[0].Names) > 0 {
|
|
3054
|
+
// Try to get receiver type from TypesInfo first
|
|
3055
|
+
if len(funcDecl.Recv.List[0].Names) > 0 && pkg.TypesInfo != nil {
|
|
2413
3056
|
if def := pkg.TypesInfo.Defs[funcDecl.Recv.List[0].Names[0]]; def != nil {
|
|
2414
3057
|
if vr, ok := def.(*types.Var); ok {
|
|
2415
3058
|
receiverType = v.getTypeName(vr.Type())
|
|
2416
3059
|
}
|
|
2417
3060
|
}
|
|
2418
3061
|
}
|
|
3062
|
+
|
|
3063
|
+
// Fallback to AST if TypesInfo is unavailable or failed
|
|
3064
|
+
if receiverType == "" {
|
|
3065
|
+
receiverType = v.getReceiverTypeFromAST(funcDecl.Recv.List[0].Type)
|
|
3066
|
+
}
|
|
2419
3067
|
}
|
|
2420
3068
|
|
|
2421
3069
|
return MethodKey{
|
|
@@ -2437,6 +3085,23 @@ func (v *analysisVisitor) getTypeName(t types.Type) string {
|
|
|
2437
3085
|
}
|
|
2438
3086
|
}
|
|
2439
3087
|
|
|
3088
|
+
// getReceiverTypeFromAST extracts the receiver type name from AST when TypesInfo is unavailable
|
|
3089
|
+
func (v *analysisVisitor) getReceiverTypeFromAST(expr ast.Expr) string {
|
|
3090
|
+
switch t := expr.(type) {
|
|
3091
|
+
case *ast.StarExpr:
|
|
3092
|
+
// Pointer receiver: *Type
|
|
3093
|
+
return v.getReceiverTypeFromAST(t.X)
|
|
3094
|
+
case *ast.Ident:
|
|
3095
|
+
// Simple type name
|
|
3096
|
+
return t.Name
|
|
3097
|
+
case *ast.SelectorExpr:
|
|
3098
|
+
// Qualified type: pkg.Type
|
|
3099
|
+
return t.Sel.Name
|
|
3100
|
+
default:
|
|
3101
|
+
return ""
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
|
|
2440
3105
|
// containsAsyncOperationsComplete is a comprehensive async detection that handles method calls
|
|
2441
3106
|
func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *packages.Package) bool {
|
|
2442
3107
|
var hasAsync bool
|
|
@@ -2489,7 +3154,13 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
|
|
|
2489
3154
|
// Direct function call
|
|
2490
3155
|
if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
|
|
2491
3156
|
if funcObj, ok := obj.(*types.Func); ok {
|
|
2492
|
-
|
|
3157
|
+
result := v.isFunctionAsync(funcObj, pkg)
|
|
3158
|
+
return result
|
|
3159
|
+
}
|
|
3160
|
+
// Check if this is a variable that returns async values
|
|
3161
|
+
// (e.g., indirect := sync.OnceValue(asyncFunc))
|
|
3162
|
+
if v.analysis.IsAsyncReturningVar(obj) {
|
|
3163
|
+
return true
|
|
2493
3164
|
}
|
|
2494
3165
|
}
|
|
2495
3166
|
|
|
@@ -2512,14 +3183,16 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
|
|
|
2512
3183
|
if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
|
|
2513
3184
|
methodName := fun.Sel.Name
|
|
2514
3185
|
// For interface method calls, check if the interface method is async
|
|
2515
|
-
|
|
3186
|
+
result := v.analysis.IsInterfaceMethodAsync(interfaceType, methodName)
|
|
3187
|
+
return result
|
|
2516
3188
|
}
|
|
2517
3189
|
}
|
|
2518
3190
|
|
|
2519
3191
|
// Method call on concrete objects
|
|
2520
3192
|
if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
|
|
2521
3193
|
if methodObj := selection.Obj(); methodObj != nil {
|
|
2522
|
-
|
|
3194
|
+
result := v.isMethodAsyncFromSelection(fun, methodObj, pkg)
|
|
3195
|
+
return result
|
|
2523
3196
|
}
|
|
2524
3197
|
}
|
|
2525
3198
|
}
|
|
@@ -2534,7 +3207,7 @@ func (v *analysisVisitor) isFunctionAsync(funcObj *types.Func, pkg *packages.Pac
|
|
|
2534
3207
|
return v.analysis.IsMethodAsync(funcObj.Pkg().Path(), "", funcObj.Name())
|
|
2535
3208
|
}
|
|
2536
3209
|
|
|
2537
|
-
// Check internal method status
|
|
3210
|
+
// Check internal method status (should already be computed during analysis)
|
|
2538
3211
|
methodKey := MethodKey{
|
|
2539
3212
|
PackagePath: pkg.Types.Path(),
|
|
2540
3213
|
ReceiverType: "",
|
|
@@ -2545,14 +3218,7 @@ func (v *analysisVisitor) isFunctionAsync(funcObj *types.Func, pkg *packages.Pac
|
|
|
2545
3218
|
return status
|
|
2546
3219
|
}
|
|
2547
3220
|
|
|
2548
|
-
// Not analyzed
|
|
2549
|
-
if funcDecl := v.findFunctionDecl(funcObj.Name(), pkg); funcDecl != nil {
|
|
2550
|
-
v.analyzeMethodAsync(funcDecl, pkg)
|
|
2551
|
-
if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
|
|
2552
|
-
return status
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
|
|
3221
|
+
// Not found - should have been analyzed during analyzeAllMethodsAsync
|
|
2556
3222
|
return false
|
|
2557
3223
|
}
|
|
2558
3224
|
|
|
@@ -2572,7 +3238,12 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
|
|
|
2572
3238
|
}
|
|
2573
3239
|
}
|
|
2574
3240
|
case *ast.SelectorExpr:
|
|
2575
|
-
// Field access (e.g., l.m.Lock())
|
|
3241
|
+
// Field access (e.g., l.m.Lock() or d.mu.Lock())
|
|
3242
|
+
if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
|
|
3243
|
+
receiverType = v.getTypeName(typeExpr)
|
|
3244
|
+
}
|
|
3245
|
+
default:
|
|
3246
|
+
// For other cases, try to get type directly
|
|
2576
3247
|
if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
|
|
2577
3248
|
receiverType = v.getTypeName(typeExpr)
|
|
2578
3249
|
}
|
|
@@ -2585,7 +3256,6 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
|
|
|
2585
3256
|
}
|
|
2586
3257
|
}
|
|
2587
3258
|
|
|
2588
|
-
// If no package path found, use current package
|
|
2589
3259
|
if methodPkgPath == "" {
|
|
2590
3260
|
methodPkgPath = pkg.Types.Path()
|
|
2591
3261
|
}
|
|
@@ -2602,28 +3272,7 @@ func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr,
|
|
|
2602
3272
|
return status
|
|
2603
3273
|
}
|
|
2604
3274
|
|
|
2605
|
-
//
|
|
2606
|
-
if methodPkgPath == v.pkg.Types.Path() {
|
|
2607
|
-
// This is a method in the same package - we should analyze it if we haven't yet
|
|
2608
|
-
if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), v.pkg); funcDecl != nil {
|
|
2609
|
-
v.analyzeMethodAsync(funcDecl, v.pkg)
|
|
2610
|
-
if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
|
|
2611
|
-
return status
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
} else {
|
|
2615
|
-
// For methods in other packages that we're compiling together
|
|
2616
|
-
if targetPkg := v.analysis.AllPackages[methodPkgPath]; targetPkg != nil {
|
|
2617
|
-
// Try to analyze the method if we haven't already
|
|
2618
|
-
if funcDecl := v.findMethodDecl(receiverType, methodObj.Name(), targetPkg); funcDecl != nil {
|
|
2619
|
-
v.analyzeMethodAsync(funcDecl, targetPkg)
|
|
2620
|
-
if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
|
|
2621
|
-
return status
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
|
|
3275
|
+
// Not found - should have been analyzed during analyzeAllMethodsAsync
|
|
2627
3276
|
return false
|
|
2628
3277
|
}
|
|
2629
3278
|
|
|
@@ -2663,22 +3312,6 @@ func (v *analysisVisitor) findMethodDecl(receiverType, methodName string, pkg *p
|
|
|
2663
3312
|
return nil
|
|
2664
3313
|
}
|
|
2665
3314
|
|
|
2666
|
-
// checkExternalMethodMetadata checks if an external method is async based on pre-loaded metadata
|
|
2667
|
-
func (v *analysisVisitor) checkExternalMethodMetadata(pkgPath, receiverType, methodName string) bool {
|
|
2668
|
-
// Use MethodKey to check pre-loaded metadata in MethodAsyncStatus
|
|
2669
|
-
key := MethodKey{
|
|
2670
|
-
PackagePath: pkgPath,
|
|
2671
|
-
ReceiverType: receiverType,
|
|
2672
|
-
MethodName: methodName,
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
if isAsync, exists := v.analysis.MethodAsyncStatus[key]; exists {
|
|
2676
|
-
return isAsync
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
return false
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
3315
|
// IsLocalMethodAsync checks if a local method is async using pre-computed analysis
|
|
2683
3316
|
func (a *Analysis) IsLocalMethodAsync(pkgPath, receiverType, methodName string) bool {
|
|
2684
3317
|
methodKey := MethodKey{
|
|
@@ -2693,31 +3326,3 @@ func (a *Analysis) IsLocalMethodAsync(pkgPath, receiverType, methodName string)
|
|
|
2693
3326
|
|
|
2694
3327
|
return false
|
|
2695
3328
|
}
|
|
2696
|
-
|
|
2697
|
-
// updateInterfaceImplementationAsyncStatus updates interface implementations with correct async status
|
|
2698
|
-
// This runs after method async analysis is complete
|
|
2699
|
-
func (v *analysisVisitor) updateInterfaceImplementationAsyncStatus() {
|
|
2700
|
-
// Iterate through all tracked interface implementations and update their async status
|
|
2701
|
-
for key, implementations := range v.analysis.InterfaceImplementations {
|
|
2702
|
-
// Remove duplicates first
|
|
2703
|
-
seenMethods := make(map[string]bool)
|
|
2704
|
-
uniqueImplementations := []ImplementationInfo{}
|
|
2705
|
-
|
|
2706
|
-
for _, impl := range implementations {
|
|
2707
|
-
methodKey := impl.StructType.Obj().Name() + "." + key.MethodName
|
|
2708
|
-
if !seenMethods[methodKey] {
|
|
2709
|
-
seenMethods[methodKey] = true
|
|
2710
|
-
|
|
2711
|
-
// Now that method async analysis is complete, get the correct async status
|
|
2712
|
-
isAsync := v.analysis.IsAsyncFunc(impl.Method)
|
|
2713
|
-
|
|
2714
|
-
// Update the implementation with the correct async status
|
|
2715
|
-
impl.IsAsyncByFlow = isAsync
|
|
2716
|
-
uniqueImplementations = append(uniqueImplementations, impl)
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
|
|
2720
|
-
// Store the updated implementations without duplicates
|
|
2721
|
-
v.analysis.InterfaceImplementations[key] = uniqueImplementations
|
|
2722
|
-
}
|
|
2723
|
-
}
|