goscript 0.0.84 → 0.1.0
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 +13 -1
- package/cmd/goscript/cmd_compile.go +70 -69
- package/cmd/goscript/cmd_compile_test.go +79 -0
- package/cmd/goscript/main.go +10 -5
- package/compiler/compile-request.go +218 -0
- package/compiler/compiler.go +16 -1336
- package/compiler/compliance_test.go +196 -0
- package/compiler/config.go +6 -13
- package/compiler/diagnostic.go +70 -0
- package/compiler/index.test.ts +28 -28
- package/compiler/index.ts +40 -72
- package/compiler/lowered-program.go +132 -0
- package/compiler/lowering.go +3576 -0
- package/compiler/override-registry.go +422 -0
- package/compiler/override-registry_test.go +207 -0
- package/compiler/package-graph.go +231 -0
- package/compiler/package-graph_test.go +281 -0
- package/compiler/result.go +13 -0
- package/compiler/runtime-contract.go +279 -0
- package/compiler/runtime-contract_test.go +90 -0
- package/compiler/semantic-model-types.go +110 -0
- package/compiler/semantic-model.go +922 -0
- package/compiler/semantic-model_test.go +416 -0
- package/compiler/service.go +133 -0
- package/compiler/skeleton_test.go +1145 -0
- package/compiler/typescript-emitter.go +663 -0
- package/compiler/wasm/compile.go +2 -3
- package/compiler/wasm/compile_test.go +29 -0
- package/compiler/wasm_api.go +10 -159
- package/dist/compiler/index.d.ts +1 -3
- package/dist/compiler/index.js +31 -55
- package/dist/compiler/index.js.map +1 -1
- package/dist/gs/builtin/builtin.d.ts +13 -0
- package/dist/gs/builtin/builtin.js +23 -0
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/channel.d.ts +3 -3
- package/dist/gs/builtin/channel.js.map +1 -1
- package/dist/gs/builtin/hostio.d.ts +15 -1
- package/dist/gs/builtin/hostio.js +134 -49
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/index.d.ts +1 -0
- package/dist/gs/builtin/index.js +1 -0
- package/dist/gs/builtin/index.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +11 -0
- package/dist/gs/builtin/type.js +55 -1
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/bytes/buffer.gs.js.map +1 -1
- package/dist/gs/bytes/bytes.gs.js.map +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/crypto/rand/index.d.ts +5 -0
- package/dist/gs/crypto/rand/index.js +77 -0
- package/dist/gs/crypto/rand/index.js.map +1 -0
- package/dist/gs/encoding/json/index.d.ts +3 -0
- package/dist/gs/encoding/json/index.js +160 -0
- package/dist/gs/encoding/json/index.js.map +1 -0
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
- package/dist/gs/go/scanner/index.d.ts +29 -0
- package/dist/gs/go/scanner/index.js +120 -0
- package/dist/gs/go/scanner/index.js.map +1 -0
- package/dist/gs/go/token/index.d.ts +31 -0
- package/dist/gs/go/token/index.js +82 -0
- package/dist/gs/go/token/index.js.map +1 -0
- package/dist/gs/internal/abi/index.js.map +1 -1
- package/dist/gs/io/fs/fs.js.map +1 -1
- package/dist/gs/io/fs/readdir.js.map +1 -1
- package/dist/gs/io/fs/readfile.js.map +1 -1
- package/dist/gs/io/fs/stat.js.map +1 -1
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/io.js.map +1 -1
- package/dist/gs/os/dir_unix.gs.js.map +1 -1
- package/dist/gs/os/error.gs.js +2 -4
- package/dist/gs/os/error.gs.js.map +1 -1
- package/dist/gs/os/exec.gs.js.map +1 -1
- package/dist/gs/os/exec_posix.gs.js.map +1 -1
- package/dist/gs/os/rawconn_js.gs.js.map +1 -1
- package/dist/gs/os/root_js.gs.js.map +1 -1
- package/dist/gs/os/tempfile.gs.js +66 -9
- package/dist/gs/os/tempfile.gs.js.map +1 -1
- package/dist/gs/os/types.gs.js.map +1 -1
- package/dist/gs/os/types_js.gs.js +9 -9
- package/dist/gs/os/types_js.gs.js.map +1 -1
- package/dist/gs/os/types_unix.gs.js.map +1 -1
- package/dist/gs/path/filepath/match.js.map +1 -1
- package/dist/gs/path/match.js.map +1 -1
- package/dist/gs/path/path.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +2 -2
- package/dist/gs/reflect/index.js +1 -1
- package/dist/gs/reflect/index.js.map +1 -1
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +2 -1
- package/dist/gs/reflect/type.js +85 -14
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/visiblefields.js.map +1 -1
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/sort/sort.gs.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/strconv/quote.gs.js.map +1 -1
- package/dist/gs/strings/builder.js.map +1 -1
- package/dist/gs/strings/reader.js.map +1 -1
- package/dist/gs/strings/replace.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/sync/atomic/value.gs.js.map +1 -1
- package/dist/gs/sync/sync.d.ts +1 -0
- package/dist/gs/sync/sync.js +12 -0
- package/dist/gs/sync/sync.js.map +1 -1
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unicode/unicode.js.map +1 -1
- package/go.mod +2 -2
- package/gs/builtin/builtin.ts +27 -0
- package/gs/builtin/hostio.test.ts +177 -0
- package/gs/builtin/hostio.ts +171 -56
- package/gs/builtin/index.ts +1 -0
- package/gs/builtin/runtime-contract.test.ts +230 -0
- package/gs/builtin/type.ts +84 -1
- package/gs/crypto/rand/index.test.ts +32 -0
- package/gs/crypto/rand/index.ts +90 -0
- package/gs/crypto/rand/meta.json +5 -0
- package/gs/encoding/json/index.test.ts +65 -0
- package/gs/encoding/json/index.ts +186 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
- package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
- package/gs/go/scanner/index.test.ts +50 -0
- package/gs/go/scanner/index.ts +157 -0
- package/gs/go/token/index.test.ts +21 -0
- package/gs/go/token/index.ts +120 -0
- package/gs/os/file_unix_js.test.ts +50 -0
- package/gs/os/meta.json +1 -2
- package/gs/os/tempfile.gs.test.ts +85 -0
- package/gs/os/tempfile.gs.ts +71 -11
- package/gs/os/types_js.gs.ts +9 -9
- package/gs/reflect/index.ts +1 -1
- package/gs/reflect/type.ts +106 -17
- package/gs/reflect/typefor.test.ts +75 -0
- package/gs/sync/sync.test.ts +24 -0
- package/gs/sync/sync.ts +12 -0
- package/package.json +13 -13
- package/compiler/analysis.go +0 -3475
- package/compiler/analysis_test.go +0 -338
- package/compiler/assignment.go +0 -580
- package/compiler/builtin_test.go +0 -92
- package/compiler/code-writer.go +0 -115
- package/compiler/compiler_test.go +0 -149
- package/compiler/composite-lit.go +0 -779
- package/compiler/config_test.go +0 -62
- package/compiler/constraint.go +0 -86
- package/compiler/decl.go +0 -801
- package/compiler/expr-call-async.go +0 -188
- package/compiler/expr-call-builtins.go +0 -208
- package/compiler/expr-call-helpers.go +0 -382
- package/compiler/expr-call-make.go +0 -318
- package/compiler/expr-call-type-conversion.go +0 -520
- package/compiler/expr-call.go +0 -413
- package/compiler/expr-selector.go +0 -343
- package/compiler/expr-star.go +0 -82
- package/compiler/expr-type.go +0 -442
- package/compiler/expr-value.go +0 -89
- package/compiler/expr.go +0 -773
- package/compiler/field.go +0 -183
- package/compiler/gs_dependencies_test.go +0 -298
- package/compiler/lit.go +0 -322
- package/compiler/output.go +0 -72
- package/compiler/primitive.go +0 -149
- package/compiler/protobuf.go +0 -697
- package/compiler/sanitize.go +0 -100
- package/compiler/spec-struct.go +0 -995
- package/compiler/spec-value.go +0 -540
- package/compiler/spec.go +0 -725
- package/compiler/stmt-assign.go +0 -664
- package/compiler/stmt-for.go +0 -266
- package/compiler/stmt-range.go +0 -475
- package/compiler/stmt-select.go +0 -262
- package/compiler/stmt-type-switch.go +0 -147
- package/compiler/stmt.go +0 -1308
- package/compiler/type-assert.go +0 -386
- package/compiler/type-info.go +0 -156
- package/compiler/type-utils.go +0 -207
- package/compiler/type.go +0 -892
package/compiler/analysis.go
DELETED
|
@@ -1,3475 +0,0 @@
|
|
|
1
|
-
package compiler
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"go/ast"
|
|
6
|
-
"go/token"
|
|
7
|
-
"go/types"
|
|
8
|
-
"path/filepath"
|
|
9
|
-
"slices"
|
|
10
|
-
"strings"
|
|
11
|
-
|
|
12
|
-
"github.com/aperturerobotics/goscript"
|
|
13
|
-
"golang.org/x/tools/go/packages"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
// fileImport tracks an import in a file.
|
|
17
|
-
type fileImport struct {
|
|
18
|
-
importPath string
|
|
19
|
-
importVars map[string]struct{}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// AssignmentType indicates how a variable's value was assigned or used.
|
|
23
|
-
type AssignmentType int
|
|
24
|
-
|
|
25
|
-
const (
|
|
26
|
-
// DirectAssignment represents a direct value copy (e.g., x = y)
|
|
27
|
-
DirectAssignment AssignmentType = iota
|
|
28
|
-
// AddressOfAssignment represents taking the address (e.g., p = &y)
|
|
29
|
-
// or assigning to a dereferenced pointer (*p = y) - indicating the pointer p is used.
|
|
30
|
-
AddressOfAssignment
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
// AssignmentInfo stores information about a single assignment source or destination.
|
|
34
|
-
type AssignmentInfo struct {
|
|
35
|
-
Object types.Object // The source or destination variable object
|
|
36
|
-
Type AssignmentType // The type of assignment involved
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// VariableUsageInfo tracks how a variable is used throughout the code.
|
|
40
|
-
type VariableUsageInfo struct {
|
|
41
|
-
// Sources lists variables whose values (or addresses) are assigned TO this variable.
|
|
42
|
-
// Example: For `x = y`, y is a source for x. For `x = &y`, y is a source for x.
|
|
43
|
-
Sources []AssignmentInfo
|
|
44
|
-
// Destinations lists variables that are assigned the value (or address) FROM this variable.
|
|
45
|
-
// Example: For `y = x`, y is a destination for x. For `p = &x`, p is a destination for x.
|
|
46
|
-
Destinations []AssignmentInfo
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ShadowingInfo tracks variable shadowing in if statement initializations
|
|
50
|
-
type ShadowingInfo struct {
|
|
51
|
-
// ShadowedVariables maps shadowed variable names to their outer scope objects
|
|
52
|
-
ShadowedVariables map[string]types.Object
|
|
53
|
-
// TempVariables maps shadowed variable names to temporary variable names
|
|
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
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// FunctionTypeInfo represents Go function type information for reflection
|
|
62
|
-
type FunctionTypeInfo struct {
|
|
63
|
-
Params []types.Type // Parameter types
|
|
64
|
-
Results []types.Type // Return types
|
|
65
|
-
Variadic bool // Whether the function is variadic
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// FunctionInfo consolidates function-related tracking data.
|
|
69
|
-
type FunctionInfo struct {
|
|
70
|
-
ReceiverUsed bool
|
|
71
|
-
NamedReturns []string
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ReflectedFunctionInfo tracks functions that need reflection support
|
|
75
|
-
type ReflectedFunctionInfo struct {
|
|
76
|
-
FuncType *types.Signature // The function's type signature
|
|
77
|
-
NeedsReflect bool // Whether this function is used with reflection
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// NodeInfo consolidates node-related tracking data.
|
|
81
|
-
type NodeInfo struct {
|
|
82
|
-
NeedsDefer bool
|
|
83
|
-
InAsyncContext bool
|
|
84
|
-
IsBareReturn bool
|
|
85
|
-
EnclosingFuncDecl *ast.FuncDecl
|
|
86
|
-
EnclosingFuncLit *ast.FuncLit
|
|
87
|
-
IsInsideFunction bool // true if this declaration is inside a function body
|
|
88
|
-
IsMethodValue bool // true if this SelectorExpr is a method value that needs binding
|
|
89
|
-
ShadowingInfo *ShadowingInfo // variable shadowing information for if statements
|
|
90
|
-
IdentifierMapping string // replacement name for this identifier (e.g., receiver -> "receiver")
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// GsMetadata represents the structure of a meta.json file in gs/ packages
|
|
94
|
-
type GsMetadata struct {
|
|
95
|
-
Dependencies []string `json:"dependencies,omitempty"`
|
|
96
|
-
AsyncMethods map[string]bool `json:"asyncMethods,omitempty"`
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// InterfaceMethodKey uniquely identifies an interface method
|
|
100
|
-
type InterfaceMethodKey struct {
|
|
101
|
-
InterfaceType string // The string representation of the interface type
|
|
102
|
-
MethodName string // The method name
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// MethodKey uniquely identifies a method for async analysis
|
|
106
|
-
type MethodKey struct {
|
|
107
|
-
PackagePath string // Package path
|
|
108
|
-
ReceiverType string // Receiver type name (empty for package-level functions)
|
|
109
|
-
MethodName string // Method or function name
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ImplementationInfo tracks information about a struct that implements an interface method
|
|
113
|
-
type ImplementationInfo struct {
|
|
114
|
-
StructType *types.Named // The struct type that implements the interface
|
|
115
|
-
Method *types.Func // The method object
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Analysis holds information gathered during the analysis phase of the Go code compilation.
|
|
119
|
-
// This data is used to make decisions about how to generate TypeScript code.
|
|
120
|
-
// Analysis is read-only after being built and should not be modified during code generation.
|
|
121
|
-
type Analysis struct {
|
|
122
|
-
// VariableUsage tracks how variables are assigned and used, particularly for pointer analysis.
|
|
123
|
-
// The key is the variable's types.Object.
|
|
124
|
-
VariableUsage map[types.Object]*VariableUsageInfo
|
|
125
|
-
|
|
126
|
-
// Imports stores the imports for the file
|
|
127
|
-
Imports map[string]*fileImport
|
|
128
|
-
|
|
129
|
-
// Cmap stores the comment map for the file
|
|
130
|
-
Cmap ast.CommentMap
|
|
131
|
-
|
|
132
|
-
// FunctionData consolidates function-related tracking into one map
|
|
133
|
-
FunctionData map[types.Object]*FunctionInfo
|
|
134
|
-
|
|
135
|
-
// NodeData consolidates node-related tracking into one map
|
|
136
|
-
NodeData map[ast.Node]*NodeInfo
|
|
137
|
-
|
|
138
|
-
// Keep specialized maps that serve different purposes
|
|
139
|
-
// FuncLitData tracks function literal specific data since they don't have types.Object
|
|
140
|
-
FuncLitData map[*ast.FuncLit]*FunctionInfo
|
|
141
|
-
|
|
142
|
-
// ReflectedFunctions tracks functions that need reflection type metadata
|
|
143
|
-
ReflectedFunctions map[ast.Node]*ReflectedFunctionInfo
|
|
144
|
-
|
|
145
|
-
// FunctionAssignments tracks which function literals are assigned to which variables
|
|
146
|
-
FunctionAssignments map[types.Object]ast.Node
|
|
147
|
-
|
|
148
|
-
// AsyncReturningVars tracks variables whose function type returns async values
|
|
149
|
-
// This happens when a variable is assigned from a higher-order function (like sync.OnceValue)
|
|
150
|
-
// that receives an async function literal as an argument
|
|
151
|
-
AsyncReturningVars map[types.Object]bool
|
|
152
|
-
|
|
153
|
-
// NamedBasicTypes tracks types that should be implemented as type aliases with standalone functions
|
|
154
|
-
// This includes named types with basic underlying types (like uint32, string) that have methods
|
|
155
|
-
NamedBasicTypes map[types.Type]bool
|
|
156
|
-
|
|
157
|
-
// AllPackages stores all loaded packages for dependency analysis
|
|
158
|
-
// Key: package path, Value: package data
|
|
159
|
-
AllPackages map[string]*packages.Package
|
|
160
|
-
|
|
161
|
-
// InterfaceImplementations tracks which struct types implement which interface methods
|
|
162
|
-
// This is used to determine interface method async status based on implementations
|
|
163
|
-
InterfaceImplementations map[InterfaceMethodKey][]ImplementationInfo
|
|
164
|
-
|
|
165
|
-
// MethodAsyncStatus stores the async status of all methods analyzed
|
|
166
|
-
// This is computed once during analysis and reused during code generation
|
|
167
|
-
MethodAsyncStatus map[MethodKey]bool
|
|
168
|
-
|
|
169
|
-
// ReferencedTypesPerFile tracks which named types are referenced in each file.
|
|
170
|
-
// This is used to filter synthetic imports to only include packages needed
|
|
171
|
-
// by types actually used in each specific file, not all types in the package.
|
|
172
|
-
// Key: file path, Value: set of named types referenced in that file
|
|
173
|
-
ReferencedTypesPerFile map[string]map[*types.Named]bool
|
|
174
|
-
|
|
175
|
-
// SyntheticImportsPerFile stores synthetic imports needed per file.
|
|
176
|
-
// Key: file path, Value: map of package name to import info
|
|
177
|
-
SyntheticImportsPerFile map[string]map[string]*fileImport
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// PackageAnalysis holds cross-file analysis data for a package
|
|
181
|
-
type PackageAnalysis struct {
|
|
182
|
-
// FunctionDefs maps file names to the functions defined in that file
|
|
183
|
-
// Key: filename (without .go extension), Value: list of function names
|
|
184
|
-
FunctionDefs map[string][]string
|
|
185
|
-
|
|
186
|
-
// FunctionCalls maps file names to the functions they call from other files
|
|
187
|
-
// Key: filename (without .go extension), Value: map[sourceFile][]functionNames
|
|
188
|
-
FunctionCalls map[string]map[string][]string
|
|
189
|
-
|
|
190
|
-
// TypeDefs maps file names to the types defined in that file
|
|
191
|
-
// Key: filename (without .go extension), Value: list of type names
|
|
192
|
-
TypeDefs map[string][]string
|
|
193
|
-
|
|
194
|
-
// TypeCalls maps file names to the types they reference from other files
|
|
195
|
-
// Key: filename (without .go extension), Value: map[sourceFile][]typeNames
|
|
196
|
-
TypeCalls map[string]map[string][]string
|
|
197
|
-
|
|
198
|
-
// VariableDefs maps file names to the package-level variables defined in that file
|
|
199
|
-
// Key: filename (without .go extension), Value: list of variable names
|
|
200
|
-
VariableDefs map[string][]string
|
|
201
|
-
|
|
202
|
-
// VariableCalls maps file names to the package-level variables they reference from other files
|
|
203
|
-
// Key: filename (without .go extension), Value: map[sourceFile][]variableNames
|
|
204
|
-
VariableCalls map[string]map[string][]string
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// NewAnalysis creates a new Analysis instance.
|
|
208
|
-
func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
|
|
209
|
-
if allPackages == nil {
|
|
210
|
-
allPackages = make(map[string]*packages.Package)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return &Analysis{
|
|
214
|
-
VariableUsage: make(map[types.Object]*VariableUsageInfo),
|
|
215
|
-
Imports: make(map[string]*fileImport),
|
|
216
|
-
FunctionData: make(map[types.Object]*FunctionInfo),
|
|
217
|
-
NodeData: make(map[ast.Node]*NodeInfo),
|
|
218
|
-
FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
|
|
219
|
-
ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
|
|
220
|
-
FunctionAssignments: make(map[types.Object]ast.Node),
|
|
221
|
-
AsyncReturningVars: make(map[types.Object]bool),
|
|
222
|
-
NamedBasicTypes: make(map[types.Type]bool),
|
|
223
|
-
AllPackages: allPackages,
|
|
224
|
-
InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
|
|
225
|
-
MethodAsyncStatus: make(map[MethodKey]bool),
|
|
226
|
-
ReferencedTypesPerFile: make(map[string]map[*types.Named]bool),
|
|
227
|
-
SyntheticImportsPerFile: make(map[string]map[string]*fileImport),
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// NewPackageAnalysis creates a new PackageAnalysis instance
|
|
232
|
-
func NewPackageAnalysis() *PackageAnalysis {
|
|
233
|
-
return &PackageAnalysis{
|
|
234
|
-
FunctionDefs: make(map[string][]string),
|
|
235
|
-
FunctionCalls: make(map[string]map[string][]string),
|
|
236
|
-
TypeDefs: make(map[string][]string),
|
|
237
|
-
TypeCalls: make(map[string]map[string][]string),
|
|
238
|
-
VariableDefs: make(map[string][]string),
|
|
239
|
-
VariableCalls: make(map[string]map[string][]string),
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// collectZeroValueTypeNames records named struct types whose zero value emits a
|
|
244
|
-
// runtime constructor call.
|
|
245
|
-
func collectZeroValueTypeNames(typ types.Type, names map[string]struct{}) {
|
|
246
|
-
switch t := typ.(type) {
|
|
247
|
-
case *types.Array:
|
|
248
|
-
collectZeroValueTypeNames(t.Elem(), names)
|
|
249
|
-
case *types.Named:
|
|
250
|
-
if _, isStruct := t.Underlying().(*types.Struct); isStruct {
|
|
251
|
-
names[t.Obj().Name()] = struct{}{}
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
collectZeroValueTypeNames(t.Underlying(), names)
|
|
255
|
-
case *types.Alias:
|
|
256
|
-
collectZeroValueTypeNames(t.Underlying(), names)
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// addTypeRefsFromZeroValue adds same-package type imports needed when code
|
|
261
|
-
// generation synthesizes a zero value without an explicit AST type reference.
|
|
262
|
-
func addTypeRefsFromZeroValue(analysis *PackageAnalysis, currentFileName string, typ types.Type, refs map[string][]string) {
|
|
263
|
-
typeNames := make(map[string]struct{})
|
|
264
|
-
collectZeroValueTypeNames(typ, typeNames)
|
|
265
|
-
if len(typeNames) == 0 {
|
|
266
|
-
return
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
currentFileTypes := analysis.TypeDefs[currentFileName]
|
|
270
|
-
for typeName := range typeNames {
|
|
271
|
-
if slices.Contains(currentFileTypes, typeName) {
|
|
272
|
-
continue
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
for sourceFile, types := range analysis.TypeDefs {
|
|
276
|
-
if sourceFile == currentFileName || !slices.Contains(types, typeName) {
|
|
277
|
-
continue
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if refs[sourceFile] == nil {
|
|
281
|
-
refs[sourceFile] = []string{}
|
|
282
|
-
}
|
|
283
|
-
if !slices.Contains(refs[sourceFile], typeName) {
|
|
284
|
-
refs[sourceFile] = append(refs[sourceFile], typeName)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ensureNodeData ensures that NodeData exists for a given node and returns it
|
|
291
|
-
func (a *Analysis) ensureNodeData(node ast.Node) *NodeInfo {
|
|
292
|
-
if node == nil {
|
|
293
|
-
return nil
|
|
294
|
-
}
|
|
295
|
-
if a.NodeData[node] == nil {
|
|
296
|
-
a.NodeData[node] = &NodeInfo{}
|
|
297
|
-
}
|
|
298
|
-
return a.NodeData[node]
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// ensureFunctionData ensures that FunctionData exists for a given object and returns it
|
|
302
|
-
func (a *Analysis) ensureFunctionData(obj types.Object) *FunctionInfo {
|
|
303
|
-
if obj == nil {
|
|
304
|
-
return nil
|
|
305
|
-
}
|
|
306
|
-
if a.FunctionData[obj] == nil {
|
|
307
|
-
a.FunctionData[obj] = &FunctionInfo{}
|
|
308
|
-
}
|
|
309
|
-
return a.FunctionData[obj]
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// GetFunctionInfoFromContext returns FunctionInfo based on the enclosing function context
|
|
313
|
-
func (a *Analysis) GetFunctionInfoFromContext(nodeInfo *NodeInfo, pkg *packages.Package) *FunctionInfo {
|
|
314
|
-
if nodeInfo == nil {
|
|
315
|
-
return nil
|
|
316
|
-
}
|
|
317
|
-
if nodeInfo.EnclosingFuncDecl != nil {
|
|
318
|
-
if obj := pkg.TypesInfo.ObjectOf(nodeInfo.EnclosingFuncDecl.Name); obj != nil {
|
|
319
|
-
return a.FunctionData[obj]
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if nodeInfo.EnclosingFuncLit != nil {
|
|
323
|
-
return a.FuncLitData[nodeInfo.EnclosingFuncLit]
|
|
324
|
-
}
|
|
325
|
-
return nil
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// NeedsDefer returns whether the given node needs defer handling.
|
|
329
|
-
func (a *Analysis) NeedsDefer(node ast.Node) bool {
|
|
330
|
-
if node == nil {
|
|
331
|
-
return false
|
|
332
|
-
}
|
|
333
|
-
nodeInfo := a.NodeData[node]
|
|
334
|
-
if nodeInfo == nil {
|
|
335
|
-
return false
|
|
336
|
-
}
|
|
337
|
-
return nodeInfo.NeedsDefer
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// IsInAsyncFunction returns whether the given node is inside an async function.
|
|
341
|
-
func (a *Analysis) IsInAsyncFunction(node ast.Node) bool {
|
|
342
|
-
if node == nil {
|
|
343
|
-
return false
|
|
344
|
-
}
|
|
345
|
-
nodeInfo := a.NodeData[node]
|
|
346
|
-
if nodeInfo == nil {
|
|
347
|
-
return false
|
|
348
|
-
}
|
|
349
|
-
return nodeInfo.InAsyncContext
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// IsAsyncFunc returns whether the given object represents an async function.
|
|
353
|
-
func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
|
|
354
|
-
if obj == nil {
|
|
355
|
-
return false
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Use MethodAsyncStatus for all async status lookups
|
|
359
|
-
funcObj, ok := obj.(*types.Func)
|
|
360
|
-
if !ok {
|
|
361
|
-
return false
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Create MethodKey for lookup
|
|
365
|
-
methodKey := MethodKey{
|
|
366
|
-
PackagePath: funcObj.Pkg().Path(),
|
|
367
|
-
ReceiverType: "", // Functions have no receiver, methods are handled separately
|
|
368
|
-
MethodName: funcObj.Name(),
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Check if it's a method with receiver
|
|
372
|
-
if sig, ok := funcObj.Type().(*types.Signature); ok && sig.Recv() != nil {
|
|
373
|
-
// For methods, get the receiver type name using same format as analysis
|
|
374
|
-
recv := sig.Recv()
|
|
375
|
-
recvType := recv.Type()
|
|
376
|
-
// Handle pointer receivers
|
|
377
|
-
if ptr, isPtr := recvType.(*types.Pointer); isPtr {
|
|
378
|
-
recvType = ptr.Elem()
|
|
379
|
-
}
|
|
380
|
-
// Use short type name, not full path (consistent with analysis)
|
|
381
|
-
if named, ok := recvType.(*types.Named); ok {
|
|
382
|
-
methodKey.ReceiverType = named.Obj().Name()
|
|
383
|
-
} else {
|
|
384
|
-
methodKey.ReceiverType = recvType.String()
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists {
|
|
389
|
-
return isAsync
|
|
390
|
-
}
|
|
391
|
-
return false
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// IsAsyncReturningVar returns whether the given variable holds a function that returns async values.
|
|
395
|
-
// This is true when the variable is assigned from a higher-order function that receives an async function literal.
|
|
396
|
-
func (a *Analysis) IsAsyncReturningVar(obj types.Object) bool {
|
|
397
|
-
if obj == nil {
|
|
398
|
-
return false
|
|
399
|
-
}
|
|
400
|
-
return a.AsyncReturningVars[obj]
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
func (a *Analysis) IsReceiverUsed(obj types.Object) bool {
|
|
404
|
-
if obj == nil {
|
|
405
|
-
return false
|
|
406
|
-
}
|
|
407
|
-
funcInfo := a.FunctionData[obj]
|
|
408
|
-
if funcInfo == nil {
|
|
409
|
-
return false
|
|
410
|
-
}
|
|
411
|
-
return funcInfo.ReceiverUsed
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// IsFuncLitAsync checks if a function literal is async based on our analysis.
|
|
415
|
-
func (a *Analysis) IsFuncLitAsync(funcLit *ast.FuncLit) bool {
|
|
416
|
-
if funcLit == nil {
|
|
417
|
-
return false
|
|
418
|
-
}
|
|
419
|
-
// Check function literal specific data first - but IsAsync field was removed
|
|
420
|
-
// Function literals don't have types.Object, so fall back to node data
|
|
421
|
-
nodeInfo := a.NodeData[funcLit]
|
|
422
|
-
if nodeInfo == nil {
|
|
423
|
-
return false
|
|
424
|
-
}
|
|
425
|
-
return nodeInfo.InAsyncContext
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// NeedsVarRef returns whether the given object needs to be variable referenced.
|
|
429
|
-
// This is true when the object's address is taken (e.g., &myVar) in the analyzed code.
|
|
430
|
-
// Variables that have their address taken must be wrapped in VarRef to maintain identity.
|
|
431
|
-
func (a *Analysis) NeedsVarRef(obj types.Object) bool {
|
|
432
|
-
if obj == nil {
|
|
433
|
-
return false
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
usageInfo, exists := a.VariableUsage[obj]
|
|
437
|
-
if !exists {
|
|
438
|
-
return false
|
|
439
|
-
}
|
|
440
|
-
// Check if any destination assignment involves taking the address of 'obj'
|
|
441
|
-
for _, destInfo := range usageInfo.Destinations {
|
|
442
|
-
if destInfo.Type == AddressOfAssignment {
|
|
443
|
-
return true
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return false
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// NeedsVarRefAccess returns whether accessing the given object requires '.value' access in TypeScript.
|
|
450
|
-
// This is more nuanced than NeedsVarRef and considers both direct variable references and
|
|
451
|
-
// pointers that may point to variable-referenced values.
|
|
452
|
-
//
|
|
453
|
-
// Examples:
|
|
454
|
-
// - Direct variable reference (NeedsVarRef = true):
|
|
455
|
-
// Example: let x = $.varRef(10) => x.value
|
|
456
|
-
// - Pointer pointing to a variable-referenced value:
|
|
457
|
-
// Example: let p: VarRef<number> | null = x => p!.value
|
|
458
|
-
// - Regular pointer (NeedsVarRef = false, but points to variable reference):
|
|
459
|
-
// Example: let q = x => q!.value (where x is VarRef)
|
|
460
|
-
func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
|
|
461
|
-
if obj == nil {
|
|
462
|
-
return false
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// If the variable itself is variable referenced, it needs .value access
|
|
466
|
-
if a.NeedsVarRef(obj) {
|
|
467
|
-
return true
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// For pointer variables, check if they point to a variable-referenced value
|
|
471
|
-
if _, ok := obj.Type().(*types.Pointer); ok {
|
|
472
|
-
// Check all assignments to this pointer variable
|
|
473
|
-
for varObj, info := range a.VariableUsage {
|
|
474
|
-
if varObj == obj {
|
|
475
|
-
for _, src := range info.Sources {
|
|
476
|
-
if src.Type == AddressOfAssignment && src.Object != nil {
|
|
477
|
-
// This pointer was assigned &someVar, check if someVar is variable referenced
|
|
478
|
-
return a.NeedsVarRef(src.Object)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return false
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// IsMethodValue returns whether the given SelectorExpr node is a method value that needs binding.
|
|
489
|
-
func (a *Analysis) IsMethodValue(node *ast.SelectorExpr) bool {
|
|
490
|
-
if node == nil {
|
|
491
|
-
return false
|
|
492
|
-
}
|
|
493
|
-
nodeInfo := a.NodeData[node]
|
|
494
|
-
if nodeInfo == nil {
|
|
495
|
-
return false
|
|
496
|
-
}
|
|
497
|
-
return nodeInfo.IsMethodValue
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// HasVariableShadowing returns whether the given node has variable shadowing issues
|
|
501
|
-
func (a *Analysis) HasVariableShadowing(node ast.Node) bool {
|
|
502
|
-
if node == nil {
|
|
503
|
-
return false
|
|
504
|
-
}
|
|
505
|
-
nodeInfo := a.NodeData[node]
|
|
506
|
-
if nodeInfo == nil {
|
|
507
|
-
return false
|
|
508
|
-
}
|
|
509
|
-
return nodeInfo.ShadowingInfo != nil
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// GetShadowingInfo returns the variable shadowing information for the given node
|
|
513
|
-
func (a *Analysis) GetShadowingInfo(node ast.Node) *ShadowingInfo {
|
|
514
|
-
if node == nil {
|
|
515
|
-
return nil
|
|
516
|
-
}
|
|
517
|
-
nodeInfo := a.NodeData[node]
|
|
518
|
-
if nodeInfo == nil {
|
|
519
|
-
return nil
|
|
520
|
-
}
|
|
521
|
-
return nodeInfo.ShadowingInfo
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// analysisVisitor implements ast.Visitor and is used to traverse the AST during analysis.
|
|
525
|
-
type analysisVisitor struct {
|
|
526
|
-
// analysis stores information gathered during the traversal
|
|
527
|
-
analysis *Analysis
|
|
528
|
-
|
|
529
|
-
// pkg provides type information and other package details
|
|
530
|
-
pkg *packages.Package
|
|
531
|
-
|
|
532
|
-
// inAsyncFunction tracks if we're currently inside an async function
|
|
533
|
-
inAsyncFunction bool
|
|
534
|
-
|
|
535
|
-
// currentFuncName tracks the name of the function we're currently analyzing
|
|
536
|
-
currentFuncName string
|
|
537
|
-
|
|
538
|
-
// currentReceiver tracks the object of the receiver if inside a method
|
|
539
|
-
currentReceiver *types.Var
|
|
540
|
-
|
|
541
|
-
// currentFuncObj tracks the object of the function declaration we're currently analyzing
|
|
542
|
-
currentFuncObj types.Object
|
|
543
|
-
|
|
544
|
-
// currentFuncDecl tracks the *ast.FuncDecl of the function we're currently analyzing.
|
|
545
|
-
currentFuncDecl *ast.FuncDecl
|
|
546
|
-
|
|
547
|
-
// currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
|
|
548
|
-
currentFuncLit *ast.FuncLit
|
|
549
|
-
|
|
550
|
-
// currentFilePath tracks the file path of the file we're currently analyzing.
|
|
551
|
-
// This is used to track which types are referenced in each file.
|
|
552
|
-
currentFilePath string
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
|
|
556
|
-
func (v *analysisVisitor) getOrCreateUsageInfo(obj types.Object) *VariableUsageInfo {
|
|
557
|
-
if obj == nil {
|
|
558
|
-
return nil // Should not happen with valid objects
|
|
559
|
-
}
|
|
560
|
-
info, exists := v.analysis.VariableUsage[obj]
|
|
561
|
-
if !exists {
|
|
562
|
-
info = &VariableUsageInfo{}
|
|
563
|
-
v.analysis.VariableUsage[obj] = info
|
|
564
|
-
}
|
|
565
|
-
return info
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Visit implements the ast.Visitor interface.
|
|
569
|
-
// It analyzes each node in the AST to gather information needed for code generation.
|
|
570
|
-
func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
571
|
-
if node == nil {
|
|
572
|
-
return nil
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Initialize and store async state for the current node
|
|
576
|
-
nodeInfo := v.analysis.ensureNodeData(node)
|
|
577
|
-
nodeInfo.InAsyncContext = v.inAsyncFunction
|
|
578
|
-
|
|
579
|
-
switch n := node.(type) {
|
|
580
|
-
case *ast.GenDecl:
|
|
581
|
-
// Handle general declarations (var, const, type, import)
|
|
582
|
-
if n.Tok == token.VAR {
|
|
583
|
-
for _, spec := range n.Specs {
|
|
584
|
-
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
585
|
-
// Track type references from variable declarations for synthetic import filtering
|
|
586
|
-
if valueSpec.Type != nil {
|
|
587
|
-
if t := v.pkg.TypesInfo.TypeOf(valueSpec.Type); t != nil {
|
|
588
|
-
v.trackTypeReference(t)
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
for _, name := range valueSpec.Names {
|
|
592
|
-
if obj := v.pkg.TypesInfo.ObjectOf(name); obj != nil {
|
|
593
|
-
v.trackTypeReference(obj.Type())
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Process each declared variable (LHS)
|
|
598
|
-
for i, lhsIdent := range valueSpec.Names {
|
|
599
|
-
if lhsIdent.Name == "_" {
|
|
600
|
-
continue
|
|
601
|
-
}
|
|
602
|
-
lhsObj := v.pkg.TypesInfo.ObjectOf(lhsIdent)
|
|
603
|
-
if lhsObj == nil {
|
|
604
|
-
continue
|
|
605
|
-
}
|
|
606
|
-
// Check if there's a corresponding initial value (RHS)
|
|
607
|
-
if valueSpec.Values != nil && i < len(valueSpec.Values) {
|
|
608
|
-
rhsExpr := valueSpec.Values[i]
|
|
609
|
-
|
|
610
|
-
// --- Analyze RHS and Update Usage Info (similar to AssignStmt) ---
|
|
611
|
-
assignmentType := DirectAssignment
|
|
612
|
-
var sourceObj types.Object
|
|
613
|
-
shouldTrackUsage := true
|
|
614
|
-
|
|
615
|
-
if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
|
|
616
|
-
// Case: var lhs = &rhs_expr
|
|
617
|
-
assignmentType = AddressOfAssignment
|
|
618
|
-
if rhsIdent, ok := unaryExpr.X.(*ast.Ident); ok {
|
|
619
|
-
// Case: var lhs = &rhs_ident (taking address of a variable)
|
|
620
|
-
sourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
621
|
-
} else {
|
|
622
|
-
// Case: var lhs = &CompositeLit{} (taking address of composite literal)
|
|
623
|
-
// No variable tracking needed - this doesn't create VarRef requirements
|
|
624
|
-
shouldTrackUsage = false
|
|
625
|
-
}
|
|
626
|
-
} else if rhsIdent, ok := rhsExpr.(*ast.Ident); ok {
|
|
627
|
-
// Case: var lhs = rhs_ident
|
|
628
|
-
assignmentType = DirectAssignment
|
|
629
|
-
sourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
630
|
-
} else if funcLit, ok := rhsExpr.(*ast.FuncLit); ok {
|
|
631
|
-
// Case: var lhs = func(...) { ... }
|
|
632
|
-
// Track function literal assignment
|
|
633
|
-
v.analysis.FunctionAssignments[lhsObj] = funcLit
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// --- Record Usage ---
|
|
637
|
-
// Only create usage tracking if we're dealing with variable references
|
|
638
|
-
if shouldTrackUsage {
|
|
639
|
-
// Ensure usage info exists for LHS only when needed
|
|
640
|
-
lhsUsageInfo := v.getOrCreateUsageInfo(lhsObj)
|
|
641
|
-
|
|
642
|
-
if sourceObj != nil {
|
|
643
|
-
// Record source for LHS
|
|
644
|
-
lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
|
|
645
|
-
Object: sourceObj,
|
|
646
|
-
Type: assignmentType,
|
|
647
|
-
})
|
|
648
|
-
|
|
649
|
-
// Record destination for RHS source
|
|
650
|
-
sourceUsageInfo := v.getOrCreateUsageInfo(sourceObj)
|
|
651
|
-
sourceUsageInfo.Destinations = append(sourceUsageInfo.Destinations, AssignmentInfo{
|
|
652
|
-
Object: lhsObj,
|
|
653
|
-
Type: assignmentType,
|
|
654
|
-
})
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return v
|
|
663
|
-
|
|
664
|
-
case *ast.FuncDecl:
|
|
665
|
-
return v.visitFuncDecl(n)
|
|
666
|
-
|
|
667
|
-
case *ast.FuncLit:
|
|
668
|
-
return v.visitFuncLit(n)
|
|
669
|
-
|
|
670
|
-
case *ast.BlockStmt:
|
|
671
|
-
return v.visitBlockStmt(n)
|
|
672
|
-
|
|
673
|
-
case *ast.UnaryExpr:
|
|
674
|
-
// We handle address-of (&) within AssignStmt where it's actually used.
|
|
675
|
-
return v
|
|
676
|
-
|
|
677
|
-
case *ast.CallExpr:
|
|
678
|
-
return v.visitCallExpr(n)
|
|
679
|
-
|
|
680
|
-
case *ast.SelectorExpr:
|
|
681
|
-
return v.visitSelectorExpr(n)
|
|
682
|
-
|
|
683
|
-
case *ast.AssignStmt:
|
|
684
|
-
return v.visitAssignStmt(n)
|
|
685
|
-
|
|
686
|
-
case *ast.ReturnStmt:
|
|
687
|
-
return v.visitReturnStmt(n)
|
|
688
|
-
|
|
689
|
-
case *ast.DeclStmt:
|
|
690
|
-
return v.visitDeclStmt(n)
|
|
691
|
-
|
|
692
|
-
case *ast.IfStmt:
|
|
693
|
-
return v.visitIfStmt(n)
|
|
694
|
-
|
|
695
|
-
case *ast.TypeAssertExpr:
|
|
696
|
-
return v.visitTypeAssertExpr(n)
|
|
697
|
-
|
|
698
|
-
case *ast.CompositeLit:
|
|
699
|
-
// Traverse into composite literal elements to detect &variable expressions
|
|
700
|
-
// This is important for cases like: arr := []interface{}{value1, &value2}
|
|
701
|
-
// where value2 needs to be marked as NeedsVarRef due to the &value2 usage
|
|
702
|
-
return v.visitCompositeLit(n)
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// For all other nodes, continue traversal
|
|
706
|
-
return v
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// visitFuncDecl handles function declaration analysis
|
|
710
|
-
func (v *analysisVisitor) visitFuncDecl(n *ast.FuncDecl) ast.Visitor {
|
|
711
|
-
// Save original states to restore after visiting
|
|
712
|
-
originalInAsync := v.inAsyncFunction
|
|
713
|
-
originalFuncObj := v.currentFuncObj
|
|
714
|
-
originalFuncDecl := v.currentFuncDecl
|
|
715
|
-
originalFuncLit := v.currentFuncLit
|
|
716
|
-
originalReceiver := v.currentReceiver
|
|
717
|
-
|
|
718
|
-
// Reset for current function
|
|
719
|
-
v.currentFuncName = n.Name.Name
|
|
720
|
-
v.currentFuncDecl = n
|
|
721
|
-
v.currentFuncLit = nil
|
|
722
|
-
v.currentReceiver = nil
|
|
723
|
-
|
|
724
|
-
nodeInfo := v.analysis.ensureNodeData(n)
|
|
725
|
-
// InAsyncContext will be set by the second analysis phase
|
|
726
|
-
|
|
727
|
-
// Set current receiver if this is a method
|
|
728
|
-
if n.Recv != nil && len(n.Recv.List) > 0 {
|
|
729
|
-
// Assuming a single receiver for simplicity for now
|
|
730
|
-
if len(n.Recv.List[0].Names) > 0 {
|
|
731
|
-
if ident := n.Recv.List[0].Names[0]; ident != nil && ident.Name != "_" {
|
|
732
|
-
if def := v.pkg.TypesInfo.Defs[ident]; def != nil {
|
|
733
|
-
if vr, ok := def.(*types.Var); ok {
|
|
734
|
-
v.currentReceiver = vr
|
|
735
|
-
// Add the receiver variable to the VariableUsage map
|
|
736
|
-
v.getOrCreateUsageInfo(v.currentReceiver)
|
|
737
|
-
|
|
738
|
-
// Check if receiver is used in method body
|
|
739
|
-
receiverUsed := false
|
|
740
|
-
if n.Body != nil {
|
|
741
|
-
receiverUsed = v.containsReceiverUsage(n.Body, vr)
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// Update function data with receiver usage info
|
|
745
|
-
if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
|
|
746
|
-
funcInfo := v.analysis.ensureFunctionData(obj)
|
|
747
|
-
funcInfo.ReceiverUsed = receiverUsed
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// If receiver is used, mark all identifiers that refer to the receiver variable
|
|
751
|
-
if receiverUsed && n.Body != nil {
|
|
752
|
-
recvName := ident.Name
|
|
753
|
-
ast.Inspect(n.Body, func(nn ast.Node) bool {
|
|
754
|
-
id, ok := nn.(*ast.Ident)
|
|
755
|
-
if !ok {
|
|
756
|
-
return true
|
|
757
|
-
}
|
|
758
|
-
if obj := v.pkg.TypesInfo.Uses[id]; obj != nil && obj == vr {
|
|
759
|
-
ni := v.analysis.ensureNodeData(id)
|
|
760
|
-
ni.IdentifierMapping = recvName
|
|
761
|
-
}
|
|
762
|
-
return true
|
|
763
|
-
})
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Store named return variables (sanitized for TypeScript)
|
|
772
|
-
if n.Type != nil && n.Type.Results != nil {
|
|
773
|
-
var namedReturns []string
|
|
774
|
-
for _, field := range n.Type.Results.List {
|
|
775
|
-
for _, name := range field.Names {
|
|
776
|
-
namedReturns = append(namedReturns, sanitizeIdentifier(name.Name))
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
if len(namedReturns) > 0 {
|
|
780
|
-
if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
|
|
781
|
-
funcInfo := v.analysis.ensureFunctionData(obj)
|
|
782
|
-
funcInfo.NamedReturns = namedReturns
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Update visitor state for this function
|
|
788
|
-
// Note: inAsyncFunction will be determined later by comprehensive analysis phase
|
|
789
|
-
v.currentFuncObj = v.pkg.TypesInfo.ObjectOf(n.Name)
|
|
790
|
-
|
|
791
|
-
if n.Body != nil {
|
|
792
|
-
// Check if the body contains any defer statements
|
|
793
|
-
if v.containsDefer(n.Body) {
|
|
794
|
-
nodeInfo.NeedsDefer = true
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// Visit the body with updated state
|
|
798
|
-
ast.Walk(v, n.Body)
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Restore states after visiting
|
|
802
|
-
defer func() {
|
|
803
|
-
v.currentFuncName = ""
|
|
804
|
-
v.inAsyncFunction = originalInAsync
|
|
805
|
-
v.currentReceiver = originalReceiver
|
|
806
|
-
v.currentFuncObj = originalFuncObj
|
|
807
|
-
v.currentFuncDecl = originalFuncDecl
|
|
808
|
-
v.currentFuncLit = originalFuncLit
|
|
809
|
-
}()
|
|
810
|
-
return nil // Stop traversal here, ast.Walk handled the body
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// visitFuncLit handles function literal analysis
|
|
814
|
-
func (v *analysisVisitor) visitFuncLit(n *ast.FuncLit) ast.Visitor {
|
|
815
|
-
// Save original inAsyncFunction state to restore after visiting
|
|
816
|
-
originalInAsync := v.inAsyncFunction
|
|
817
|
-
originalFuncDecl := v.currentFuncDecl
|
|
818
|
-
originalFuncLit := v.currentFuncLit
|
|
819
|
-
|
|
820
|
-
// Set current function literal
|
|
821
|
-
v.currentFuncDecl = nil
|
|
822
|
-
v.currentFuncLit = n
|
|
823
|
-
|
|
824
|
-
// Note: Function literal async analysis is handled by comprehensive analysis phase
|
|
825
|
-
nodeInfo := v.analysis.ensureNodeData(n)
|
|
826
|
-
|
|
827
|
-
// Store named return variables for function literal
|
|
828
|
-
if n.Type != nil && n.Type.Results != nil {
|
|
829
|
-
var namedReturns []string
|
|
830
|
-
for _, field := range n.Type.Results.List {
|
|
831
|
-
for _, name := range field.Names {
|
|
832
|
-
namedReturns = append(namedReturns, name.Name)
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if len(namedReturns) > 0 {
|
|
836
|
-
v.analysis.FuncLitData[n] = &FunctionInfo{
|
|
837
|
-
NamedReturns: namedReturns,
|
|
838
|
-
// IsAsync will be set by comprehensive analysis
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// Check if the body contains any defer statements
|
|
844
|
-
if n.Body != nil && v.containsDefer(n.Body) {
|
|
845
|
-
nodeInfo.NeedsDefer = true
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// Visit the body with updated state
|
|
849
|
-
ast.Walk(v, n.Body)
|
|
850
|
-
|
|
851
|
-
// Restore inAsyncFunction state after visiting
|
|
852
|
-
v.inAsyncFunction = originalInAsync
|
|
853
|
-
v.currentFuncDecl = originalFuncDecl
|
|
854
|
-
v.currentFuncLit = originalFuncLit
|
|
855
|
-
return nil // Stop traversal here, ast.Walk handled the body
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// visitBlockStmt handles block statement analysis
|
|
859
|
-
func (v *analysisVisitor) visitBlockStmt(n *ast.BlockStmt) ast.Visitor {
|
|
860
|
-
if n == nil || len(n.List) == 0 {
|
|
861
|
-
return v
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// Initialize NodeData for this block
|
|
865
|
-
nodeInfo := v.analysis.ensureNodeData(n)
|
|
866
|
-
|
|
867
|
-
// Check for defer statements in this block
|
|
868
|
-
if v.containsDefer(n) {
|
|
869
|
-
nodeInfo.NeedsDefer = true
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
return v
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// visitCallExpr handles call expression analysis
|
|
876
|
-
func (v *analysisVisitor) visitCallExpr(n *ast.CallExpr) ast.Visitor {
|
|
877
|
-
// Check for reflect function calls that operate on functions
|
|
878
|
-
v.checkReflectUsage(n)
|
|
879
|
-
|
|
880
|
-
// Track interface implementations from function call arguments
|
|
881
|
-
v.trackInterfaceCallArguments(n)
|
|
882
|
-
|
|
883
|
-
// Check for implicit address-taking in method calls with pointer receivers
|
|
884
|
-
v.checkImplicitAddressTaking(n)
|
|
885
|
-
|
|
886
|
-
// Check for address-of expressions in function arguments
|
|
887
|
-
v.checkAddressOfInArguments(n)
|
|
888
|
-
|
|
889
|
-
return v
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// checkAddressOfInArguments detects when &variable is passed as a function argument.
|
|
893
|
-
// Example: json.Unmarshal(data, &person) where person needs to be marked as NeedsVarRef
|
|
894
|
-
func (v *analysisVisitor) checkAddressOfInArguments(callExpr *ast.CallExpr) {
|
|
895
|
-
for _, arg := range callExpr.Args {
|
|
896
|
-
if unaryExpr, ok := arg.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
|
|
897
|
-
if ident, ok := unaryExpr.X.(*ast.Ident); ok {
|
|
898
|
-
if obj := v.pkg.TypesInfo.ObjectOf(ident); obj != nil {
|
|
899
|
-
usageInfo := v.getOrCreateUsageInfo(obj)
|
|
900
|
-
usageInfo.Destinations = append(usageInfo.Destinations, AssignmentInfo{
|
|
901
|
-
Object: nil,
|
|
902
|
-
Type: AddressOfAssignment,
|
|
903
|
-
})
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// checkImplicitAddressTaking detects when a method call with a pointer receiver
|
|
911
|
-
// is called on a non-pointer variable, which requires implicit address-taking.
|
|
912
|
-
// Example: var s MySlice; s.Add(10) where Add has receiver *MySlice
|
|
913
|
-
// This is equivalent to (&s).Add(10), so s needs to be marked as NeedsVarRef
|
|
914
|
-
func (v *analysisVisitor) checkImplicitAddressTaking(callExpr *ast.CallExpr) {
|
|
915
|
-
// Check if this is a method call (selector expression)
|
|
916
|
-
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
917
|
-
if !ok {
|
|
918
|
-
return
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// Get the selection information
|
|
922
|
-
selection := v.pkg.TypesInfo.Selections[selExpr]
|
|
923
|
-
if selection == nil || selection.Kind() != types.MethodVal {
|
|
924
|
-
return
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
// Get the method object
|
|
928
|
-
methodObj := selection.Obj()
|
|
929
|
-
if methodObj == nil {
|
|
930
|
-
return
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
// Get the method's signature to check the receiver type
|
|
934
|
-
methodFunc, ok := methodObj.(*types.Func)
|
|
935
|
-
if !ok {
|
|
936
|
-
return
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
sig := methodFunc.Type().(*types.Signature)
|
|
940
|
-
recv := sig.Recv()
|
|
941
|
-
if recv == nil {
|
|
942
|
-
return
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// Check if the method has a pointer receiver
|
|
946
|
-
recvType := recv.Type()
|
|
947
|
-
_, hasPointerReceiver := recvType.(*types.Pointer)
|
|
948
|
-
if !hasPointerReceiver {
|
|
949
|
-
return
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// Get the type of the receiver expression (the thing before the dot)
|
|
953
|
-
exprType := v.pkg.TypesInfo.TypeOf(selExpr.X)
|
|
954
|
-
if exprType == nil {
|
|
955
|
-
return
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// Check if the receiver expression is NOT already a pointer
|
|
959
|
-
_, exprIsPointer := exprType.(*types.Pointer)
|
|
960
|
-
if exprIsPointer {
|
|
961
|
-
// Expression is already a pointer, no implicit address-taking needed
|
|
962
|
-
return
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// At this point, we have:
|
|
966
|
-
// - A method with a pointer receiver
|
|
967
|
-
// - Being called on a non-pointer expression
|
|
968
|
-
// This means Go will implicitly take the address
|
|
969
|
-
|
|
970
|
-
// Check if the receiver expression is an identifier (variable)
|
|
971
|
-
ident, ok := selExpr.X.(*ast.Ident)
|
|
972
|
-
if !ok {
|
|
973
|
-
return
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Get the variable object
|
|
977
|
-
obj := v.pkg.TypesInfo.ObjectOf(ident)
|
|
978
|
-
if obj == nil {
|
|
979
|
-
return
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// Mark this variable as needing VarRef (its address is being taken)
|
|
983
|
-
usageInfo := v.getOrCreateUsageInfo(obj)
|
|
984
|
-
usageInfo.Destinations = append(usageInfo.Destinations, AssignmentInfo{
|
|
985
|
-
Object: nil, // No specific destination for method calls
|
|
986
|
-
Type: AddressOfAssignment,
|
|
987
|
-
})
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// visitSelectorExpr handles selector expression analysis
|
|
991
|
-
func (v *analysisVisitor) visitSelectorExpr(n *ast.SelectorExpr) ast.Visitor {
|
|
992
|
-
// Check if this is a method value (method being used as a value, not called immediately)
|
|
993
|
-
if selection := v.pkg.TypesInfo.Selections[n]; selection != nil {
|
|
994
|
-
if selection.Kind() == types.MethodVal {
|
|
995
|
-
// This is a method value - mark it for binding during code generation
|
|
996
|
-
nodeInfo := v.analysis.ensureNodeData(n)
|
|
997
|
-
nodeInfo.IsMethodValue = true
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
return v
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// visitAssignStmt handles assignment statement analysis
|
|
1004
|
-
func (v *analysisVisitor) visitAssignStmt(n *ast.AssignStmt) ast.Visitor {
|
|
1005
|
-
// Handle variable assignment tracking and generate shadowing information
|
|
1006
|
-
shadowingInfo := v.detectVariableShadowing(n)
|
|
1007
|
-
|
|
1008
|
-
// Store shadowing information if needed for code generation
|
|
1009
|
-
if shadowingInfo != nil {
|
|
1010
|
-
nodeInfo := v.analysis.ensureNodeData(n)
|
|
1011
|
-
nodeInfo.ShadowingInfo = shadowingInfo
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// Track assignment relationships for pointer analysis
|
|
1015
|
-
for i, lhsExpr := range n.Lhs {
|
|
1016
|
-
if i < len(n.Rhs) {
|
|
1017
|
-
v.analyzeAssignment(lhsExpr, n.Rhs[i])
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// Track interface implementations for assignments to interface variables
|
|
1022
|
-
v.trackInterfaceAssignments(n)
|
|
1023
|
-
|
|
1024
|
-
// Track function assignments (function literals assigned to variables)
|
|
1025
|
-
if len(n.Lhs) == 1 && len(n.Rhs) == 1 {
|
|
1026
|
-
if lhsIdent, ok := n.Lhs[0].(*ast.Ident); ok {
|
|
1027
|
-
if rhsFuncLit, ok := n.Rhs[0].(*ast.FuncLit); ok {
|
|
1028
|
-
// Get the object for the LHS variable
|
|
1029
|
-
if obj := v.pkg.TypesInfo.ObjectOf(lhsIdent); obj != nil {
|
|
1030
|
-
v.analysis.FunctionAssignments[obj] = rhsFuncLit
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// NOTE: Async-returning variable tracking (trackAsyncReturningVar) is done in a separate pass
|
|
1037
|
-
// after function literals are analyzed for async status. See trackAsyncReturningVarsAllFiles.
|
|
1038
|
-
|
|
1039
|
-
return v
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// trackAsyncReturningVar tracks variables that are assigned from higher-order function calls
|
|
1043
|
-
// where one of the arguments is an async function literal.
|
|
1044
|
-
// Pattern: x := higherOrderFunc(asyncFuncLit)
|
|
1045
|
-
// This is needed because when sync.OnceValue(asyncFunc) is called, the result is a function
|
|
1046
|
-
// that returns a Promise, and callers of x() need to await the result.
|
|
1047
|
-
func (v *analysisVisitor) trackAsyncReturningVar(lhs ast.Expr, rhs ast.Expr) {
|
|
1048
|
-
// LHS must be an identifier
|
|
1049
|
-
lhsIdent, ok := lhs.(*ast.Ident)
|
|
1050
|
-
if !ok {
|
|
1051
|
-
return
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
// RHS must be a call expression
|
|
1055
|
-
callExpr, ok := rhs.(*ast.CallExpr)
|
|
1056
|
-
if !ok {
|
|
1057
|
-
return
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// The result type of the call must be a function type
|
|
1061
|
-
rhsType := v.pkg.TypesInfo.TypeOf(rhs)
|
|
1062
|
-
if rhsType == nil {
|
|
1063
|
-
return
|
|
1064
|
-
}
|
|
1065
|
-
_, isFunc := rhsType.Underlying().(*types.Signature)
|
|
1066
|
-
if !isFunc {
|
|
1067
|
-
return
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// Check if any argument is an async function literal
|
|
1071
|
-
// Use containsAsyncOperationsComplete to check the function body directly
|
|
1072
|
-
// rather than relying on the InAsyncContext flag which may not be set yet
|
|
1073
|
-
hasAsyncArg := false
|
|
1074
|
-
for _, arg := range callExpr.Args {
|
|
1075
|
-
if funcLit, ok := arg.(*ast.FuncLit); ok {
|
|
1076
|
-
if funcLit.Body != nil && v.containsAsyncOperationsComplete(funcLit.Body, v.pkg) {
|
|
1077
|
-
hasAsyncArg = true
|
|
1078
|
-
break
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
if !hasAsyncArg {
|
|
1084
|
-
return
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
// Mark the LHS variable as returning async values
|
|
1088
|
-
if obj := v.pkg.TypesInfo.ObjectOf(lhsIdent); obj != nil {
|
|
1089
|
-
v.analysis.AsyncReturningVars[obj] = true
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// visitReturnStmt handles return statement analysis
|
|
1094
|
-
func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
|
|
1095
|
-
nodeInfo := v.analysis.ensureNodeData(n)
|
|
1096
|
-
|
|
1097
|
-
// Record the enclosing function/literal for this return statement
|
|
1098
|
-
if v.currentFuncDecl != nil {
|
|
1099
|
-
nodeInfo.EnclosingFuncDecl = v.currentFuncDecl
|
|
1100
|
-
} else if v.currentFuncLit != nil {
|
|
1101
|
-
nodeInfo.EnclosingFuncLit = v.currentFuncLit
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// Check if it's a bare return
|
|
1105
|
-
if len(n.Results) == 0 {
|
|
1106
|
-
if v.analysis.GetFunctionInfoFromContext(nodeInfo, v.pkg) != nil {
|
|
1107
|
-
nodeInfo.IsBareReturn = true
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
return v
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// visitDeclStmt handles declaration statement analysis
|
|
1114
|
-
func (v *analysisVisitor) visitDeclStmt(n *ast.DeclStmt) ast.Visitor {
|
|
1115
|
-
// Handle declarations inside functions (const, var, type declarations within function bodies)
|
|
1116
|
-
// These should not have export modifiers in TypeScript
|
|
1117
|
-
if genDecl, ok := n.Decl.(*ast.GenDecl); ok {
|
|
1118
|
-
// Check if we're inside a function (either FuncDecl or FuncLit)
|
|
1119
|
-
isInsideFunction := v.currentFuncDecl != nil || v.currentFuncLit != nil
|
|
1120
|
-
|
|
1121
|
-
for _, spec := range genDecl.Specs {
|
|
1122
|
-
if isInsideFunction {
|
|
1123
|
-
// Mark all specs in this declaration as being inside a function
|
|
1124
|
-
nodeInfo := v.analysis.ensureNodeData(spec)
|
|
1125
|
-
nodeInfo.IsInsideFunction = true
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
// Track type references from variable declarations (e.g., var w MyWriter)
|
|
1129
|
-
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
1130
|
-
// Track explicit type if present
|
|
1131
|
-
if valueSpec.Type != nil {
|
|
1132
|
-
if t := v.pkg.TypesInfo.TypeOf(valueSpec.Type); t != nil {
|
|
1133
|
-
v.trackTypeReference(t)
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
// Also track types inferred from values
|
|
1137
|
-
for _, name := range valueSpec.Names {
|
|
1138
|
-
if obj := v.pkg.TypesInfo.ObjectOf(name); obj != nil {
|
|
1139
|
-
v.trackTypeReference(obj.Type())
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
return v
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
// visitIfStmt handles if statement analysis
|
|
1149
|
-
func (v *analysisVisitor) visitIfStmt(n *ast.IfStmt) ast.Visitor {
|
|
1150
|
-
// Detect variable shadowing in if statement initializations
|
|
1151
|
-
if n.Init != nil {
|
|
1152
|
-
if assignStmt, ok := n.Init.(*ast.AssignStmt); ok && assignStmt.Tok == token.DEFINE {
|
|
1153
|
-
shadowingInfo := v.detectVariableShadowing(assignStmt)
|
|
1154
|
-
if shadowingInfo != nil {
|
|
1155
|
-
nodeInfo := v.analysis.ensureNodeData(n)
|
|
1156
|
-
nodeInfo.ShadowingInfo = shadowingInfo
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
return v
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
// visitTypeAssertExpr handles type assertion analysis for interface method implementations
|
|
1164
|
-
func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) ast.Visitor {
|
|
1165
|
-
// Get the type being asserted to
|
|
1166
|
-
assertedType := v.pkg.TypesInfo.TypeOf(typeAssert.Type)
|
|
1167
|
-
if assertedType == nil {
|
|
1168
|
-
return v
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
// Track the asserted type for synthetic import filtering
|
|
1172
|
-
v.trackTypeReference(assertedType)
|
|
1173
|
-
|
|
1174
|
-
// Check if the asserted type is an interface
|
|
1175
|
-
interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
|
|
1176
|
-
if !isInterface {
|
|
1177
|
-
return v
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
// Get the type of the expression being asserted
|
|
1181
|
-
exprType := v.pkg.TypesInfo.TypeOf(typeAssert.X)
|
|
1182
|
-
if exprType == nil {
|
|
1183
|
-
return v
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
// Handle pointer types by getting the element type
|
|
1187
|
-
if ptrType, isPtr := exprType.(*types.Pointer); isPtr {
|
|
1188
|
-
exprType = ptrType.Elem()
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
// Check if the expression type is a named struct type
|
|
1192
|
-
namedType, isNamed := exprType.(*types.Named)
|
|
1193
|
-
if !isNamed {
|
|
1194
|
-
return v
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
// For each method in the interface, check if the struct implements it
|
|
1198
|
-
for interfaceMethod := range interfaceType.ExplicitMethods() {
|
|
1199
|
-
|
|
1200
|
-
// Find the corresponding method in the struct type
|
|
1201
|
-
structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
|
|
1202
|
-
if structMethod != nil {
|
|
1203
|
-
// Track this interface implementation
|
|
1204
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
return v
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
// trackTypeReference records that a named type is referenced in the current file.
|
|
1211
|
-
// This is used to filter synthetic imports to only include packages actually needed.
|
|
1212
|
-
func (v *analysisVisitor) trackTypeReference(t types.Type) {
|
|
1213
|
-
if t == nil || v.currentFilePath == "" {
|
|
1214
|
-
return
|
|
1215
|
-
}
|
|
1216
|
-
// Unwrap pointers
|
|
1217
|
-
if ptr, ok := t.(*types.Pointer); ok {
|
|
1218
|
-
t = ptr.Elem()
|
|
1219
|
-
}
|
|
1220
|
-
// Track named types per file
|
|
1221
|
-
if named, ok := t.(*types.Named); ok {
|
|
1222
|
-
if v.analysis.ReferencedTypesPerFile[v.currentFilePath] == nil {
|
|
1223
|
-
v.analysis.ReferencedTypesPerFile[v.currentFilePath] = make(map[*types.Named]bool)
|
|
1224
|
-
}
|
|
1225
|
-
v.analysis.ReferencedTypesPerFile[v.currentFilePath][named] = true
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// visitCompositeLit analyzes composite literals for address-of expressions
|
|
1230
|
-
// This is important for detecting cases like: arr := []interface{}{value1, &value2}
|
|
1231
|
-
// where value2 needs to be marked as NeedsVarRef due to the &value2 usage
|
|
1232
|
-
func (v *analysisVisitor) visitCompositeLit(compLit *ast.CompositeLit) ast.Visitor {
|
|
1233
|
-
// Track the type of this composite literal for synthetic import filtering
|
|
1234
|
-
if compLit.Type != nil {
|
|
1235
|
-
if t := v.pkg.TypesInfo.TypeOf(compLit.Type); t != nil {
|
|
1236
|
-
v.trackTypeReference(t)
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
// Analyze each element of the composite literal
|
|
1241
|
-
for _, elt := range compLit.Elts {
|
|
1242
|
-
// Handle both direct elements and key-value pairs
|
|
1243
|
-
var expr ast.Expr
|
|
1244
|
-
if kv, ok := elt.(*ast.KeyValueExpr); ok {
|
|
1245
|
-
// For key-value pairs, analyze the value expression
|
|
1246
|
-
expr = kv.Value
|
|
1247
|
-
} else {
|
|
1248
|
-
// For direct elements, analyze the element expression
|
|
1249
|
-
expr = elt
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// Check if this element is an address-of expression
|
|
1253
|
-
if unaryExpr, ok := expr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
|
|
1254
|
-
// Found &something in the composite literal
|
|
1255
|
-
if ident, ok := unaryExpr.X.(*ast.Ident); ok {
|
|
1256
|
-
// Found &variable - mark the variable as needing VarRef
|
|
1257
|
-
if obj := v.pkg.TypesInfo.ObjectOf(ident); obj != nil {
|
|
1258
|
-
// Record that this variable has its address taken
|
|
1259
|
-
usageInfo := v.getOrCreateUsageInfo(obj)
|
|
1260
|
-
usageInfo.Destinations = append(usageInfo.Destinations, AssignmentInfo{
|
|
1261
|
-
Object: nil, // No specific destination object for composite literals
|
|
1262
|
-
Type: AddressOfAssignment,
|
|
1263
|
-
})
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
return v
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// containsAsyncOperations checks if a node contains any async operations like channel operations.
|
|
1272
|
-
|
|
1273
|
-
// containsDefer checks if a block contains any defer statements.
|
|
1274
|
-
func (v *analysisVisitor) containsDefer(block *ast.BlockStmt) bool {
|
|
1275
|
-
hasDefer := false
|
|
1276
|
-
|
|
1277
|
-
ast.Inspect(block, func(n ast.Node) bool {
|
|
1278
|
-
if n == nil {
|
|
1279
|
-
return true
|
|
1280
|
-
}
|
|
1281
|
-
if _, ok := n.(*ast.DeferStmt); ok {
|
|
1282
|
-
hasDefer = true
|
|
1283
|
-
return false
|
|
1284
|
-
}
|
|
1285
|
-
return true
|
|
1286
|
-
})
|
|
1287
|
-
|
|
1288
|
-
return hasDefer
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
// containsReceiverUsage checks if a method body contains any references to the receiver variable.
|
|
1292
|
-
func (v *analysisVisitor) containsReceiverUsage(node ast.Node, receiver *types.Var) bool {
|
|
1293
|
-
if receiver == nil {
|
|
1294
|
-
return false
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
var hasReceiverUsage bool
|
|
1298
|
-
|
|
1299
|
-
ast.Inspect(node, func(n ast.Node) bool {
|
|
1300
|
-
if n == nil {
|
|
1301
|
-
return true
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
switch expr := n.(type) {
|
|
1305
|
-
case *ast.Ident:
|
|
1306
|
-
// Check if this identifier refers to the receiver variable
|
|
1307
|
-
if obj := v.pkg.TypesInfo.Uses[expr]; obj != nil && obj == receiver {
|
|
1308
|
-
hasReceiverUsage = true
|
|
1309
|
-
return false
|
|
1310
|
-
}
|
|
1311
|
-
case *ast.SelectorExpr:
|
|
1312
|
-
// Check if selector expression uses the receiver (e.g., m.Field, m.Method())
|
|
1313
|
-
if ident, ok := expr.X.(*ast.Ident); ok {
|
|
1314
|
-
if obj := v.pkg.TypesInfo.Uses[ident]; obj != nil && obj == receiver {
|
|
1315
|
-
hasReceiverUsage = true
|
|
1316
|
-
return false
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
return true
|
|
1322
|
-
})
|
|
1323
|
-
|
|
1324
|
-
return hasReceiverUsage
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// AnalyzePackageFiles analyzes all Go source files in a package and populates the Analysis struct
|
|
1328
|
-
// with information that will be used during code generation to properly handle pointers,
|
|
1329
|
-
// variables that need varRefing, receiver usage, etc. This replaces the old file-by-file analysis.
|
|
1330
|
-
func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages.Package) *Analysis {
|
|
1331
|
-
analysis := NewAnalysis(allPackages)
|
|
1332
|
-
|
|
1333
|
-
// Load package metadata for async function detection
|
|
1334
|
-
analysis.LoadPackageMetadata()
|
|
1335
|
-
|
|
1336
|
-
// Process imports from all files in the package
|
|
1337
|
-
for _, file := range pkg.Syntax {
|
|
1338
|
-
// Create comment map for each file and store it (we'll merge them if needed)
|
|
1339
|
-
cmap := ast.NewCommentMap(pkg.Fset, file, file.Comments)
|
|
1340
|
-
if len(analysis.Cmap) == 0 {
|
|
1341
|
-
analysis.Cmap = cmap
|
|
1342
|
-
} else {
|
|
1343
|
-
// Merge comment maps from multiple files
|
|
1344
|
-
for node, comments := range cmap {
|
|
1345
|
-
analysis.Cmap[node] = append(analysis.Cmap[node], comments...)
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
// Process imports from this file
|
|
1350
|
-
for _, imp := range file.Imports {
|
|
1351
|
-
path := ""
|
|
1352
|
-
if imp.Path != nil {
|
|
1353
|
-
path = imp.Path.Value
|
|
1354
|
-
// Remove quotes from the import path string
|
|
1355
|
-
path = path[1 : len(path)-1]
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
// Store the import in the analysis
|
|
1359
|
-
if path != "" {
|
|
1360
|
-
name := ""
|
|
1361
|
-
if imp.Name != nil {
|
|
1362
|
-
name = imp.Name.Name
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
fileImp := &fileImport{
|
|
1366
|
-
importPath: path,
|
|
1367
|
-
importVars: make(map[string]struct{}),
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
// Use the import name or the actual package name as the key
|
|
1371
|
-
var key string
|
|
1372
|
-
if name != "" {
|
|
1373
|
-
// Explicit alias provided
|
|
1374
|
-
key = name
|
|
1375
|
-
} else {
|
|
1376
|
-
// No explicit alias, use the actual package name from type information
|
|
1377
|
-
// This handles cases where package name differs from the last path segment
|
|
1378
|
-
if actualName, err := getActualPackageName(path, pkg.Imports); err == nil {
|
|
1379
|
-
key = actualName
|
|
1380
|
-
} else {
|
|
1381
|
-
// Fallback to last segment of path if package not found in type information
|
|
1382
|
-
pts := strings.Split(path, "/")
|
|
1383
|
-
key = pts[len(pts)-1]
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
analysis.Imports[key] = fileImp
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
// Create visitor for the entire package
|
|
1393
|
-
visitor := &analysisVisitor{
|
|
1394
|
-
analysis: analysis,
|
|
1395
|
-
pkg: pkg,
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// First pass: analyze all declarations and statements across all files
|
|
1399
|
-
for i, file := range pkg.Syntax {
|
|
1400
|
-
// Set the current file path for per-file type tracking
|
|
1401
|
-
if i < len(pkg.CompiledGoFiles) {
|
|
1402
|
-
visitor.currentFilePath = pkg.CompiledGoFiles[i]
|
|
1403
|
-
}
|
|
1404
|
-
ast.Walk(visitor, file)
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// Post-processing: Find all CallExpr nodes and unmark their Fun SelectorExpr as method values
|
|
1408
|
-
// This distinguishes between method calls (obj.Method()) and method values (obj.Method)
|
|
1409
|
-
for _, file := range pkg.Syntax {
|
|
1410
|
-
ast.Inspect(file, func(n ast.Node) bool {
|
|
1411
|
-
if callExpr, ok := n.(*ast.CallExpr); ok {
|
|
1412
|
-
if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
|
|
1413
|
-
// This SelectorExpr is the function being called, so it's NOT a method value
|
|
1414
|
-
if nodeInfo := analysis.NodeData[selExpr]; nodeInfo != nil {
|
|
1415
|
-
nodeInfo.IsMethodValue = false
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
return true
|
|
1420
|
-
})
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
// Second pass: analyze interface implementations first
|
|
1424
|
-
interfaceVisitor := &interfaceImplementationVisitor{
|
|
1425
|
-
analysis: analysis,
|
|
1426
|
-
pkg: pkg,
|
|
1427
|
-
}
|
|
1428
|
-
for _, file := range pkg.Syntax {
|
|
1429
|
-
ast.Walk(interfaceVisitor, file)
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
// Third pass: comprehensive async analysis for all methods
|
|
1433
|
-
// Interface implementation async status is now updated on-demand in IsInterfaceMethodAsync
|
|
1434
|
-
visitor.analyzeAllMethodsAsync()
|
|
1435
|
-
|
|
1436
|
-
// Fourth pass: collect imports needed by promoted methods from embedded structs
|
|
1437
|
-
analysis.addImportsForPromotedMethods(pkg)
|
|
1438
|
-
|
|
1439
|
-
return analysis
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
// addImportsForPromotedMethods scans struct types that are actually referenced in each file
|
|
1443
|
-
// and adds imports for any packages referenced by the promoted methods' parameter/return types.
|
|
1444
|
-
// This generates per-file synthetic imports to avoid adding unused imports.
|
|
1445
|
-
func (a *Analysis) addImportsForPromotedMethods(pkg *packages.Package) {
|
|
1446
|
-
// Process each file's referenced types separately
|
|
1447
|
-
for filePath, referencedTypes := range a.ReferencedTypesPerFile {
|
|
1448
|
-
// Collect package imports needed for this specific file
|
|
1449
|
-
packagesToAdd := make(map[string]*types.Package)
|
|
1450
|
-
|
|
1451
|
-
// Only process types that are actually referenced in this file
|
|
1452
|
-
// and are defined in the current package
|
|
1453
|
-
for namedType := range referencedTypes {
|
|
1454
|
-
// Skip types from other packages - we only need to process types defined in this package
|
|
1455
|
-
if namedType.Obj().Pkg() != pkg.Types {
|
|
1456
|
-
continue
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
// Check if it's a struct
|
|
1460
|
-
structType, ok := namedType.Underlying().(*types.Struct)
|
|
1461
|
-
if !ok {
|
|
1462
|
-
continue
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
// Look for embedded fields
|
|
1466
|
-
for field := range structType.Fields() {
|
|
1467
|
-
if !field.Embedded() {
|
|
1468
|
-
continue
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
// Get the type of the embedded field
|
|
1472
|
-
embeddedType := field.Type()
|
|
1473
|
-
|
|
1474
|
-
// Handle pointer to embedded type
|
|
1475
|
-
if ptr, ok := embeddedType.(*types.Pointer); ok {
|
|
1476
|
-
embeddedType = ptr.Elem()
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
// Use method set to get all promoted methods including pointer receiver methods
|
|
1480
|
-
// This matches Go's behavior where embedding T promotes both T and *T methods
|
|
1481
|
-
methodSetType := embeddedType
|
|
1482
|
-
if _, isPtr := embeddedType.(*types.Pointer); !isPtr {
|
|
1483
|
-
if _, isInterface := embeddedType.Underlying().(*types.Interface); !isInterface {
|
|
1484
|
-
methodSetType = types.NewPointer(embeddedType)
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
embeddedMethodSet := types.NewMethodSet(methodSetType)
|
|
1488
|
-
|
|
1489
|
-
// Scan all methods in the method set
|
|
1490
|
-
for selection := range embeddedMethodSet.Methods() {
|
|
1491
|
-
method := selection.Obj()
|
|
1492
|
-
sig, ok := method.Type().(*types.Signature)
|
|
1493
|
-
if !ok {
|
|
1494
|
-
continue
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// Scan parameters
|
|
1498
|
-
if sig.Params() != nil {
|
|
1499
|
-
for param := range sig.Params().Variables() {
|
|
1500
|
-
a.collectPackageFromType(param.Type(), pkg.Types, packagesToAdd)
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
// Scan results
|
|
1505
|
-
if sig.Results() != nil {
|
|
1506
|
-
for result := range sig.Results().Variables() {
|
|
1507
|
-
a.collectPackageFromType(result.Type(), pkg.Types, packagesToAdd)
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
// Store the synthetic imports for this file
|
|
1515
|
-
if len(packagesToAdd) > 0 {
|
|
1516
|
-
fileImports := make(map[string]*fileImport)
|
|
1517
|
-
for pkgName, pkgObj := range packagesToAdd {
|
|
1518
|
-
tsImportPath := translateGoImportPathToTypescriptModulePath(pkgObj.Path())
|
|
1519
|
-
fileImports[pkgName] = &fileImport{
|
|
1520
|
-
importPath: tsImportPath,
|
|
1521
|
-
importVars: make(map[string]struct{}),
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
a.SyntheticImportsPerFile[filePath] = fileImports
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
// collectPackageFromType recursively collects packages referenced by a type.
|
|
1530
|
-
func (a *Analysis) collectPackageFromType(t types.Type, currentPkg *types.Package, packagesToAdd map[string]*types.Package) {
|
|
1531
|
-
switch typ := t.(type) {
|
|
1532
|
-
case *types.Named:
|
|
1533
|
-
pkg := typ.Obj().Pkg()
|
|
1534
|
-
if pkg != nil && pkg != currentPkg {
|
|
1535
|
-
packagesToAdd[pkg.Name()] = pkg
|
|
1536
|
-
}
|
|
1537
|
-
// Check type arguments for generics
|
|
1538
|
-
if typ.TypeArgs() != nil {
|
|
1539
|
-
for t := range typ.TypeArgs().Types() {
|
|
1540
|
-
a.collectPackageFromType(t, currentPkg, packagesToAdd)
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
case *types.Interface:
|
|
1544
|
-
// For interfaces, we need to check embedded interfaces and method signatures
|
|
1545
|
-
for etyp := range typ.EmbeddedTypes() {
|
|
1546
|
-
a.collectPackageFromType(etyp, currentPkg, packagesToAdd)
|
|
1547
|
-
}
|
|
1548
|
-
for method := range typ.ExplicitMethods() {
|
|
1549
|
-
a.collectPackageFromType(method.Type(), currentPkg, packagesToAdd)
|
|
1550
|
-
}
|
|
1551
|
-
case *types.Pointer:
|
|
1552
|
-
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1553
|
-
case *types.Slice:
|
|
1554
|
-
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1555
|
-
case *types.Array:
|
|
1556
|
-
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1557
|
-
case *types.Map:
|
|
1558
|
-
a.collectPackageFromType(typ.Key(), currentPkg, packagesToAdd)
|
|
1559
|
-
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1560
|
-
case *types.Chan:
|
|
1561
|
-
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1562
|
-
case *types.Signature:
|
|
1563
|
-
// Collect from parameters
|
|
1564
|
-
if typ.Params() != nil {
|
|
1565
|
-
for v := range typ.Params().Variables() {
|
|
1566
|
-
a.collectPackageFromType(v.Type(), currentPkg, packagesToAdd)
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
// Collect from results
|
|
1570
|
-
if typ.Results() != nil {
|
|
1571
|
-
for v := range typ.Results().Variables() {
|
|
1572
|
-
a.collectPackageFromType(v.Type(), currentPkg, packagesToAdd)
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
// AnalyzePackageImports performs package-level analysis to collect function definitions
|
|
1579
|
-
// and calls across all files in the package for auto-import generation
|
|
1580
|
-
func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
1581
|
-
analysis := NewPackageAnalysis()
|
|
1582
|
-
|
|
1583
|
-
// First pass: collect all function definitions per file
|
|
1584
|
-
for i, syntax := range pkg.Syntax {
|
|
1585
|
-
fileName := pkg.CompiledGoFiles[i]
|
|
1586
|
-
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1587
|
-
|
|
1588
|
-
var functions []string
|
|
1589
|
-
var typeNames []string
|
|
1590
|
-
var variables []string
|
|
1591
|
-
for _, decl := range syntax.Decls {
|
|
1592
|
-
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
1593
|
-
// Collect top-level functions (not methods)
|
|
1594
|
-
if funcDecl.Recv == nil {
|
|
1595
|
-
functions = append(functions, funcDecl.Name.Name)
|
|
1596
|
-
} else {
|
|
1597
|
-
// Check if this is a method on a wrapper type (named basic type)
|
|
1598
|
-
// If so, it will be compiled as TypeName_MethodName function
|
|
1599
|
-
if len(funcDecl.Recv.List) > 0 {
|
|
1600
|
-
recvType := funcDecl.Recv.List[0].Type
|
|
1601
|
-
// Handle pointer receiver (*Type)
|
|
1602
|
-
if starExpr, ok := recvType.(*ast.StarExpr); ok {
|
|
1603
|
-
recvType = starExpr.X
|
|
1604
|
-
}
|
|
1605
|
-
if recvIdent, ok := recvType.(*ast.Ident); ok {
|
|
1606
|
-
// Check if this receiver type is a wrapper type
|
|
1607
|
-
if obj := pkg.TypesInfo.Uses[recvIdent]; obj != nil {
|
|
1608
|
-
if typeName, ok := obj.(*types.TypeName); ok {
|
|
1609
|
-
if namedType, ok := typeName.Type().(*types.Named); ok {
|
|
1610
|
-
if _, ok := namedType.Underlying().(*types.Basic); ok {
|
|
1611
|
-
// This is a method on a wrapper type
|
|
1612
|
-
funcName := recvIdent.Name + "_" + funcDecl.Name.Name
|
|
1613
|
-
functions = append(functions, funcName)
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
if genDecl, ok := decl.(*ast.GenDecl); ok {
|
|
1623
|
-
// Collect type declarations
|
|
1624
|
-
for _, spec := range genDecl.Specs {
|
|
1625
|
-
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
1626
|
-
typeNames = append(typeNames, typeSpec.Name.Name)
|
|
1627
|
-
}
|
|
1628
|
-
// Collect variable/constant declarations
|
|
1629
|
-
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
1630
|
-
for _, name := range valueSpec.Names {
|
|
1631
|
-
variables = append(variables, name.Name)
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
if len(functions) > 0 {
|
|
1639
|
-
analysis.FunctionDefs[baseFileName] = functions
|
|
1640
|
-
}
|
|
1641
|
-
if len(typeNames) > 0 {
|
|
1642
|
-
analysis.TypeDefs[baseFileName] = typeNames
|
|
1643
|
-
}
|
|
1644
|
-
if len(variables) > 0 {
|
|
1645
|
-
analysis.VariableDefs[baseFileName] = variables
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
// Second pass: analyze function calls and determine which need imports
|
|
1650
|
-
for i, syntax := range pkg.Syntax {
|
|
1651
|
-
fileName := pkg.CompiledGoFiles[i]
|
|
1652
|
-
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1653
|
-
|
|
1654
|
-
// Find all function calls in this file
|
|
1655
|
-
callsFromOtherFiles := make(map[string][]string)
|
|
1656
|
-
|
|
1657
|
-
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1658
|
-
if callExpr, ok := n.(*ast.CallExpr); ok {
|
|
1659
|
-
if ident, ok := callExpr.Fun.(*ast.Ident); ok {
|
|
1660
|
-
funcName := ident.Name
|
|
1661
|
-
|
|
1662
|
-
// Check if this function is defined in the current file
|
|
1663
|
-
currentFileFuncs := analysis.FunctionDefs[baseFileName]
|
|
1664
|
-
isDefinedInCurrentFile := slices.Contains(currentFileFuncs, funcName)
|
|
1665
|
-
|
|
1666
|
-
// If not defined in current file, find which file defines it
|
|
1667
|
-
if !isDefinedInCurrentFile {
|
|
1668
|
-
for sourceFile, funcs := range analysis.FunctionDefs {
|
|
1669
|
-
if sourceFile == baseFileName {
|
|
1670
|
-
continue // Skip current file
|
|
1671
|
-
}
|
|
1672
|
-
if slices.Contains(funcs, funcName) {
|
|
1673
|
-
// Found the function in another file
|
|
1674
|
-
if callsFromOtherFiles[sourceFile] == nil {
|
|
1675
|
-
callsFromOtherFiles[sourceFile] = []string{}
|
|
1676
|
-
}
|
|
1677
|
-
// Check if already added to avoid duplicates
|
|
1678
|
-
found := slices.Contains(callsFromOtherFiles[sourceFile], funcName)
|
|
1679
|
-
if !found {
|
|
1680
|
-
callsFromOtherFiles[sourceFile] = append(callsFromOtherFiles[sourceFile], funcName)
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
return true
|
|
1688
|
-
})
|
|
1689
|
-
|
|
1690
|
-
if len(callsFromOtherFiles) > 0 {
|
|
1691
|
-
analysis.FunctionCalls[baseFileName] = callsFromOtherFiles
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// Third pass: analyze type references and determine which need imports
|
|
1696
|
-
for i, syntax := range pkg.Syntax {
|
|
1697
|
-
fileName := pkg.CompiledGoFiles[i]
|
|
1698
|
-
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1699
|
-
|
|
1700
|
-
// Find all type references in this file
|
|
1701
|
-
typeRefsFromOtherFiles := make(map[string][]string)
|
|
1702
|
-
|
|
1703
|
-
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1704
|
-
// Look for type references in struct fields, function parameters, etc.
|
|
1705
|
-
if ident, ok := n.(*ast.Ident); ok {
|
|
1706
|
-
// Check if this identifier refers to a type
|
|
1707
|
-
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
1708
|
-
if _, ok := obj.(*types.TypeName); ok {
|
|
1709
|
-
typeName := ident.Name
|
|
1710
|
-
|
|
1711
|
-
// Check if this type is defined in the current file
|
|
1712
|
-
currentFileTypes := analysis.TypeDefs[baseFileName]
|
|
1713
|
-
isDefinedInCurrentFile := slices.Contains(currentFileTypes, typeName)
|
|
1714
|
-
|
|
1715
|
-
// If not defined in current file, find which file defines it
|
|
1716
|
-
if !isDefinedInCurrentFile {
|
|
1717
|
-
for sourceFile, types := range analysis.TypeDefs {
|
|
1718
|
-
if sourceFile == baseFileName {
|
|
1719
|
-
continue // Skip current file
|
|
1720
|
-
}
|
|
1721
|
-
if slices.Contains(types, typeName) {
|
|
1722
|
-
// Found the type in another file
|
|
1723
|
-
if typeRefsFromOtherFiles[sourceFile] == nil {
|
|
1724
|
-
typeRefsFromOtherFiles[sourceFile] = []string{}
|
|
1725
|
-
}
|
|
1726
|
-
// Check if already added to avoid duplicates
|
|
1727
|
-
found := slices.Contains(typeRefsFromOtherFiles[sourceFile], typeName)
|
|
1728
|
-
if !found {
|
|
1729
|
-
typeRefsFromOtherFiles[sourceFile] = append(typeRefsFromOtherFiles[sourceFile], typeName)
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
if callExpr, ok := n.(*ast.CallExpr); ok {
|
|
1739
|
-
if funIdent, ok := callExpr.Fun.(*ast.Ident); ok && funIdent.Name == "make" && len(callExpr.Args) > 0 {
|
|
1740
|
-
if typ := pkg.TypesInfo.TypeOf(callExpr.Args[0]); typ != nil {
|
|
1741
|
-
if chanType, ok := typ.Underlying().(*types.Chan); ok {
|
|
1742
|
-
addTypeRefsFromZeroValue(analysis, baseFileName, chanType.Elem(), typeRefsFromOtherFiles)
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
if indexExpr, ok := n.(*ast.IndexExpr); ok {
|
|
1749
|
-
if tv, ok := pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
1750
|
-
if mapType, ok := tv.Type.Underlying().(*types.Map); ok {
|
|
1751
|
-
addTypeRefsFromZeroValue(analysis, baseFileName, mapType.Elem(), typeRefsFromOtherFiles)
|
|
1752
|
-
return true
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
if typeParam, ok := tv.Type.(*types.TypeParam); ok {
|
|
1756
|
-
constraint := typeParam.Constraint()
|
|
1757
|
-
if constraint == nil {
|
|
1758
|
-
return true
|
|
1759
|
-
}
|
|
1760
|
-
if iface, ok := constraint.Underlying().(*types.Interface); ok && hasMapConstraint(iface) {
|
|
1761
|
-
if mapValueType := getMapValueTypeFromConstraint(iface); mapValueType != nil {
|
|
1762
|
-
addTypeRefsFromZeroValue(analysis, baseFileName, mapValueType, typeRefsFromOtherFiles)
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
return true
|
|
1769
|
-
})
|
|
1770
|
-
|
|
1771
|
-
if len(typeRefsFromOtherFiles) > 0 {
|
|
1772
|
-
analysis.TypeCalls[baseFileName] = typeRefsFromOtherFiles
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
// Fourth pass: analyze variable references and determine which need imports
|
|
1777
|
-
for i, syntax := range pkg.Syntax {
|
|
1778
|
-
fileName := pkg.CompiledGoFiles[i]
|
|
1779
|
-
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1780
|
-
|
|
1781
|
-
// Find all variable references in this file
|
|
1782
|
-
varRefsFromOtherFiles := make(map[string][]string)
|
|
1783
|
-
|
|
1784
|
-
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1785
|
-
// Look for identifier references
|
|
1786
|
-
if ident, ok := n.(*ast.Ident); ok {
|
|
1787
|
-
// Check if this identifier refers to a package-level variable
|
|
1788
|
-
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
1789
|
-
if varObj, ok := obj.(*types.Var); ok {
|
|
1790
|
-
// Only track package-level variables (not function parameters or local vars)
|
|
1791
|
-
if varObj.Parent() == pkg.Types.Scope() {
|
|
1792
|
-
varName := ident.Name
|
|
1793
|
-
|
|
1794
|
-
// Check if this variable is defined in the current file
|
|
1795
|
-
currentFileVars := analysis.VariableDefs[baseFileName]
|
|
1796
|
-
isDefinedInCurrentFile := slices.Contains(currentFileVars, varName)
|
|
1797
|
-
|
|
1798
|
-
// If not defined in current file, find which file defines it
|
|
1799
|
-
if !isDefinedInCurrentFile {
|
|
1800
|
-
for sourceFile, vars := range analysis.VariableDefs {
|
|
1801
|
-
if sourceFile == baseFileName {
|
|
1802
|
-
continue // Skip current file
|
|
1803
|
-
}
|
|
1804
|
-
if slices.Contains(vars, varName) {
|
|
1805
|
-
// Found the variable in another file
|
|
1806
|
-
if varRefsFromOtherFiles[sourceFile] == nil {
|
|
1807
|
-
varRefsFromOtherFiles[sourceFile] = []string{}
|
|
1808
|
-
}
|
|
1809
|
-
// Check if already added to avoid duplicates
|
|
1810
|
-
found := slices.Contains(varRefsFromOtherFiles[sourceFile], varName)
|
|
1811
|
-
if !found {
|
|
1812
|
-
varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], varName)
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
// Also check for constants
|
|
1820
|
-
if constObj, ok := obj.(*types.Const); ok {
|
|
1821
|
-
// Only track package-level constants
|
|
1822
|
-
if constObj.Parent() == pkg.Types.Scope() {
|
|
1823
|
-
constName := ident.Name
|
|
1824
|
-
|
|
1825
|
-
// Check if this constant is defined in the current file
|
|
1826
|
-
currentFileVars := analysis.VariableDefs[baseFileName]
|
|
1827
|
-
isDefinedInCurrentFile := slices.Contains(currentFileVars, constName)
|
|
1828
|
-
|
|
1829
|
-
// If not defined in current file, find which file defines it
|
|
1830
|
-
if !isDefinedInCurrentFile {
|
|
1831
|
-
for sourceFile, vars := range analysis.VariableDefs {
|
|
1832
|
-
if sourceFile == baseFileName {
|
|
1833
|
-
continue // Skip current file
|
|
1834
|
-
}
|
|
1835
|
-
if slices.Contains(vars, constName) {
|
|
1836
|
-
// Found the constant in another file
|
|
1837
|
-
if varRefsFromOtherFiles[sourceFile] == nil {
|
|
1838
|
-
varRefsFromOtherFiles[sourceFile] = []string{}
|
|
1839
|
-
}
|
|
1840
|
-
// Check if already added to avoid duplicates
|
|
1841
|
-
found := slices.Contains(varRefsFromOtherFiles[sourceFile], constName)
|
|
1842
|
-
if !found {
|
|
1843
|
-
varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], constName)
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
return true
|
|
1853
|
-
})
|
|
1854
|
-
|
|
1855
|
-
if len(varRefsFromOtherFiles) > 0 {
|
|
1856
|
-
analysis.VariableCalls[baseFileName] = varRefsFromOtherFiles
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
// Fifth pass: analyze method calls on wrapper types (named basic types with methods)
|
|
1861
|
-
// These generate TypeName_MethodName function calls that need to be imported
|
|
1862
|
-
for i, syntax := range pkg.Syntax {
|
|
1863
|
-
fileName := pkg.CompiledGoFiles[i]
|
|
1864
|
-
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1865
|
-
|
|
1866
|
-
// Find all method calls on wrapper types in this file
|
|
1867
|
-
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1868
|
-
callExpr, ok := n.(*ast.CallExpr)
|
|
1869
|
-
if !ok {
|
|
1870
|
-
return true
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
// Check if this is a method call (selector expression)
|
|
1874
|
-
selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
1875
|
-
if !ok {
|
|
1876
|
-
return true
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
// Get the type of the receiver
|
|
1880
|
-
receiverType := pkg.TypesInfo.TypeOf(selectorExpr.X)
|
|
1881
|
-
if receiverType == nil {
|
|
1882
|
-
return true
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
// Check if this is a wrapper type (named type with basic underlying type and methods)
|
|
1886
|
-
namedType, ok := receiverType.(*types.Named)
|
|
1887
|
-
if !ok {
|
|
1888
|
-
return true
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
// Check if it has a basic underlying type
|
|
1892
|
-
if _, ok := namedType.Underlying().(*types.Basic); !ok {
|
|
1893
|
-
return true
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
// Check if this type is defined in the same package
|
|
1897
|
-
obj := namedType.Obj()
|
|
1898
|
-
if obj == nil || obj.Pkg() == nil || obj.Pkg() != pkg.Types {
|
|
1899
|
-
return true // Not from this package
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
// Check if this type has the method being called
|
|
1903
|
-
methodName := selectorExpr.Sel.Name
|
|
1904
|
-
found := false
|
|
1905
|
-
for method := range namedType.Methods() {
|
|
1906
|
-
if method.Name() == methodName {
|
|
1907
|
-
found = true
|
|
1908
|
-
break
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
if !found {
|
|
1912
|
-
return true
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
// Generate the function name: TypeName_MethodName
|
|
1916
|
-
funcName := obj.Name() + "_" + methodName
|
|
1917
|
-
|
|
1918
|
-
// Find which file defines this function
|
|
1919
|
-
for sourceFile, funcs := range analysis.FunctionDefs {
|
|
1920
|
-
if sourceFile == baseFileName {
|
|
1921
|
-
continue // Skip current file
|
|
1922
|
-
}
|
|
1923
|
-
if slices.Contains(funcs, funcName) {
|
|
1924
|
-
// Found the function in another file
|
|
1925
|
-
if analysis.FunctionCalls[baseFileName] == nil {
|
|
1926
|
-
analysis.FunctionCalls[baseFileName] = make(map[string][]string)
|
|
1927
|
-
}
|
|
1928
|
-
if analysis.FunctionCalls[baseFileName][sourceFile] == nil {
|
|
1929
|
-
analysis.FunctionCalls[baseFileName][sourceFile] = []string{}
|
|
1930
|
-
}
|
|
1931
|
-
// Check if already added to avoid duplicates
|
|
1932
|
-
if !slices.Contains(analysis.FunctionCalls[baseFileName][sourceFile], funcName) {
|
|
1933
|
-
analysis.FunctionCalls[baseFileName][sourceFile] = append(analysis.FunctionCalls[baseFileName][sourceFile], funcName)
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
return true
|
|
1939
|
-
})
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
return analysis
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
// LoadPackageMetadata loads metadata from gs packages using embedded JSON files
|
|
1946
|
-
func (a *Analysis) LoadPackageMetadata() {
|
|
1947
|
-
// Discover all packages in the embedded gs/ directory
|
|
1948
|
-
packagePaths := a.discoverEmbeddedGsPackages()
|
|
1949
|
-
|
|
1950
|
-
for _, pkgPath := range packagePaths {
|
|
1951
|
-
metaFilePath := filepath.Join("gs", pkgPath, "meta.json")
|
|
1952
|
-
|
|
1953
|
-
// Try to read the meta.json file from embedded filesystem
|
|
1954
|
-
// We need access to the embedded FS, which should be imported from the parent package
|
|
1955
|
-
if metadata := a.loadGsMetadata(metaFilePath); metadata != nil {
|
|
1956
|
-
// Store async method information
|
|
1957
|
-
for methodKey, isAsync := range metadata.AsyncMethods {
|
|
1958
|
-
// Convert method key to our internal key format
|
|
1959
|
-
parts := strings.Split(methodKey, ".")
|
|
1960
|
-
var typeName, methodName string
|
|
1961
|
-
|
|
1962
|
-
if len(parts) == 2 {
|
|
1963
|
-
// "Type.Method" format for methods
|
|
1964
|
-
typeName = parts[0]
|
|
1965
|
-
methodName = parts[1]
|
|
1966
|
-
} else if len(parts) == 1 {
|
|
1967
|
-
// "Function" format for package-level functions
|
|
1968
|
-
typeName = "" // Empty type name for package-level functions
|
|
1969
|
-
methodName = parts[0]
|
|
1970
|
-
} else {
|
|
1971
|
-
// Skip invalid formats
|
|
1972
|
-
continue
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
// Use MethodKey instead of PackageMetadataKey for consistency
|
|
1976
|
-
key := MethodKey{
|
|
1977
|
-
PackagePath: pkgPath,
|
|
1978
|
-
ReceiverType: typeName,
|
|
1979
|
-
MethodName: methodName,
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
// Store the async value directly in MethodAsyncStatus
|
|
1983
|
-
a.MethodAsyncStatus[key] = isAsync
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
// discoverEmbeddedGsPackages finds all packages in the embedded gs/ directory
|
|
1990
|
-
func (a *Analysis) discoverEmbeddedGsPackages() []string {
|
|
1991
|
-
var packageList []string
|
|
1992
|
-
|
|
1993
|
-
// Read the gs/ directory from the embedded filesystem
|
|
1994
|
-
entries, err := goscript.GsOverrides.ReadDir("gs")
|
|
1995
|
-
if err != nil {
|
|
1996
|
-
// If we can't read the gs/ directory, return empty list
|
|
1997
|
-
return packageList
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
// Iterate through all entries in gs/
|
|
2001
|
-
for _, entry := range entries {
|
|
2002
|
-
if entry.IsDir() {
|
|
2003
|
-
packageList = append(packageList, entry.Name())
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
return packageList
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
// loadGsMetadata loads metadata from a meta.json file in the embedded filesystem
|
|
2011
|
-
func (a *Analysis) loadGsMetadata(metaFilePath string) *GsMetadata {
|
|
2012
|
-
// Read the meta.json file from the embedded filesystem
|
|
2013
|
-
content, err := goscript.GsOverrides.ReadFile(metaFilePath)
|
|
2014
|
-
if err != nil {
|
|
2015
|
-
return nil // No metadata file found
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
var metadata GsMetadata
|
|
2019
|
-
if err := json.Unmarshal(content, &metadata); err != nil {
|
|
2020
|
-
return nil // Invalid JSON
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
return &metadata
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
// isHandwrittenPackage checks if a package path corresponds to a handwritten package in gs/
|
|
2027
|
-
func (a *Analysis) isHandwrittenPackage(pkgPath string) bool {
|
|
2028
|
-
// Check if the package exists in the embedded gs/ directory
|
|
2029
|
-
metaFilePath := filepath.Join("gs", pkgPath, "meta.json")
|
|
2030
|
-
_, err := goscript.GsOverrides.ReadFile(metaFilePath)
|
|
2031
|
-
return err == nil
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
// IsMethodAsync checks if a method call is async based on package metadata
|
|
2035
|
-
func (a *Analysis) IsMethodAsync(pkgPath, typeName, methodName string) bool {
|
|
2036
|
-
// First, check pre-computed method async status
|
|
2037
|
-
methodKey := MethodKey{
|
|
2038
|
-
PackagePath: pkgPath,
|
|
2039
|
-
ReceiverType: typeName,
|
|
2040
|
-
MethodName: methodName,
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
if status, exists := a.MethodAsyncStatus[methodKey]; exists {
|
|
2044
|
-
return status
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
// If no pre-computed status found, external methods default to sync
|
|
2048
|
-
// Comprehensive analysis should have already analyzed all packages and loaded metadata
|
|
2049
|
-
return false
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
// NeedsReflectionMetadata returns whether the given function node needs reflection type metadata
|
|
2053
|
-
func (a *Analysis) NeedsReflectionMetadata(node ast.Node) bool {
|
|
2054
|
-
if node == nil {
|
|
2055
|
-
return false
|
|
2056
|
-
}
|
|
2057
|
-
reflectInfo := a.ReflectedFunctions[node]
|
|
2058
|
-
return reflectInfo != nil && reflectInfo.NeedsReflect
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
// GetFunctionTypeInfo returns the function type information for reflection
|
|
2062
|
-
func (a *Analysis) GetFunctionTypeInfo(node ast.Node) *ReflectedFunctionInfo {
|
|
2063
|
-
if node == nil {
|
|
2064
|
-
return nil
|
|
2065
|
-
}
|
|
2066
|
-
return a.ReflectedFunctions[node]
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
// MarkFunctionForReflection marks a function node as needing reflection support
|
|
2070
|
-
func (a *Analysis) MarkFunctionForReflection(node ast.Node, funcType *types.Signature) {
|
|
2071
|
-
if node == nil || funcType == nil {
|
|
2072
|
-
return
|
|
2073
|
-
}
|
|
2074
|
-
a.ReflectedFunctions[node] = &ReflectedFunctionInfo{
|
|
2075
|
-
FuncType: funcType,
|
|
2076
|
-
NeedsReflect: true,
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
// checkReflectUsage checks for reflect function calls that operate on functions
|
|
2081
|
-
func (v *analysisVisitor) checkReflectUsage(callExpr *ast.CallExpr) {
|
|
2082
|
-
// Check if this is a reflect package function call
|
|
2083
|
-
if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
|
|
2084
|
-
// Check if the selector is from reflect package
|
|
2085
|
-
if ident, ok := selExpr.X.(*ast.Ident); ok {
|
|
2086
|
-
// Check if this is a reflect package call (reflect.TypeOf, reflect.ValueOf, etc.)
|
|
2087
|
-
if obj := v.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
2088
|
-
if pkgName, ok := obj.(*types.PkgName); ok && pkgName.Imported().Path() == "reflect" {
|
|
2089
|
-
methodName := selExpr.Sel.Name
|
|
2090
|
-
|
|
2091
|
-
// Check for reflect.TypeOf and reflect.ValueOf calls
|
|
2092
|
-
if methodName == "TypeOf" || methodName == "ValueOf" {
|
|
2093
|
-
// Check if any argument is a function
|
|
2094
|
-
for _, arg := range callExpr.Args {
|
|
2095
|
-
v.checkReflectArgument(arg)
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
// checkReflectArgument checks if an argument to a reflect function is a function that needs metadata
|
|
2105
|
-
func (v *analysisVisitor) checkReflectArgument(arg ast.Expr) {
|
|
2106
|
-
// Check if the argument is an identifier (variable)
|
|
2107
|
-
if ident, ok := arg.(*ast.Ident); ok {
|
|
2108
|
-
// Get the object this identifier refers to
|
|
2109
|
-
if obj := v.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
2110
|
-
// Check if this object has a function type
|
|
2111
|
-
if funcType, ok := obj.Type().(*types.Signature); ok {
|
|
2112
|
-
// This is a function variable being passed to reflect
|
|
2113
|
-
// We need to find the original function definition/assignment
|
|
2114
|
-
v.markFunctionVariable(ident, funcType)
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
} else if funcLit, ok := arg.(*ast.FuncLit); ok {
|
|
2118
|
-
// This is a function literal being passed directly to reflect
|
|
2119
|
-
if funcType := v.pkg.TypesInfo.Types[funcLit].Type.(*types.Signature); funcType != nil {
|
|
2120
|
-
v.analysis.MarkFunctionForReflection(funcLit, funcType)
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
|
|
2125
|
-
// markFunctionVariable finds the function definition/assignment for a variable and marks it for reflection
|
|
2126
|
-
func (v *analysisVisitor) markFunctionVariable(ident *ast.Ident, funcType *types.Signature) {
|
|
2127
|
-
// Get the object for this identifier
|
|
2128
|
-
obj := v.pkg.TypesInfo.Uses[ident]
|
|
2129
|
-
if obj == nil {
|
|
2130
|
-
return
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
// Check if we have a tracked function assignment for this variable
|
|
2134
|
-
if funcNode := v.analysis.FunctionAssignments[obj]; funcNode != nil {
|
|
2135
|
-
// Mark the function node for reflection
|
|
2136
|
-
v.analysis.MarkFunctionForReflection(funcNode, funcType)
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
// detectVariableShadowing detects variable shadowing in any := assignment
|
|
2141
|
-
func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *ShadowingInfo {
|
|
2142
|
-
shadowingInfo := &ShadowingInfo{
|
|
2143
|
-
ShadowedVariables: make(map[string]types.Object),
|
|
2144
|
-
TempVariables: make(map[string]string),
|
|
2145
|
-
TypeShadowedVars: make(map[string]string),
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
hasShadowing := false
|
|
2149
|
-
|
|
2150
|
-
// First, collect all LHS variable names that are being declared
|
|
2151
|
-
lhsVarNames := make(map[string]*ast.Ident)
|
|
2152
|
-
for _, lhsExpr := range assignStmt.Lhs {
|
|
2153
|
-
if lhsIdent, ok := lhsExpr.(*ast.Ident); ok && lhsIdent.Name != "_" {
|
|
2154
|
-
lhsVarNames[lhsIdent.Name] = lhsIdent
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
// Next, check all RHS expressions for usage of variables that are also being declared on LHS
|
|
2159
|
-
for _, rhsExpr := range assignStmt.Rhs {
|
|
2160
|
-
v.findVariableUsageInExpr(rhsExpr, lhsVarNames, shadowingInfo, &hasShadowing)
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
// Check for type shadowing: variable name matches a type name used in its initialization
|
|
2164
|
-
// e.g., field := field{...} where the variable 'field' shadows the type 'field'
|
|
2165
|
-
if assignStmt.Tok == token.DEFINE {
|
|
2166
|
-
for i, lhsExpr := range assignStmt.Lhs {
|
|
2167
|
-
if i < len(assignStmt.Rhs) {
|
|
2168
|
-
if lhsIdent, ok := lhsExpr.(*ast.Ident); ok && lhsIdent.Name != "_" {
|
|
2169
|
-
if typeName := v.findTypeShadowing(lhsIdent.Name, assignStmt.Rhs[i]); typeName != "" {
|
|
2170
|
-
shadowingInfo.TypeShadowedVars[lhsIdent.Name] = lhsIdent.Name + "_"
|
|
2171
|
-
hasShadowing = true
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
if hasShadowing {
|
|
2179
|
-
return shadowingInfo
|
|
2180
|
-
}
|
|
2181
|
-
return nil
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
// findTypeShadowing checks if the given variable name matches a type name used in the RHS expression.
|
|
2185
|
-
// Returns the type name if shadowing is detected, empty string otherwise.
|
|
2186
|
-
func (v *analysisVisitor) findTypeShadowing(varName string, rhsExpr ast.Expr) string {
|
|
2187
|
-
// Handle address-of expressions: field := &field{...}
|
|
2188
|
-
if unary, ok := rhsExpr.(*ast.UnaryExpr); ok && unary.Op == token.AND {
|
|
2189
|
-
rhsExpr = unary.X
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
// Check if RHS is a composite literal with a type name matching varName
|
|
2193
|
-
compLit, ok := rhsExpr.(*ast.CompositeLit)
|
|
2194
|
-
if !ok {
|
|
2195
|
-
return ""
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
// Get the type name from the composite literal
|
|
2199
|
-
var typeName string
|
|
2200
|
-
switch t := compLit.Type.(type) {
|
|
2201
|
-
case *ast.Ident:
|
|
2202
|
-
typeName = t.Name
|
|
2203
|
-
case *ast.SelectorExpr:
|
|
2204
|
-
// pkg.Type - just use the type name part
|
|
2205
|
-
typeName = t.Sel.Name
|
|
2206
|
-
default:
|
|
2207
|
-
return ""
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
// Check if variable name matches type name
|
|
2211
|
-
if typeName == varName {
|
|
2212
|
-
return typeName
|
|
2213
|
-
}
|
|
2214
|
-
return ""
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
|
-
// findVariableUsageInExpr recursively searches for variable usage in an expression
|
|
2218
|
-
func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map[string]*ast.Ident, shadowingInfo *ShadowingInfo, hasShadowing *bool) {
|
|
2219
|
-
if expr == nil {
|
|
2220
|
-
return
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
switch e := expr.(type) {
|
|
2224
|
-
case *ast.Ident:
|
|
2225
|
-
// Check if this identifier is being shadowed
|
|
2226
|
-
if lhsIdent, exists := lhsVarNames[e.Name]; exists {
|
|
2227
|
-
// This variable is being used on RHS but also declared on LHS - this is shadowing!
|
|
2228
|
-
|
|
2229
|
-
// Get the outer scope object for this variable
|
|
2230
|
-
if outerObj := v.pkg.TypesInfo.Uses[e]; outerObj != nil {
|
|
2231
|
-
// Make sure this isn't the same object as the LHS (which would mean no shadowing)
|
|
2232
|
-
if lhsObj := v.pkg.TypesInfo.Defs[lhsIdent]; lhsObj != outerObj {
|
|
2233
|
-
shadowingInfo.ShadowedVariables[e.Name] = outerObj
|
|
2234
|
-
shadowingInfo.TempVariables[e.Name] = "_temp_" + e.Name
|
|
2235
|
-
*hasShadowing = true
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
case *ast.CallExpr:
|
|
2241
|
-
// Check function arguments
|
|
2242
|
-
for _, arg := range e.Args {
|
|
2243
|
-
v.findVariableUsageInExpr(arg, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2244
|
-
}
|
|
2245
|
-
// Check function expression itself
|
|
2246
|
-
v.findVariableUsageInExpr(e.Fun, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2247
|
-
|
|
2248
|
-
case *ast.SelectorExpr:
|
|
2249
|
-
// Check the base expression (e.g., x in x.Method())
|
|
2250
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2251
|
-
|
|
2252
|
-
case *ast.IndexExpr:
|
|
2253
|
-
// Check both the expression and index (e.g., arr[i])
|
|
2254
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2255
|
-
v.findVariableUsageInExpr(e.Index, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2256
|
-
|
|
2257
|
-
case *ast.SliceExpr:
|
|
2258
|
-
// Check the expression and slice bounds
|
|
2259
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2260
|
-
if e.Low != nil {
|
|
2261
|
-
v.findVariableUsageInExpr(e.Low, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2262
|
-
}
|
|
2263
|
-
if e.High != nil {
|
|
2264
|
-
v.findVariableUsageInExpr(e.High, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2265
|
-
}
|
|
2266
|
-
if e.Max != nil {
|
|
2267
|
-
v.findVariableUsageInExpr(e.Max, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
case *ast.UnaryExpr:
|
|
2271
|
-
// Check the operand (e.g., &x, -x, !x)
|
|
2272
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2273
|
-
|
|
2274
|
-
case *ast.BinaryExpr:
|
|
2275
|
-
// Check both operands (e.g., x + y)
|
|
2276
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2277
|
-
v.findVariableUsageInExpr(e.Y, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2278
|
-
|
|
2279
|
-
case *ast.ParenExpr:
|
|
2280
|
-
// Check the parenthesized expression
|
|
2281
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2282
|
-
|
|
2283
|
-
case *ast.TypeAssertExpr:
|
|
2284
|
-
// Check the expression being type-asserted
|
|
2285
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2286
|
-
|
|
2287
|
-
case *ast.StarExpr:
|
|
2288
|
-
// Check the expression being dereferenced
|
|
2289
|
-
v.findVariableUsageInExpr(e.X, lhsVarNames, shadowingInfo, hasShadowing)
|
|
2290
|
-
|
|
2291
|
-
// Add more expression types as needed
|
|
2292
|
-
default:
|
|
2293
|
-
// For other expression types, we might need to add specific handling
|
|
2294
|
-
// For now, we'll ignore them as they're less common in shadowing scenarios
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
// trackInterfaceImplementation records that a struct type implements an interface method
|
|
2299
|
-
func (a *Analysis) trackInterfaceImplementation(interfaceType *types.Interface, structType *types.Named, method *types.Func) {
|
|
2300
|
-
key := InterfaceMethodKey{
|
|
2301
|
-
InterfaceType: interfaceType.String(),
|
|
2302
|
-
MethodName: method.Name(),
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
implementation := ImplementationInfo{
|
|
2306
|
-
StructType: structType,
|
|
2307
|
-
Method: method,
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
a.InterfaceImplementations[key] = append(a.InterfaceImplementations[key], implementation)
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
// IsInterfaceMethodAsync determines if an interface method should be async based on its implementations
|
|
2314
|
-
func (a *Analysis) IsInterfaceMethodAsync(interfaceType *types.Interface, methodName string) bool {
|
|
2315
|
-
key := InterfaceMethodKey{
|
|
2316
|
-
InterfaceType: interfaceType.String(),
|
|
2317
|
-
MethodName: methodName,
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
// Find all implementations of this interface method
|
|
2321
|
-
implementations, exists := a.InterfaceImplementations[key]
|
|
2322
|
-
if !exists {
|
|
2323
|
-
return false
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
|
-
// If ANY implementation is async, the interface method is async
|
|
2327
|
-
for _, impl := range implementations {
|
|
2328
|
-
methodKey := MethodKey{
|
|
2329
|
-
PackagePath: impl.StructType.Obj().Pkg().Path(),
|
|
2330
|
-
ReceiverType: impl.StructType.Obj().Name(),
|
|
2331
|
-
MethodName: impl.Method.Name(),
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
if isAsync, exists := a.MethodAsyncStatus[methodKey]; exists && isAsync {
|
|
2335
|
-
return true
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
return false
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
|
-
// MustBeAsyncDueToInterface checks if a struct method must be async due to interface constraints
|
|
2343
|
-
func (a *Analysis) MustBeAsyncDueToInterface(structType *types.Named, methodName string) bool {
|
|
2344
|
-
// Find all interfaces that this struct implements
|
|
2345
|
-
for key, implementations := range a.InterfaceImplementations {
|
|
2346
|
-
if key.MethodName != methodName {
|
|
2347
|
-
continue
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
// Check if this struct is among the implementations
|
|
2351
|
-
for _, impl := range implementations {
|
|
2352
|
-
if impl.StructType == structType {
|
|
2353
|
-
// This struct implements this interface method
|
|
2354
|
-
// Check if the interface method is marked as async
|
|
2355
|
-
interfaceType := a.findInterfaceTypeByString(key.InterfaceType)
|
|
2356
|
-
if interfaceType != nil && a.IsInterfaceMethodAsync(interfaceType, methodName) {
|
|
2357
|
-
return true
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
return false
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
// findInterfaceTypeByString finds an interface type by its string representation
|
|
2367
|
-
// This is a helper method for MustBeAsyncDueToInterface
|
|
2368
|
-
func (a *Analysis) findInterfaceTypeByString(interfaceString string) *types.Interface {
|
|
2369
|
-
// This is a simplified implementation - in practice, we might need to store
|
|
2370
|
-
// the actual interface types in our tracking data structure
|
|
2371
|
-
for _, pkg := range a.AllPackages {
|
|
2372
|
-
for _, syntax := range pkg.Syntax {
|
|
2373
|
-
for _, decl := range syntax.Decls {
|
|
2374
|
-
if genDecl, ok := decl.(*ast.GenDecl); ok {
|
|
2375
|
-
for _, spec := range genDecl.Specs {
|
|
2376
|
-
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
2377
|
-
if interfaceType, ok := typeSpec.Type.(*ast.InterfaceType); ok {
|
|
2378
|
-
if goType := pkg.TypesInfo.TypeOf(interfaceType); goType != nil {
|
|
2379
|
-
if iface, ok := goType.(*types.Interface); ok {
|
|
2380
|
-
if iface.String() == interfaceString {
|
|
2381
|
-
return iface
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
return nil
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
// GetReceiverMapping returns the receiver variable mapping for a function declaration
|
|
2396
|
-
func (a *Analysis) GetReceiverMapping(funcDecl *ast.FuncDecl) string {
|
|
2397
|
-
if funcDecl.Recv != nil && len(funcDecl.Recv.List) > 0 {
|
|
2398
|
-
for _, field := range funcDecl.Recv.List {
|
|
2399
|
-
for _, name := range field.Names {
|
|
2400
|
-
if name != nil && name.Name != "_" {
|
|
2401
|
-
return "receiver"
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
return ""
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
// GetIdentifierMapping returns the replacement name for an identifier
|
|
2410
|
-
func (a *Analysis) GetIdentifierMapping(ident *ast.Ident) string {
|
|
2411
|
-
if ident == nil {
|
|
2412
|
-
return ""
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
// Check if this identifier has a mapping in NodeData
|
|
2416
|
-
if nodeInfo := a.NodeData[ident]; nodeInfo != nil {
|
|
2417
|
-
return nodeInfo.IdentifierMapping
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
return ""
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
// findStructMethod finds a method with the given name on a named type
|
|
2424
|
-
func (v *analysisVisitor) findStructMethod(namedType *types.Named, methodName string) *types.Func {
|
|
2425
|
-
// Check methods directly on the type
|
|
2426
|
-
for method := range namedType.Methods() {
|
|
2427
|
-
if method.Name() == methodName {
|
|
2428
|
-
return method
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
return nil
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
|
-
// analyzeAssignment analyzes a single assignment for pointer analysis
|
|
2435
|
-
func (v *analysisVisitor) analyzeAssignment(lhsExpr, rhsExpr ast.Expr) {
|
|
2436
|
-
// Determine RHS assignment type and source object
|
|
2437
|
-
rhsAssignmentType := DirectAssignment
|
|
2438
|
-
var rhsSourceObj types.Object
|
|
2439
|
-
|
|
2440
|
-
if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
|
|
2441
|
-
// RHS is &some_expr
|
|
2442
|
-
rhsAssignmentType = AddressOfAssignment
|
|
2443
|
-
if rhsIdent, ok := unaryExpr.X.(*ast.Ident); ok {
|
|
2444
|
-
rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
2445
|
-
}
|
|
2446
|
-
} else if rhsIdent, ok := rhsExpr.(*ast.Ident); ok {
|
|
2447
|
-
// RHS is variable
|
|
2448
|
-
rhsAssignmentType = DirectAssignment
|
|
2449
|
-
rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
// Determine LHS object
|
|
2453
|
-
var lhsTrackedObj types.Object
|
|
2454
|
-
|
|
2455
|
-
if lhsIdent, ok := lhsExpr.(*ast.Ident); ok {
|
|
2456
|
-
if lhsIdent.Name != "_" {
|
|
2457
|
-
lhsTrackedObj = v.pkg.TypesInfo.ObjectOf(lhsIdent)
|
|
2458
|
-
}
|
|
2459
|
-
} else if selExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
2460
|
-
if selection := v.pkg.TypesInfo.Selections[selExpr]; selection != nil {
|
|
2461
|
-
lhsTrackedObj = selection.Obj()
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
// Record usage information
|
|
2466
|
-
if _, isVar := lhsTrackedObj.(*types.Var); isVar {
|
|
2467
|
-
lhsUsageInfo := v.getOrCreateUsageInfo(lhsTrackedObj)
|
|
2468
|
-
if rhsSourceObj != nil {
|
|
2469
|
-
lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
|
|
2470
|
-
Object: rhsSourceObj,
|
|
2471
|
-
Type: rhsAssignmentType,
|
|
2472
|
-
})
|
|
2473
|
-
} else if rhsAssignmentType == AddressOfAssignment {
|
|
2474
|
-
lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
|
|
2475
|
-
Object: nil,
|
|
2476
|
-
Type: rhsAssignmentType,
|
|
2477
|
-
})
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
|
-
if rhsSourceObj != nil {
|
|
2482
|
-
sourceUsageInfo := v.getOrCreateUsageInfo(rhsSourceObj)
|
|
2483
|
-
sourceUsageInfo.Destinations = append(sourceUsageInfo.Destinations, AssignmentInfo{
|
|
2484
|
-
Object: lhsTrackedObj,
|
|
2485
|
-
Type: rhsAssignmentType,
|
|
2486
|
-
})
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
// trackInterfaceAssignments tracks interface implementations in assignment statements
|
|
2491
|
-
func (v *analysisVisitor) trackInterfaceAssignments(assignStmt *ast.AssignStmt) {
|
|
2492
|
-
// For each assignment, check if we're assigning a struct to an interface variable
|
|
2493
|
-
for i, lhsExpr := range assignStmt.Lhs {
|
|
2494
|
-
if i >= len(assignStmt.Rhs) {
|
|
2495
|
-
continue
|
|
2496
|
-
}
|
|
2497
|
-
rhsExpr := assignStmt.Rhs[i]
|
|
2498
|
-
|
|
2499
|
-
// Get the type of the LHS (destination)
|
|
2500
|
-
lhsType := v.pkg.TypesInfo.TypeOf(lhsExpr)
|
|
2501
|
-
if lhsType == nil {
|
|
2502
|
-
continue
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
// Check if LHS is an interface type
|
|
2506
|
-
interfaceType, isInterface := lhsType.Underlying().(*types.Interface)
|
|
2507
|
-
if !isInterface {
|
|
2508
|
-
continue
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
// Get the type of the RHS (source)
|
|
2512
|
-
rhsType := v.pkg.TypesInfo.TypeOf(rhsExpr)
|
|
2513
|
-
if rhsType == nil {
|
|
2514
|
-
continue
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
// Handle pointer types
|
|
2518
|
-
if ptrType, isPtr := rhsType.(*types.Pointer); isPtr {
|
|
2519
|
-
rhsType = ptrType.Elem()
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
// Check if RHS is a named struct type
|
|
2523
|
-
namedType, isNamed := rhsType.(*types.Named)
|
|
2524
|
-
if !isNamed {
|
|
2525
|
-
continue
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
// Track implementations for all interface methods
|
|
2529
|
-
for interfaceMethod := range interfaceType.ExplicitMethods() {
|
|
2530
|
-
|
|
2531
|
-
structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
|
|
2532
|
-
if structMethod != nil {
|
|
2533
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
|
|
2539
|
-
// trackInterfaceCallArguments analyzes function call arguments to detect interface implementations
|
|
2540
|
-
func (v *analysisVisitor) trackInterfaceCallArguments(callExpr *ast.CallExpr) {
|
|
2541
|
-
// Get the function signature to determine parameter types
|
|
2542
|
-
var funcType *types.Signature
|
|
2543
|
-
|
|
2544
|
-
if callFunType := v.pkg.TypesInfo.TypeOf(callExpr.Fun); callFunType != nil {
|
|
2545
|
-
if sig, ok := callFunType.(*types.Signature); ok {
|
|
2546
|
-
funcType = sig
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
if funcType == nil {
|
|
2551
|
-
return
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
// Check each argument against its corresponding parameter
|
|
2555
|
-
params := funcType.Params()
|
|
2556
|
-
for i, arg := range callExpr.Args {
|
|
2557
|
-
if i >= params.Len() {
|
|
2558
|
-
break // More arguments than parameters (variadic case)
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
|
-
paramType := params.At(i).Type()
|
|
2562
|
-
|
|
2563
|
-
// Check if the parameter is an interface type
|
|
2564
|
-
interfaceType, isInterface := paramType.Underlying().(*types.Interface)
|
|
2565
|
-
if !isInterface {
|
|
2566
|
-
continue
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
// Get the type of the argument
|
|
2570
|
-
argType := v.pkg.TypesInfo.TypeOf(arg)
|
|
2571
|
-
if argType == nil {
|
|
2572
|
-
continue
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
// Handle pointer types
|
|
2576
|
-
if ptrType, isPtr := argType.(*types.Pointer); isPtr {
|
|
2577
|
-
argType = ptrType.Elem()
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
// Check if argument is a named struct type
|
|
2581
|
-
namedType, isNamed := argType.(*types.Named)
|
|
2582
|
-
if !isNamed {
|
|
2583
|
-
continue
|
|
2584
|
-
}
|
|
2585
|
-
|
|
2586
|
-
// Track implementations for all interface methods
|
|
2587
|
-
for interfaceMethod := range interfaceType.ExplicitMethods() {
|
|
2588
|
-
|
|
2589
|
-
structMethod := v.findStructMethod(namedType, interfaceMethod.Name())
|
|
2590
|
-
if structMethod != nil {
|
|
2591
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
|
|
2597
|
-
// IsNamedBasicType returns whether the given type should be implemented as a type alias with standalone functions
|
|
2598
|
-
// This applies to named types with basic underlying types (like uint32, string, etc.) that have methods
|
|
2599
|
-
// It excludes struct types, which should remain as classes
|
|
2600
|
-
func (a *Analysis) IsNamedBasicType(t types.Type) bool {
|
|
2601
|
-
if t == nil {
|
|
2602
|
-
return false
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
// Check if we already have this result cached
|
|
2606
|
-
if result, exists := a.NamedBasicTypes[t]; exists {
|
|
2607
|
-
return result
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
var originalType types.Type = t
|
|
2611
|
-
var foundMethods bool
|
|
2612
|
-
|
|
2613
|
-
// Traverse the type chain to find any type with methods
|
|
2614
|
-
for {
|
|
2615
|
-
switch typed := t.(type) {
|
|
2616
|
-
case *types.Named:
|
|
2617
|
-
// Built-in types cannot be named basic types
|
|
2618
|
-
if typed.Obj().Pkg() == nil {
|
|
2619
|
-
return false
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
// Check if this named type has methods
|
|
2623
|
-
if typed.NumMethods() > 0 {
|
|
2624
|
-
foundMethods = true
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
// Check underlying type
|
|
2628
|
-
underlying := typed.Underlying()
|
|
2629
|
-
switch underlying.(type) {
|
|
2630
|
-
case *types.Struct, *types.Interface:
|
|
2631
|
-
return false
|
|
2632
|
-
}
|
|
2633
|
-
t = underlying
|
|
2634
|
-
|
|
2635
|
-
case *types.Alias:
|
|
2636
|
-
// Built-in types cannot be named basic types
|
|
2637
|
-
if typed.Obj().Pkg() == nil {
|
|
2638
|
-
return false
|
|
2639
|
-
}
|
|
2640
|
-
t = typed.Underlying()
|
|
2641
|
-
|
|
2642
|
-
default:
|
|
2643
|
-
// We've reached a non-named, non-alias type
|
|
2644
|
-
// Check if it's a supported type with methods
|
|
2645
|
-
switch t.(type) {
|
|
2646
|
-
case *types.Basic, *types.Slice, *types.Array, *types.Map:
|
|
2647
|
-
if foundMethods {
|
|
2648
|
-
a.NamedBasicTypes[originalType] = true
|
|
2649
|
-
return true
|
|
2650
|
-
}
|
|
2651
|
-
return false
|
|
2652
|
-
default:
|
|
2653
|
-
return false
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2659
|
-
// interfaceImplementationVisitor performs a second pass to analyze interface implementations
|
|
2660
|
-
type interfaceImplementationVisitor struct {
|
|
2661
|
-
analysis *Analysis
|
|
2662
|
-
pkg *packages.Package
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
|
-
func (v *interfaceImplementationVisitor) Visit(node ast.Node) ast.Visitor {
|
|
2666
|
-
switch n := node.(type) {
|
|
2667
|
-
case *ast.GenDecl:
|
|
2668
|
-
// Look for interface type specifications
|
|
2669
|
-
for _, spec := range n.Specs {
|
|
2670
|
-
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
2671
|
-
if interfaceType, ok := typeSpec.Type.(*ast.InterfaceType); ok {
|
|
2672
|
-
// This is an interface declaration, find all potential implementations
|
|
2673
|
-
v.findInterfaceImplementations(interfaceType)
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2678
|
-
return v
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
// findInterfaceImplementations finds all struct types that implement the given interface
|
|
2682
|
-
func (v *interfaceImplementationVisitor) findInterfaceImplementations(interfaceAST *ast.InterfaceType) {
|
|
2683
|
-
// Get the interface type from TypesInfo
|
|
2684
|
-
interfaceGoType := v.pkg.TypesInfo.TypeOf(interfaceAST)
|
|
2685
|
-
if interfaceGoType == nil {
|
|
2686
|
-
return
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
interfaceType, ok := interfaceGoType.(*types.Interface)
|
|
2690
|
-
if !ok {
|
|
2691
|
-
return
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
// Look through all packages for potential implementations
|
|
2695
|
-
for _, pkg := range v.analysis.AllPackages {
|
|
2696
|
-
v.findImplementationsInPackage(interfaceType, pkg)
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
// findImplementationsInPackage finds implementations of an interface in a specific package
|
|
2701
|
-
func (v *interfaceImplementationVisitor) findImplementationsInPackage(interfaceType *types.Interface, pkg *packages.Package) {
|
|
2702
|
-
// Get all named types in the package
|
|
2703
|
-
scope := pkg.Types.Scope()
|
|
2704
|
-
for _, name := range scope.Names() {
|
|
2705
|
-
obj := scope.Lookup(name)
|
|
2706
|
-
if obj == nil {
|
|
2707
|
-
continue
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
// Check if this is a type name
|
|
2711
|
-
typeName, ok := obj.(*types.TypeName)
|
|
2712
|
-
if !ok {
|
|
2713
|
-
continue
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2716
|
-
namedType, ok := typeName.Type().(*types.Named)
|
|
2717
|
-
if !ok {
|
|
2718
|
-
continue
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
// Check if this type implements the interface
|
|
2722
|
-
if types.Implements(namedType, interfaceType) || types.Implements(types.NewPointer(namedType), interfaceType) {
|
|
2723
|
-
v.trackImplementation(interfaceType, namedType)
|
|
2724
|
-
}
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
// trackImplementation records that a named type implements an interface
|
|
2729
|
-
func (v *interfaceImplementationVisitor) trackImplementation(interfaceType *types.Interface, namedType *types.Named) {
|
|
2730
|
-
// For each method in the interface, find the corresponding implementation
|
|
2731
|
-
for interfaceMethod := range interfaceType.ExplicitMethods() {
|
|
2732
|
-
|
|
2733
|
-
// Find the method in the implementing type
|
|
2734
|
-
structMethod := v.findMethodInType(namedType, interfaceMethod.Name())
|
|
2735
|
-
if structMethod != nil {
|
|
2736
|
-
v.analysis.trackInterfaceImplementation(interfaceType, namedType, structMethod)
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
// findMethodInType finds a method with the given name in a named type
|
|
2742
|
-
func (v *interfaceImplementationVisitor) findMethodInType(namedType *types.Named, methodName string) *types.Func {
|
|
2743
|
-
for method := range namedType.Methods() {
|
|
2744
|
-
if method.Name() == methodName {
|
|
2745
|
-
return method
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
return nil
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
// analyzeAllMethodsAsync performs comprehensive async analysis on all methods in all packages using topological sort
|
|
2752
|
-
func (v *analysisVisitor) analyzeAllMethodsAsync() {
|
|
2753
|
-
// Build the method call graph for all packages
|
|
2754
|
-
methodCalls := v.buildMethodCallGraph()
|
|
2755
|
-
|
|
2756
|
-
// Topologically sort methods by their dependencies
|
|
2757
|
-
sorted, cycles := v.topologicalSortMethods(methodCalls)
|
|
2758
|
-
|
|
2759
|
-
// Analyze methods in dependency order (dependencies analyzed before dependents)
|
|
2760
|
-
for _, methodKey := range sorted {
|
|
2761
|
-
v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
|
|
2762
|
-
}
|
|
2763
|
-
|
|
2764
|
-
// Analyze methods in cycles after the acyclic portion of the graph is known.
|
|
2765
|
-
// This lets recursive methods observe async callees outside the cycle while
|
|
2766
|
-
// still converging over recursive edges inside the cycle.
|
|
2767
|
-
maxIterations := 10
|
|
2768
|
-
for range maxIterations {
|
|
2769
|
-
changed := false
|
|
2770
|
-
|
|
2771
|
-
for _, methodKey := range cycles {
|
|
2772
|
-
pkg := v.analysis.AllPackages[methodKey.PackagePath]
|
|
2773
|
-
if pkg == nil && methodKey.PackagePath == v.pkg.Types.Path() {
|
|
2774
|
-
pkg = v.pkg
|
|
2775
|
-
}
|
|
2776
|
-
|
|
2777
|
-
isAsync := false
|
|
2778
|
-
if pkg != nil {
|
|
2779
|
-
var funcDecl *ast.FuncDecl
|
|
2780
|
-
if methodKey.ReceiverType == "" {
|
|
2781
|
-
funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
|
|
2782
|
-
} else {
|
|
2783
|
-
funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
if funcDecl != nil && funcDecl.Body != nil {
|
|
2787
|
-
isAsync = v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
|
|
2788
|
-
if !isAsync {
|
|
2789
|
-
for _, callee := range methodCalls[methodKey] {
|
|
2790
|
-
if calleeAsync, exists := v.analysis.MethodAsyncStatus[callee]; exists && calleeAsync {
|
|
2791
|
-
isAsync = true
|
|
2792
|
-
break
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
if oldStatus, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists || oldStatus != isAsync {
|
|
2800
|
-
changed = true
|
|
2801
|
-
}
|
|
2802
|
-
v.analysis.MethodAsyncStatus[methodKey] = isAsync
|
|
2803
|
-
}
|
|
2804
|
-
|
|
2805
|
-
if !changed {
|
|
2806
|
-
break
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
|
|
2810
|
-
// Track async-returning variables BEFORE analyzing function literals
|
|
2811
|
-
// This detects variables assigned from higher-order functions with async function literal args
|
|
2812
|
-
// e.g., indirect := sync.OnceValue(asyncFunc)
|
|
2813
|
-
// This must happen first so that function literals containing calls to these variables
|
|
2814
|
-
// will be correctly identified as async.
|
|
2815
|
-
v.trackAsyncReturningVarsAllFiles()
|
|
2816
|
-
|
|
2817
|
-
// Finally, analyze function literals in the current package only
|
|
2818
|
-
// (external packages' function literals are not accessible)
|
|
2819
|
-
// This must run AFTER trackAsyncReturningVarsAllFiles so that function literals
|
|
2820
|
-
// containing calls to async-returning variables are correctly marked as async.
|
|
2821
|
-
v.analyzeFunctionLiteralsAsync(v.pkg)
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
// buildMethodCallGraph builds a graph of which methods call which other methods
|
|
2825
|
-
func (v *analysisVisitor) analyzeFunctionLiteralsAsync(pkg *packages.Package) {
|
|
2826
|
-
for _, file := range pkg.Syntax {
|
|
2827
|
-
ast.Inspect(file, func(n ast.Node) bool {
|
|
2828
|
-
if funcLit, ok := n.(*ast.FuncLit); ok {
|
|
2829
|
-
v.analyzeFunctionLiteralAsync(funcLit, pkg)
|
|
2830
|
-
}
|
|
2831
|
-
return true
|
|
2832
|
-
})
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
|
|
2836
|
-
// trackAsyncReturningVarsAllFiles scans all files for assignment statements
|
|
2837
|
-
// and marks variables that are assigned from higher-order functions with async function literal args
|
|
2838
|
-
func (v *analysisVisitor) trackAsyncReturningVarsAllFiles() {
|
|
2839
|
-
for _, file := range v.pkg.Syntax {
|
|
2840
|
-
ast.Inspect(file, func(n ast.Node) bool {
|
|
2841
|
-
if assignStmt, ok := n.(*ast.AssignStmt); ok {
|
|
2842
|
-
if len(assignStmt.Lhs) == 1 && len(assignStmt.Rhs) == 1 {
|
|
2843
|
-
v.trackAsyncReturningVar(assignStmt.Lhs[0], assignStmt.Rhs[0])
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
return true
|
|
2847
|
-
})
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
|
|
2851
|
-
// analyzeFunctionLiteralAsync determines if a function literal is async and stores the result
|
|
2852
|
-
func (v *analysisVisitor) analyzeFunctionLiteralAsync(funcLit *ast.FuncLit, pkg *packages.Package) {
|
|
2853
|
-
// Check if already analyzed
|
|
2854
|
-
nodeInfo := v.analysis.NodeData[funcLit]
|
|
2855
|
-
if nodeInfo != nil && nodeInfo.InAsyncContext {
|
|
2856
|
-
// Already marked as async, skip
|
|
2857
|
-
return
|
|
2858
|
-
}
|
|
2859
|
-
|
|
2860
|
-
// Analyze function literal body for async operations
|
|
2861
|
-
isAsync := false
|
|
2862
|
-
if funcLit.Body != nil {
|
|
2863
|
-
isAsync = v.containsAsyncOperationsComplete(funcLit.Body, pkg)
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
// Store result in NodeData
|
|
2867
|
-
if nodeInfo == nil {
|
|
2868
|
-
nodeInfo = v.analysis.ensureNodeData(funcLit)
|
|
2869
|
-
}
|
|
2870
|
-
nodeInfo.InAsyncContext = isAsync
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
// buildMethodCallGraph builds a graph of which methods call which other methods
|
|
2874
|
-
func (v *analysisVisitor) buildMethodCallGraph() map[MethodKey][]MethodKey {
|
|
2875
|
-
methodCalls := make(map[MethodKey][]MethodKey)
|
|
2876
|
-
|
|
2877
|
-
// Iterate through all packages
|
|
2878
|
-
allPkgs := []*packages.Package{v.pkg}
|
|
2879
|
-
for _, pkg := range v.analysis.AllPackages {
|
|
2880
|
-
if pkg != v.pkg {
|
|
2881
|
-
allPkgs = append(allPkgs, pkg)
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
for _, pkg := range allPkgs {
|
|
2886
|
-
for _, file := range pkg.Syntax {
|
|
2887
|
-
for _, decl := range file.Decls {
|
|
2888
|
-
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
2889
|
-
methodKey := v.getMethodKey(funcDecl, pkg)
|
|
2890
|
-
|
|
2891
|
-
// Initialize the entry for this method
|
|
2892
|
-
if _, exists := methodCalls[methodKey]; !exists {
|
|
2893
|
-
methodCalls[methodKey] = []MethodKey{}
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
// Extract method calls from the function body
|
|
2897
|
-
if funcDecl.Body != nil {
|
|
2898
|
-
callees := v.extractMethodCalls(funcDecl.Body, pkg)
|
|
2899
|
-
methodCalls[methodKey] = callees
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2905
|
-
|
|
2906
|
-
return methodCalls
|
|
2907
|
-
}
|
|
2908
|
-
|
|
2909
|
-
// extractMethodCalls extracts all method and function calls from a node
|
|
2910
|
-
func (v *analysisVisitor) extractMethodCalls(node ast.Node, pkg *packages.Package) []MethodKey {
|
|
2911
|
-
var calls []MethodKey
|
|
2912
|
-
seen := make(map[MethodKey]bool)
|
|
2913
|
-
|
|
2914
|
-
ast.Inspect(node, func(n ast.Node) bool {
|
|
2915
|
-
if n == nil {
|
|
2916
|
-
return false
|
|
2917
|
-
}
|
|
2918
|
-
|
|
2919
|
-
if callExpr, ok := n.(*ast.CallExpr); ok {
|
|
2920
|
-
methodKeys := v.extractMethodKeysFromCall(callExpr, pkg)
|
|
2921
|
-
for _, methodKey := range methodKeys {
|
|
2922
|
-
if !seen[methodKey] {
|
|
2923
|
-
seen[methodKey] = true
|
|
2924
|
-
calls = append(calls, methodKey)
|
|
2925
|
-
}
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
return true
|
|
2930
|
-
})
|
|
2931
|
-
|
|
2932
|
-
return calls
|
|
2933
|
-
}
|
|
2934
|
-
|
|
2935
|
-
// extractMethodKeysFromCall extracts MethodKeys from a call expression
|
|
2936
|
-
// Returns multiple keys for interface method calls (one for each implementation)
|
|
2937
|
-
func (v *analysisVisitor) extractMethodKeysFromCall(callExpr *ast.CallExpr, pkg *packages.Package) []MethodKey {
|
|
2938
|
-
singleKey := v.extractMethodKeyFromCall(callExpr, pkg)
|
|
2939
|
-
if singleKey != nil {
|
|
2940
|
-
return []MethodKey{*singleKey}
|
|
2941
|
-
}
|
|
2942
|
-
|
|
2943
|
-
// Check if this is an interface method call
|
|
2944
|
-
if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
|
|
2945
|
-
if receiverType := pkg.TypesInfo.TypeOf(selExpr.X); receiverType != nil {
|
|
2946
|
-
if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
|
|
2947
|
-
methodName := selExpr.Sel.Name
|
|
2948
|
-
// Find all implementations of this interface method
|
|
2949
|
-
key := InterfaceMethodKey{
|
|
2950
|
-
InterfaceType: interfaceType.String(),
|
|
2951
|
-
MethodName: methodName,
|
|
2952
|
-
}
|
|
2953
|
-
if implementations, exists := v.analysis.InterfaceImplementations[key]; exists {
|
|
2954
|
-
var keys []MethodKey
|
|
2955
|
-
for _, impl := range implementations {
|
|
2956
|
-
keys = append(keys, MethodKey{
|
|
2957
|
-
PackagePath: impl.StructType.Obj().Pkg().Path(),
|
|
2958
|
-
ReceiverType: impl.StructType.Obj().Name(),
|
|
2959
|
-
MethodName: impl.Method.Name(),
|
|
2960
|
-
})
|
|
2961
|
-
}
|
|
2962
|
-
return keys
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
return nil
|
|
2969
|
-
}
|
|
2970
|
-
|
|
2971
|
-
// extractMethodKeyFromCall extracts a MethodKey from a call expression
|
|
2972
|
-
func (v *analysisVisitor) extractMethodKeyFromCall(callExpr *ast.CallExpr, pkg *packages.Package) *MethodKey {
|
|
2973
|
-
switch fun := callExpr.Fun.(type) {
|
|
2974
|
-
case *ast.Ident:
|
|
2975
|
-
// Direct function call
|
|
2976
|
-
if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
|
|
2977
|
-
if funcObj, ok := obj.(*types.Func); ok {
|
|
2978
|
-
pkgPath := pkg.Types.Path()
|
|
2979
|
-
if funcObj.Pkg() != nil {
|
|
2980
|
-
pkgPath = funcObj.Pkg().Path()
|
|
2981
|
-
}
|
|
2982
|
-
return &MethodKey{
|
|
2983
|
-
PackagePath: pkgPath,
|
|
2984
|
-
ReceiverType: "",
|
|
2985
|
-
MethodName: funcObj.Name(),
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
case *ast.SelectorExpr:
|
|
2991
|
-
// Package-level function call (e.g., time.Sleep)
|
|
2992
|
-
if ident, ok := fun.X.(*ast.Ident); ok {
|
|
2993
|
-
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
2994
|
-
if pkgName, isPkg := obj.(*types.PkgName); isPkg {
|
|
2995
|
-
return &MethodKey{
|
|
2996
|
-
PackagePath: pkgName.Imported().Path(),
|
|
2997
|
-
ReceiverType: "",
|
|
2998
|
-
MethodName: fun.Sel.Name,
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
|
-
// Check if this is an interface method call - if so, return nil
|
|
3005
|
-
// so extractMethodKeysFromCall can expand it to all implementations
|
|
3006
|
-
if receiverType := pkg.TypesInfo.TypeOf(fun.X); receiverType != nil {
|
|
3007
|
-
if _, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
|
|
3008
|
-
// This is an interface method call - return nil to let
|
|
3009
|
-
// extractMethodKeysFromCall handle expanding to implementations
|
|
3010
|
-
return nil
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
|
|
3014
|
-
// Method call on concrete objects
|
|
3015
|
-
if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
|
|
3016
|
-
if methodObj := selection.Obj(); methodObj != nil {
|
|
3017
|
-
receiverType := ""
|
|
3018
|
-
methodPkgPath := ""
|
|
3019
|
-
|
|
3020
|
-
// Get receiver type
|
|
3021
|
-
switch x := fun.X.(type) {
|
|
3022
|
-
case *ast.Ident:
|
|
3023
|
-
if obj := pkg.TypesInfo.Uses[x]; obj != nil {
|
|
3024
|
-
if varObj, ok := obj.(*types.Var); ok {
|
|
3025
|
-
receiverType = v.getTypeName(varObj.Type())
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
case *ast.SelectorExpr:
|
|
3029
|
-
if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
|
|
3030
|
-
receiverType = v.getTypeName(typeExpr)
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
// Get method's package path
|
|
3035
|
-
if methodFunc, ok := methodObj.(*types.Func); ok {
|
|
3036
|
-
if methodFunc.Pkg() != nil {
|
|
3037
|
-
methodPkgPath = methodFunc.Pkg().Path()
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
|
|
3041
|
-
if methodPkgPath == "" {
|
|
3042
|
-
methodPkgPath = pkg.Types.Path()
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3045
|
-
return &MethodKey{
|
|
3046
|
-
PackagePath: methodPkgPath,
|
|
3047
|
-
ReceiverType: receiverType,
|
|
3048
|
-
MethodName: methodObj.Name(),
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
|
-
|
|
3054
|
-
return nil
|
|
3055
|
-
}
|
|
3056
|
-
|
|
3057
|
-
// topologicalSortMethods performs a topological sort of methods based on their call dependencies
|
|
3058
|
-
// Returns sorted methods and methods involved in cycles
|
|
3059
|
-
func (v *analysisVisitor) topologicalSortMethods(methodCalls map[MethodKey][]MethodKey) ([]MethodKey, []MethodKey) {
|
|
3060
|
-
// Kahn's algorithm for topological sorting
|
|
3061
|
-
inDegree := make(map[MethodKey]int)
|
|
3062
|
-
graph := make(map[MethodKey][]MethodKey)
|
|
3063
|
-
|
|
3064
|
-
// Initialize in-degree counts and reverse graph
|
|
3065
|
-
for method := range methodCalls {
|
|
3066
|
-
inDegree[method] = 0
|
|
3067
|
-
graph[method] = []MethodKey{}
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
// Build reverse graph and count in-degrees
|
|
3071
|
-
// graph[dependency] = methods that depend on it
|
|
3072
|
-
for method, callees := range methodCalls {
|
|
3073
|
-
for _, callee := range callees {
|
|
3074
|
-
// Only create dependency edges for callees that exist in the call graph
|
|
3075
|
-
// This automatically excludes handwritten packages that aren't being compiled
|
|
3076
|
-
if _, exists := inDegree[callee]; exists {
|
|
3077
|
-
// Additionally, skip if the callee is from a handwritten package
|
|
3078
|
-
// This prevents our code from being blocked by unresolved handwritten package dependencies
|
|
3079
|
-
if !v.analysis.isHandwrittenPackage(callee.PackagePath) {
|
|
3080
|
-
graph[callee] = append(graph[callee], method)
|
|
3081
|
-
inDegree[method]++
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
|
|
3087
|
-
// Find methods with no dependencies (in-degree == 0)
|
|
3088
|
-
var queue []MethodKey
|
|
3089
|
-
for method, degree := range inDegree {
|
|
3090
|
-
if degree == 0 {
|
|
3091
|
-
queue = append(queue, method)
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
|
|
3095
|
-
var sorted []MethodKey
|
|
3096
|
-
|
|
3097
|
-
for len(queue) > 0 {
|
|
3098
|
-
// Remove method from queue
|
|
3099
|
-
current := queue[0]
|
|
3100
|
-
queue = queue[1:]
|
|
3101
|
-
sorted = append(sorted, current)
|
|
3102
|
-
|
|
3103
|
-
// For each method that depends on current
|
|
3104
|
-
for _, dependent := range graph[current] {
|
|
3105
|
-
inDegree[dependent]--
|
|
3106
|
-
if inDegree[dependent] == 0 {
|
|
3107
|
-
queue = append(queue, dependent)
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3111
|
-
|
|
3112
|
-
// Find methods in cycles (not in sorted list)
|
|
3113
|
-
var cycles []MethodKey
|
|
3114
|
-
if len(sorted) != len(methodCalls) {
|
|
3115
|
-
for method := range methodCalls {
|
|
3116
|
-
found := slices.Contains(sorted, method)
|
|
3117
|
-
if !found {
|
|
3118
|
-
cycles = append(cycles, method)
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
return sorted, cycles
|
|
3124
|
-
}
|
|
3125
|
-
|
|
3126
|
-
// analyzeMethodAsyncTopological analyzes a single method for async operations in topological order
|
|
3127
|
-
func (v *analysisVisitor) analyzeMethodAsyncTopological(methodKey MethodKey, callees []MethodKey) {
|
|
3128
|
-
// Check if method is from handwritten package
|
|
3129
|
-
isHandwrittenPackage := v.analysis.isHandwrittenPackage(methodKey.PackagePath)
|
|
3130
|
-
|
|
3131
|
-
if isHandwrittenPackage {
|
|
3132
|
-
// For handwritten packages, check if we have pre-loaded metadata
|
|
3133
|
-
_, hasMetadata := v.analysis.MethodAsyncStatus[methodKey]
|
|
3134
|
-
if hasMetadata {
|
|
3135
|
-
// Already set from metadata, don't override
|
|
3136
|
-
return
|
|
3137
|
-
}
|
|
3138
|
-
// No metadata means assume sync
|
|
3139
|
-
v.analysis.MethodAsyncStatus[methodKey] = false
|
|
3140
|
-
return
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3143
|
-
// Find the method declaration
|
|
3144
|
-
pkg := v.analysis.AllPackages[methodKey.PackagePath]
|
|
3145
|
-
if pkg == nil {
|
|
3146
|
-
if methodKey.PackagePath == v.pkg.Types.Path() {
|
|
3147
|
-
pkg = v.pkg
|
|
3148
|
-
}
|
|
3149
|
-
}
|
|
3150
|
-
|
|
3151
|
-
if pkg == nil {
|
|
3152
|
-
// Can't find package, assume sync
|
|
3153
|
-
v.analysis.MethodAsyncStatus[methodKey] = false
|
|
3154
|
-
return
|
|
3155
|
-
}
|
|
3156
|
-
|
|
3157
|
-
var funcDecl *ast.FuncDecl
|
|
3158
|
-
if methodKey.ReceiverType == "" {
|
|
3159
|
-
// Package-level function
|
|
3160
|
-
funcDecl = v.findFunctionDecl(methodKey.MethodName, pkg)
|
|
3161
|
-
} else {
|
|
3162
|
-
// Method with receiver
|
|
3163
|
-
funcDecl = v.findMethodDecl(methodKey.ReceiverType, methodKey.MethodName, pkg)
|
|
3164
|
-
}
|
|
3165
|
-
|
|
3166
|
-
if funcDecl == nil || funcDecl.Body == nil {
|
|
3167
|
-
// No body to analyze, assume sync
|
|
3168
|
-
v.analysis.MethodAsyncStatus[methodKey] = false
|
|
3169
|
-
return
|
|
3170
|
-
}
|
|
3171
|
-
|
|
3172
|
-
// Check if method contains async operations (including calls to async external methods)
|
|
3173
|
-
isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
|
|
3174
|
-
|
|
3175
|
-
// If not directly async, check if any callee from the call graph is async
|
|
3176
|
-
// (This catches calls to other methods in the same codebase)
|
|
3177
|
-
if !isAsync {
|
|
3178
|
-
for _, callee := range callees {
|
|
3179
|
-
if calleeAsync, exists := v.analysis.MethodAsyncStatus[callee]; exists && calleeAsync {
|
|
3180
|
-
isAsync = true
|
|
3181
|
-
break
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3186
|
-
v.analysis.MethodAsyncStatus[methodKey] = isAsync
|
|
3187
|
-
}
|
|
3188
|
-
|
|
3189
|
-
// getMethodKey creates a unique key for a method
|
|
3190
|
-
func (v *analysisVisitor) getMethodKey(funcDecl *ast.FuncDecl, pkg *packages.Package) MethodKey {
|
|
3191
|
-
packagePath := pkg.Types.Path()
|
|
3192
|
-
methodName := funcDecl.Name.Name
|
|
3193
|
-
receiverType := ""
|
|
3194
|
-
|
|
3195
|
-
if funcDecl.Recv != nil && len(funcDecl.Recv.List) > 0 {
|
|
3196
|
-
// Try to get receiver type from TypesInfo first
|
|
3197
|
-
if len(funcDecl.Recv.List[0].Names) > 0 && pkg.TypesInfo != nil {
|
|
3198
|
-
if def := pkg.TypesInfo.Defs[funcDecl.Recv.List[0].Names[0]]; def != nil {
|
|
3199
|
-
if vr, ok := def.(*types.Var); ok {
|
|
3200
|
-
receiverType = v.getTypeName(vr.Type())
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
|
|
3205
|
-
// Fallback to AST if TypesInfo is unavailable or failed
|
|
3206
|
-
if receiverType == "" {
|
|
3207
|
-
receiverType = v.getReceiverTypeFromAST(funcDecl.Recv.List[0].Type)
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
|
|
3211
|
-
return MethodKey{
|
|
3212
|
-
PackagePath: packagePath,
|
|
3213
|
-
ReceiverType: receiverType,
|
|
3214
|
-
MethodName: methodName,
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
|
|
3218
|
-
// getTypeName extracts a clean type name from a types.Type
|
|
3219
|
-
func (v *analysisVisitor) getTypeName(t types.Type) string {
|
|
3220
|
-
switch typ := t.(type) {
|
|
3221
|
-
case *types.Named:
|
|
3222
|
-
return typ.Obj().Name()
|
|
3223
|
-
case *types.Pointer:
|
|
3224
|
-
return v.getTypeName(typ.Elem())
|
|
3225
|
-
default:
|
|
3226
|
-
return typ.String()
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
|
|
3230
|
-
// getReceiverTypeFromAST extracts the receiver type name from AST when TypesInfo is unavailable
|
|
3231
|
-
func (v *analysisVisitor) getReceiverTypeFromAST(expr ast.Expr) string {
|
|
3232
|
-
switch t := expr.(type) {
|
|
3233
|
-
case *ast.StarExpr:
|
|
3234
|
-
// Pointer receiver: *Type
|
|
3235
|
-
return v.getReceiverTypeFromAST(t.X)
|
|
3236
|
-
case *ast.Ident:
|
|
3237
|
-
// Simple type name
|
|
3238
|
-
return t.Name
|
|
3239
|
-
case *ast.SelectorExpr:
|
|
3240
|
-
// Qualified type: pkg.Type
|
|
3241
|
-
return t.Sel.Name
|
|
3242
|
-
default:
|
|
3243
|
-
return ""
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
|
-
// containsAsyncOperationsComplete is a comprehensive async detection that handles method calls
|
|
3248
|
-
func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *packages.Package) bool {
|
|
3249
|
-
var hasAsync bool
|
|
3250
|
-
var asyncReasons []string
|
|
3251
|
-
|
|
3252
|
-
ast.Inspect(node, func(n ast.Node) bool {
|
|
3253
|
-
if n == nil {
|
|
3254
|
-
return false
|
|
3255
|
-
}
|
|
3256
|
-
if n != node {
|
|
3257
|
-
if _, ok := n.(*ast.FuncLit); ok {
|
|
3258
|
-
return false
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
|
|
3262
|
-
switch s := n.(type) {
|
|
3263
|
-
case *ast.SendStmt:
|
|
3264
|
-
// Channel send operation (ch <- value)
|
|
3265
|
-
hasAsync = true
|
|
3266
|
-
asyncReasons = append(asyncReasons, "channel send")
|
|
3267
|
-
return false
|
|
3268
|
-
|
|
3269
|
-
case *ast.UnaryExpr:
|
|
3270
|
-
// Channel receive operation (<-ch)
|
|
3271
|
-
if s.Op == token.ARROW {
|
|
3272
|
-
hasAsync = true
|
|
3273
|
-
asyncReasons = append(asyncReasons, "channel receive")
|
|
3274
|
-
return false
|
|
3275
|
-
}
|
|
3276
|
-
|
|
3277
|
-
case *ast.SelectStmt:
|
|
3278
|
-
// Select statement with channel operations
|
|
3279
|
-
hasAsync = true
|
|
3280
|
-
asyncReasons = append(asyncReasons, "select statement")
|
|
3281
|
-
return false
|
|
3282
|
-
|
|
3283
|
-
case *ast.CallExpr:
|
|
3284
|
-
// Check if we're calling a function known to be async
|
|
3285
|
-
if v.isCallAsync(s, pkg) {
|
|
3286
|
-
hasAsync = true
|
|
3287
|
-
return false
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
|
|
3291
|
-
return true
|
|
3292
|
-
})
|
|
3293
|
-
|
|
3294
|
-
return hasAsync
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
// isCallAsync determines if a call expression is async
|
|
3298
|
-
func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Package) bool {
|
|
3299
|
-
switch fun := callExpr.Fun.(type) {
|
|
3300
|
-
case *ast.Ident:
|
|
3301
|
-
// Direct function call
|
|
3302
|
-
if obj := pkg.TypesInfo.Uses[fun]; obj != nil {
|
|
3303
|
-
if funcObj, ok := obj.(*types.Func); ok {
|
|
3304
|
-
result := v.isFunctionAsync(funcObj, pkg)
|
|
3305
|
-
return result
|
|
3306
|
-
}
|
|
3307
|
-
// Check if this is a variable that returns async values
|
|
3308
|
-
// (e.g., indirect := sync.OnceValue(asyncFunc))
|
|
3309
|
-
if v.analysis.IsAsyncReturningVar(obj) {
|
|
3310
|
-
return true
|
|
3311
|
-
}
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
|
-
case *ast.SelectorExpr:
|
|
3315
|
-
// Handle package-level function calls (e.g., time.Sleep)
|
|
3316
|
-
if ident, ok := fun.X.(*ast.Ident); ok {
|
|
3317
|
-
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
3318
|
-
if pkgName, isPkg := obj.(*types.PkgName); isPkg {
|
|
3319
|
-
methodName := fun.Sel.Name
|
|
3320
|
-
pkgPath := pkgName.Imported().Path()
|
|
3321
|
-
// Check if this package-level function is async (empty TypeName)
|
|
3322
|
-
isAsync := v.analysis.IsMethodAsync(pkgPath, "", methodName)
|
|
3323
|
-
return isAsync
|
|
3324
|
-
}
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
|
|
3328
|
-
// Check if this is an interface method call
|
|
3329
|
-
if receiverType := pkg.TypesInfo.TypeOf(fun.X); receiverType != nil {
|
|
3330
|
-
if interfaceType, isInterface := receiverType.Underlying().(*types.Interface); isInterface {
|
|
3331
|
-
methodName := fun.Sel.Name
|
|
3332
|
-
// For interface method calls, check if the interface method is async
|
|
3333
|
-
result := v.analysis.IsInterfaceMethodAsync(interfaceType, methodName)
|
|
3334
|
-
return result
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
|
|
3338
|
-
// Method call on concrete objects
|
|
3339
|
-
if selection := pkg.TypesInfo.Selections[fun]; selection != nil {
|
|
3340
|
-
if methodObj := selection.Obj(); methodObj != nil {
|
|
3341
|
-
result := v.isMethodAsyncFromSelection(fun, methodObj, pkg)
|
|
3342
|
-
return result
|
|
3343
|
-
}
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
return false
|
|
3348
|
-
}
|
|
3349
|
-
|
|
3350
|
-
// isFunctionAsync checks if a function object is async
|
|
3351
|
-
func (v *analysisVisitor) isFunctionAsync(funcObj *types.Func, pkg *packages.Package) bool {
|
|
3352
|
-
// Check if it's from external package metadata
|
|
3353
|
-
if funcObj.Pkg() != nil && funcObj.Pkg() != pkg.Types {
|
|
3354
|
-
return v.analysis.IsMethodAsync(funcObj.Pkg().Path(), "", funcObj.Name())
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
// Check internal method status (should already be computed during analysis)
|
|
3358
|
-
methodKey := MethodKey{
|
|
3359
|
-
PackagePath: pkg.Types.Path(),
|
|
3360
|
-
ReceiverType: "",
|
|
3361
|
-
MethodName: funcObj.Name(),
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
|
|
3365
|
-
return status
|
|
3366
|
-
}
|
|
3367
|
-
|
|
3368
|
-
// Not found - should have been analyzed during analyzeAllMethodsAsync
|
|
3369
|
-
return false
|
|
3370
|
-
}
|
|
3371
|
-
|
|
3372
|
-
// isMethodAsyncFromSelection checks if a method call is async based on selection
|
|
3373
|
-
func (v *analysisVisitor) isMethodAsyncFromSelection(selExpr *ast.SelectorExpr, methodObj types.Object, pkg *packages.Package) bool {
|
|
3374
|
-
// Get receiver type - handle both direct identifiers and field access
|
|
3375
|
-
var receiverType string
|
|
3376
|
-
var methodPkgPath string
|
|
3377
|
-
|
|
3378
|
-
// Handle different receiver patterns
|
|
3379
|
-
switch x := selExpr.X.(type) {
|
|
3380
|
-
case *ast.Ident:
|
|
3381
|
-
// Direct variable (e.g., mtx.Lock())
|
|
3382
|
-
if obj := pkg.TypesInfo.Uses[x]; obj != nil {
|
|
3383
|
-
if varObj, ok := obj.(*types.Var); ok {
|
|
3384
|
-
receiverType = v.getTypeName(varObj.Type())
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
case *ast.SelectorExpr:
|
|
3388
|
-
// Field access (e.g., l.m.Lock() or d.mu.Lock())
|
|
3389
|
-
if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
|
|
3390
|
-
receiverType = v.getTypeName(typeExpr)
|
|
3391
|
-
}
|
|
3392
|
-
default:
|
|
3393
|
-
// For other cases, try to get type directly
|
|
3394
|
-
if typeExpr := pkg.TypesInfo.TypeOf(x); typeExpr != nil {
|
|
3395
|
-
receiverType = v.getTypeName(typeExpr)
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
// Get the method's package path
|
|
3400
|
-
if methodFunc, ok := methodObj.(*types.Func); ok {
|
|
3401
|
-
if methodFunc.Pkg() != nil {
|
|
3402
|
-
methodPkgPath = methodFunc.Pkg().Path()
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
|
|
3406
|
-
if methodPkgPath == "" {
|
|
3407
|
-
methodPkgPath = pkg.Types.Path()
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
|
-
// For external packages, check unified MethodAsyncStatus first
|
|
3411
|
-
// For internal packages, try analysis first, then fallback to lookup
|
|
3412
|
-
methodKey := MethodKey{
|
|
3413
|
-
PackagePath: methodPkgPath,
|
|
3414
|
-
ReceiverType: receiverType,
|
|
3415
|
-
MethodName: methodObj.Name(),
|
|
3416
|
-
}
|
|
3417
|
-
|
|
3418
|
-
if status, exists := v.analysis.MethodAsyncStatus[methodKey]; exists {
|
|
3419
|
-
return status
|
|
3420
|
-
}
|
|
3421
|
-
|
|
3422
|
-
// Not found - should have been analyzed during analyzeAllMethodsAsync
|
|
3423
|
-
return false
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
|
-
// findFunctionDecl finds a function declaration by name in a package
|
|
3427
|
-
func (v *analysisVisitor) findFunctionDecl(funcName string, pkg *packages.Package) *ast.FuncDecl {
|
|
3428
|
-
for _, file := range pkg.Syntax {
|
|
3429
|
-
for _, decl := range file.Decls {
|
|
3430
|
-
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
3431
|
-
if funcDecl.Name.Name == funcName && funcDecl.Recv == nil {
|
|
3432
|
-
return funcDecl
|
|
3433
|
-
}
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
return nil
|
|
3438
|
-
}
|
|
3439
|
-
|
|
3440
|
-
// findMethodDecl finds a method declaration by receiver type and method name
|
|
3441
|
-
func (v *analysisVisitor) findMethodDecl(receiverType, methodName string, pkg *packages.Package) *ast.FuncDecl {
|
|
3442
|
-
for _, file := range pkg.Syntax {
|
|
3443
|
-
for _, decl := range file.Decls {
|
|
3444
|
-
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
3445
|
-
if funcDecl.Name.Name == methodName && funcDecl.Recv != nil {
|
|
3446
|
-
if len(funcDecl.Recv.List) > 0 && len(funcDecl.Recv.List[0].Names) > 0 {
|
|
3447
|
-
if def := pkg.TypesInfo.Defs[funcDecl.Recv.List[0].Names[0]]; def != nil {
|
|
3448
|
-
if vr, ok := def.(*types.Var); ok {
|
|
3449
|
-
if v.getTypeName(vr.Type()) == receiverType {
|
|
3450
|
-
return funcDecl
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
return nil
|
|
3460
|
-
}
|
|
3461
|
-
|
|
3462
|
-
// IsLocalMethodAsync checks if a local method is async using pre-computed analysis
|
|
3463
|
-
func (a *Analysis) IsLocalMethodAsync(pkgPath, receiverType, methodName string) bool {
|
|
3464
|
-
methodKey := MethodKey{
|
|
3465
|
-
PackagePath: pkgPath,
|
|
3466
|
-
ReceiverType: receiverType,
|
|
3467
|
-
MethodName: methodName,
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3470
|
-
if status, exists := a.MethodAsyncStatus[methodKey]; exists {
|
|
3471
|
-
return status
|
|
3472
|
-
}
|
|
3473
|
-
|
|
3474
|
-
return false
|
|
3475
|
-
}
|