goscript 0.0.61 → 0.0.63
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 +62 -46
- package/compiler/analysis.go +621 -19
- package/compiler/analysis_test.go +3 -3
- package/compiler/assignment.go +100 -0
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +76 -16
- package/compiler/compiler_test.go +9 -9
- package/compiler/composite-lit.go +29 -8
- package/compiler/decl.go +20 -11
- package/compiler/expr-call-async.go +26 -1
- package/compiler/expr-call-builtins.go +60 -4
- package/compiler/expr-call-type-conversion.go +37 -5
- package/compiler/expr-call.go +26 -6
- package/compiler/expr-selector.go +35 -2
- package/compiler/expr-type.go +12 -2
- package/compiler/expr.go +61 -0
- package/compiler/index.test.ts +3 -1
- package/compiler/lit.go +13 -4
- package/compiler/spec-struct.go +30 -8
- package/compiler/spec-value.go +2 -2
- package/compiler/spec.go +23 -4
- package/compiler/stmt-assign.go +124 -0
- package/compiler/stmt-range.go +2 -2
- package/compiler/stmt.go +160 -14
- package/compiler/type-info.go +3 -5
- package/compiler/type-utils.go +40 -1
- package/compiler/type.go +52 -14
- package/dist/gs/builtin/builtin.d.ts +8 -1
- package/dist/gs/builtin/builtin.js +26 -1
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/errors.d.ts +1 -0
- package/dist/gs/builtin/errors.js +8 -0
- package/dist/gs/builtin/errors.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +5 -4
- package/dist/gs/builtin/slice.js +88 -51
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +23 -2
- package/dist/gs/builtin/type.js +125 -0
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/builtin/varRef.d.ts +3 -0
- package/dist/gs/builtin/varRef.js +6 -1
- package/dist/gs/builtin/varRef.js.map +1 -1
- package/dist/gs/bytes/reader.gs.d.ts +1 -1
- package/dist/gs/bytes/reader.gs.js +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/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.d.ts +3 -2
- package/dist/gs/reflect/map.js +37 -3
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +53 -12
- package/dist/gs/reflect/type.js +906 -31
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.d.ts +11 -12
- package/dist/gs/reflect/types.js +26 -15
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/value.d.ts +4 -4
- package/dist/gs/reflect/value.js +8 -2
- package/dist/gs/reflect/value.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +21 -0
- package/dist/gs/slices/slices.js +48 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.js +20 -2
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +3 -3
- package/dist/gs/sync/atomic/type.gs.js +13 -7
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
- package/dist/gs/unicode/utf8/utf8.js +10 -6
- package/dist/gs/unicode/utf8/utf8.js.map +1 -1
- package/go.mod +6 -6
- package/go.sum +12 -8
- package/gs/builtin/builtin.ts +27 -2
- package/gs/builtin/errors.ts +12 -0
- package/gs/builtin/slice.ts +126 -55
- package/gs/builtin/type.ts +159 -2
- package/gs/builtin/varRef.ts +8 -2
- package/gs/bytes/reader.gs.ts +2 -2
- package/gs/math/hypot.gs.test.ts +3 -1
- package/gs/math/pow10.gs.test.ts +5 -4
- package/gs/reflect/index.ts +3 -2
- package/gs/reflect/map.test.ts +7 -6
- package/gs/reflect/map.ts +49 -7
- package/gs/reflect/type.ts +1150 -57
- package/gs/reflect/types.ts +34 -21
- package/gs/reflect/value.ts +12 -6
- package/gs/slices/slices.ts +55 -0
- package/gs/strconv/atoi.gs.ts +18 -2
- package/gs/sync/atomic/type.gs.ts +15 -10
- package/gs/unicode/utf8/utf8.ts +12 -8
- package/package.json +23 -14
package/compiler/analysis.go
CHANGED
|
@@ -52,6 +52,10 @@ type ShadowingInfo struct {
|
|
|
52
52
|
ShadowedVariables map[string]types.Object
|
|
53
53
|
// TempVariables maps shadowed variable names to temporary variable names
|
|
54
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
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
// FunctionTypeInfo represents Go function type information for reflection
|
|
@@ -141,6 +145,11 @@ type Analysis struct {
|
|
|
141
145
|
// FunctionAssignments tracks which function literals are assigned to which variables
|
|
142
146
|
FunctionAssignments map[types.Object]ast.Node
|
|
143
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
|
+
|
|
144
153
|
// NamedBasicTypes tracks types that should be implemented as type aliases with standalone functions
|
|
145
154
|
// This includes named types with basic underlying types (like uint32, string) that have methods
|
|
146
155
|
NamedBasicTypes map[types.Type]bool
|
|
@@ -156,6 +165,16 @@ type Analysis struct {
|
|
|
156
165
|
// MethodAsyncStatus stores the async status of all methods analyzed
|
|
157
166
|
// This is computed once during analysis and reused during code generation
|
|
158
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
|
|
159
178
|
}
|
|
160
179
|
|
|
161
180
|
// PackageAnalysis holds cross-file analysis data for a package
|
|
@@ -175,6 +194,14 @@ type PackageAnalysis struct {
|
|
|
175
194
|
// TypeCalls maps file names to the types they reference from other files
|
|
176
195
|
// Key: filename (without .go extension), Value: map[sourceFile][]typeNames
|
|
177
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
|
|
178
205
|
}
|
|
179
206
|
|
|
180
207
|
// NewAnalysis creates a new Analysis instance.
|
|
@@ -184,18 +211,20 @@ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
|
|
|
184
211
|
}
|
|
185
212
|
|
|
186
213
|
return &Analysis{
|
|
187
|
-
VariableUsage:
|
|
188
|
-
Imports:
|
|
189
|
-
FunctionData:
|
|
190
|
-
NodeData:
|
|
191
|
-
FuncLitData:
|
|
192
|
-
ReflectedFunctions:
|
|
193
|
-
FunctionAssignments:
|
|
194
|
-
|
|
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),
|
|
195
222
|
NamedBasicTypes: make(map[types.Type]bool),
|
|
196
223
|
AllPackages: allPackages,
|
|
197
|
-
InterfaceImplementations:
|
|
198
|
-
MethodAsyncStatus:
|
|
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),
|
|
199
228
|
}
|
|
200
229
|
}
|
|
201
230
|
|
|
@@ -206,6 +235,8 @@ func NewPackageAnalysis() *PackageAnalysis {
|
|
|
206
235
|
FunctionCalls: make(map[string]map[string][]string),
|
|
207
236
|
TypeDefs: make(map[string][]string),
|
|
208
237
|
TypeCalls: make(map[string]map[string][]string),
|
|
238
|
+
VariableDefs: make(map[string][]string),
|
|
239
|
+
VariableCalls: make(map[string]map[string][]string),
|
|
209
240
|
}
|
|
210
241
|
}
|
|
211
242
|
|
|
@@ -313,6 +344,15 @@ func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
|
|
|
313
344
|
return false
|
|
314
345
|
}
|
|
315
346
|
|
|
347
|
+
// IsAsyncReturningVar returns whether the given variable holds a function that returns async values.
|
|
348
|
+
// This is true when the variable is assigned from a higher-order function that receives an async function literal.
|
|
349
|
+
func (a *Analysis) IsAsyncReturningVar(obj types.Object) bool {
|
|
350
|
+
if obj == nil {
|
|
351
|
+
return false
|
|
352
|
+
}
|
|
353
|
+
return a.AsyncReturningVars[obj]
|
|
354
|
+
}
|
|
355
|
+
|
|
316
356
|
func (a *Analysis) IsReceiverUsed(obj types.Object) bool {
|
|
317
357
|
if obj == nil {
|
|
318
358
|
return false
|
|
@@ -459,6 +499,10 @@ type analysisVisitor struct {
|
|
|
459
499
|
|
|
460
500
|
// currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
|
|
461
501
|
currentFuncLit *ast.FuncLit
|
|
502
|
+
|
|
503
|
+
// currentFilePath tracks the file path of the file we're currently analyzing.
|
|
504
|
+
// This is used to track which types are referenced in each file.
|
|
505
|
+
currentFilePath string
|
|
462
506
|
}
|
|
463
507
|
|
|
464
508
|
// getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
|
|
@@ -491,6 +535,18 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
491
535
|
if n.Tok == token.VAR {
|
|
492
536
|
for _, spec := range n.Specs {
|
|
493
537
|
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
538
|
+
// Track type references from variable declarations for synthetic import filtering
|
|
539
|
+
if valueSpec.Type != nil {
|
|
540
|
+
if t := v.pkg.TypesInfo.TypeOf(valueSpec.Type); t != nil {
|
|
541
|
+
v.trackTypeReference(t)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
for _, name := range valueSpec.Names {
|
|
545
|
+
if obj := v.pkg.TypesInfo.ObjectOf(name); obj != nil {
|
|
546
|
+
v.trackTypeReference(obj.Type())
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
494
550
|
// Process each declared variable (LHS)
|
|
495
551
|
for i, lhsIdent := range valueSpec.Names {
|
|
496
552
|
if lhsIdent.Name == "_" {
|
|
@@ -780,9 +836,30 @@ func (v *analysisVisitor) visitCallExpr(n *ast.CallExpr) ast.Visitor {
|
|
|
780
836
|
// Check for implicit address-taking in method calls with pointer receivers
|
|
781
837
|
v.checkImplicitAddressTaking(n)
|
|
782
838
|
|
|
839
|
+
// Check for address-of expressions in function arguments
|
|
840
|
+
v.checkAddressOfInArguments(n)
|
|
841
|
+
|
|
783
842
|
return v
|
|
784
843
|
}
|
|
785
844
|
|
|
845
|
+
// checkAddressOfInArguments detects when &variable is passed as a function argument.
|
|
846
|
+
// Example: json.Unmarshal(data, &person) where person needs to be marked as NeedsVarRef
|
|
847
|
+
func (v *analysisVisitor) checkAddressOfInArguments(callExpr *ast.CallExpr) {
|
|
848
|
+
for _, arg := range callExpr.Args {
|
|
849
|
+
if unaryExpr, ok := arg.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
|
|
850
|
+
if ident, ok := unaryExpr.X.(*ast.Ident); ok {
|
|
851
|
+
if obj := v.pkg.TypesInfo.ObjectOf(ident); obj != nil {
|
|
852
|
+
usageInfo := v.getOrCreateUsageInfo(obj)
|
|
853
|
+
usageInfo.Destinations = append(usageInfo.Destinations, AssignmentInfo{
|
|
854
|
+
Object: nil,
|
|
855
|
+
Type: AddressOfAssignment,
|
|
856
|
+
})
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
786
863
|
// checkImplicitAddressTaking detects when a method call with a pointer receiver
|
|
787
864
|
// is called on a non-pointer variable, which requires implicit address-taking.
|
|
788
865
|
// Example: var s MySlice; s.Add(10) where Add has receiver *MySlice
|
|
@@ -909,9 +986,63 @@ func (v *analysisVisitor) visitAssignStmt(n *ast.AssignStmt) ast.Visitor {
|
|
|
909
986
|
}
|
|
910
987
|
}
|
|
911
988
|
|
|
989
|
+
// NOTE: Async-returning variable tracking (trackAsyncReturningVar) is done in a separate pass
|
|
990
|
+
// after function literals are analyzed for async status. See trackAsyncReturningVarsAllFiles.
|
|
991
|
+
|
|
912
992
|
return v
|
|
913
993
|
}
|
|
914
994
|
|
|
995
|
+
// trackAsyncReturningVar tracks variables that are assigned from higher-order function calls
|
|
996
|
+
// where one of the arguments is an async function literal.
|
|
997
|
+
// Pattern: x := higherOrderFunc(asyncFuncLit)
|
|
998
|
+
// This is needed because when sync.OnceValue(asyncFunc) is called, the result is a function
|
|
999
|
+
// that returns a Promise, and callers of x() need to await the result.
|
|
1000
|
+
func (v *analysisVisitor) trackAsyncReturningVar(lhs ast.Expr, rhs ast.Expr) {
|
|
1001
|
+
// LHS must be an identifier
|
|
1002
|
+
lhsIdent, ok := lhs.(*ast.Ident)
|
|
1003
|
+
if !ok {
|
|
1004
|
+
return
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// RHS must be a call expression
|
|
1008
|
+
callExpr, ok := rhs.(*ast.CallExpr)
|
|
1009
|
+
if !ok {
|
|
1010
|
+
return
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// The result type of the call must be a function type
|
|
1014
|
+
rhsType := v.pkg.TypesInfo.TypeOf(rhs)
|
|
1015
|
+
if rhsType == nil {
|
|
1016
|
+
return
|
|
1017
|
+
}
|
|
1018
|
+
_, isFunc := rhsType.Underlying().(*types.Signature)
|
|
1019
|
+
if !isFunc {
|
|
1020
|
+
return
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Check if any argument is an async function literal
|
|
1024
|
+
// Use containsAsyncOperationsComplete to check the function body directly
|
|
1025
|
+
// rather than relying on the InAsyncContext flag which may not be set yet
|
|
1026
|
+
hasAsyncArg := false
|
|
1027
|
+
for _, arg := range callExpr.Args {
|
|
1028
|
+
if funcLit, ok := arg.(*ast.FuncLit); ok {
|
|
1029
|
+
if funcLit.Body != nil && v.containsAsyncOperationsComplete(funcLit.Body, v.pkg) {
|
|
1030
|
+
hasAsyncArg = true
|
|
1031
|
+
break
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if !hasAsyncArg {
|
|
1037
|
+
return
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Mark the LHS variable as returning async values
|
|
1041
|
+
if obj := v.pkg.TypesInfo.ObjectOf(lhsIdent); obj != nil {
|
|
1042
|
+
v.analysis.AsyncReturningVars[obj] = true
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
915
1046
|
// visitReturnStmt handles return statement analysis
|
|
916
1047
|
func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
|
|
917
1048
|
nodeInfo := v.analysis.ensureNodeData(n)
|
|
@@ -940,12 +1071,28 @@ func (v *analysisVisitor) visitDeclStmt(n *ast.DeclStmt) ast.Visitor {
|
|
|
940
1071
|
// Check if we're inside a function (either FuncDecl or FuncLit)
|
|
941
1072
|
isInsideFunction := v.currentFuncDecl != nil || v.currentFuncLit != nil
|
|
942
1073
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1074
|
+
for _, spec := range genDecl.Specs {
|
|
1075
|
+
if isInsideFunction {
|
|
1076
|
+
// Mark all specs in this declaration as being inside a function
|
|
946
1077
|
nodeInfo := v.analysis.ensureNodeData(spec)
|
|
947
1078
|
nodeInfo.IsInsideFunction = true
|
|
948
1079
|
}
|
|
1080
|
+
|
|
1081
|
+
// Track type references from variable declarations (e.g., var w MyWriter)
|
|
1082
|
+
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
1083
|
+
// Track explicit type if present
|
|
1084
|
+
if valueSpec.Type != nil {
|
|
1085
|
+
if t := v.pkg.TypesInfo.TypeOf(valueSpec.Type); t != nil {
|
|
1086
|
+
v.trackTypeReference(t)
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
// Also track types inferred from values
|
|
1090
|
+
for _, name := range valueSpec.Names {
|
|
1091
|
+
if obj := v.pkg.TypesInfo.ObjectOf(name); obj != nil {
|
|
1092
|
+
v.trackTypeReference(obj.Type())
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
949
1096
|
}
|
|
950
1097
|
}
|
|
951
1098
|
return v
|
|
@@ -974,6 +1121,9 @@ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) as
|
|
|
974
1121
|
return v
|
|
975
1122
|
}
|
|
976
1123
|
|
|
1124
|
+
// Track the asserted type for synthetic import filtering
|
|
1125
|
+
v.trackTypeReference(assertedType)
|
|
1126
|
+
|
|
977
1127
|
// Check if the asserted type is an interface
|
|
978
1128
|
interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
|
|
979
1129
|
if !isInterface {
|
|
@@ -1011,10 +1161,36 @@ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) as
|
|
|
1011
1161
|
return v
|
|
1012
1162
|
}
|
|
1013
1163
|
|
|
1164
|
+
// trackTypeReference records that a named type is referenced in the current file.
|
|
1165
|
+
// This is used to filter synthetic imports to only include packages actually needed.
|
|
1166
|
+
func (v *analysisVisitor) trackTypeReference(t types.Type) {
|
|
1167
|
+
if t == nil || v.currentFilePath == "" {
|
|
1168
|
+
return
|
|
1169
|
+
}
|
|
1170
|
+
// Unwrap pointers
|
|
1171
|
+
if ptr, ok := t.(*types.Pointer); ok {
|
|
1172
|
+
t = ptr.Elem()
|
|
1173
|
+
}
|
|
1174
|
+
// Track named types per file
|
|
1175
|
+
if named, ok := t.(*types.Named); ok {
|
|
1176
|
+
if v.analysis.ReferencedTypesPerFile[v.currentFilePath] == nil {
|
|
1177
|
+
v.analysis.ReferencedTypesPerFile[v.currentFilePath] = make(map[*types.Named]bool)
|
|
1178
|
+
}
|
|
1179
|
+
v.analysis.ReferencedTypesPerFile[v.currentFilePath][named] = true
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1014
1183
|
// visitCompositeLit analyzes composite literals for address-of expressions
|
|
1015
1184
|
// This is important for detecting cases like: arr := []interface{}{value1, &value2}
|
|
1016
1185
|
// where value2 needs to be marked as NeedsVarRef due to the &value2 usage
|
|
1017
1186
|
func (v *analysisVisitor) visitCompositeLit(compLit *ast.CompositeLit) ast.Visitor {
|
|
1187
|
+
// Track the type of this composite literal for synthetic import filtering
|
|
1188
|
+
if compLit.Type != nil {
|
|
1189
|
+
if t := v.pkg.TypesInfo.TypeOf(compLit.Type); t != nil {
|
|
1190
|
+
v.trackTypeReference(t)
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1018
1194
|
// Analyze each element of the composite literal
|
|
1019
1195
|
for _, elt := range compLit.Elts {
|
|
1020
1196
|
// Handle both direct elements and key-value pairs
|
|
@@ -1174,7 +1350,11 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
|
|
|
1174
1350
|
}
|
|
1175
1351
|
|
|
1176
1352
|
// First pass: analyze all declarations and statements across all files
|
|
1177
|
-
for
|
|
1353
|
+
for i, file := range pkg.Syntax {
|
|
1354
|
+
// Set the current file path for per-file type tracking
|
|
1355
|
+
if i < len(pkg.CompiledGoFiles) {
|
|
1356
|
+
visitor.currentFilePath = pkg.CompiledGoFiles[i]
|
|
1357
|
+
}
|
|
1178
1358
|
ast.Walk(visitor, file)
|
|
1179
1359
|
}
|
|
1180
1360
|
|
|
@@ -1207,9 +1387,153 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
|
|
|
1207
1387
|
// Interface implementation async status is now updated on-demand in IsInterfaceMethodAsync
|
|
1208
1388
|
visitor.analyzeAllMethodsAsync()
|
|
1209
1389
|
|
|
1390
|
+
// Fourth pass: collect imports needed by promoted methods from embedded structs
|
|
1391
|
+
analysis.addImportsForPromotedMethods(pkg)
|
|
1392
|
+
|
|
1210
1393
|
return analysis
|
|
1211
1394
|
}
|
|
1212
1395
|
|
|
1396
|
+
// addImportsForPromotedMethods scans struct types that are actually referenced in each file
|
|
1397
|
+
// and adds imports for any packages referenced by the promoted methods' parameter/return types.
|
|
1398
|
+
// This generates per-file synthetic imports to avoid adding unused imports.
|
|
1399
|
+
func (a *Analysis) addImportsForPromotedMethods(pkg *packages.Package) {
|
|
1400
|
+
// Process each file's referenced types separately
|
|
1401
|
+
for filePath, referencedTypes := range a.ReferencedTypesPerFile {
|
|
1402
|
+
// Collect package imports needed for this specific file
|
|
1403
|
+
packagesToAdd := make(map[string]*types.Package)
|
|
1404
|
+
|
|
1405
|
+
// Only process types that are actually referenced in this file
|
|
1406
|
+
// and are defined in the current package
|
|
1407
|
+
for namedType := range referencedTypes {
|
|
1408
|
+
// Skip types from other packages - we only need to process types defined in this package
|
|
1409
|
+
if namedType.Obj().Pkg() != pkg.Types {
|
|
1410
|
+
continue
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// Check if it's a struct
|
|
1414
|
+
structType, ok := namedType.Underlying().(*types.Struct)
|
|
1415
|
+
if !ok {
|
|
1416
|
+
continue
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Look for embedded fields
|
|
1420
|
+
for i := 0; i < structType.NumFields(); i++ {
|
|
1421
|
+
field := structType.Field(i)
|
|
1422
|
+
if !field.Embedded() {
|
|
1423
|
+
continue
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// Get the type of the embedded field
|
|
1427
|
+
embeddedType := field.Type()
|
|
1428
|
+
|
|
1429
|
+
// Handle pointer to embedded type
|
|
1430
|
+
if ptr, ok := embeddedType.(*types.Pointer); ok {
|
|
1431
|
+
embeddedType = ptr.Elem()
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// Use method set to get all promoted methods including pointer receiver methods
|
|
1435
|
+
// This matches Go's behavior where embedding T promotes both T and *T methods
|
|
1436
|
+
methodSetType := embeddedType
|
|
1437
|
+
if _, isPtr := embeddedType.(*types.Pointer); !isPtr {
|
|
1438
|
+
if _, isInterface := embeddedType.Underlying().(*types.Interface); !isInterface {
|
|
1439
|
+
methodSetType = types.NewPointer(embeddedType)
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
embeddedMethodSet := types.NewMethodSet(methodSetType)
|
|
1443
|
+
|
|
1444
|
+
// Scan all methods in the method set
|
|
1445
|
+
for j := 0; j < embeddedMethodSet.Len(); j++ {
|
|
1446
|
+
selection := embeddedMethodSet.At(j)
|
|
1447
|
+
method := selection.Obj()
|
|
1448
|
+
sig, ok := method.Type().(*types.Signature)
|
|
1449
|
+
if !ok {
|
|
1450
|
+
continue
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Scan parameters
|
|
1454
|
+
if sig.Params() != nil {
|
|
1455
|
+
for k := 0; k < sig.Params().Len(); k++ {
|
|
1456
|
+
param := sig.Params().At(k)
|
|
1457
|
+
a.collectPackageFromType(param.Type(), pkg.Types, packagesToAdd)
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// Scan results
|
|
1462
|
+
if sig.Results() != nil {
|
|
1463
|
+
for k := 0; k < sig.Results().Len(); k++ {
|
|
1464
|
+
result := sig.Results().At(k)
|
|
1465
|
+
a.collectPackageFromType(result.Type(), pkg.Types, packagesToAdd)
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Store the synthetic imports for this file
|
|
1473
|
+
if len(packagesToAdd) > 0 {
|
|
1474
|
+
fileImports := make(map[string]*fileImport)
|
|
1475
|
+
for pkgName, pkgObj := range packagesToAdd {
|
|
1476
|
+
tsImportPath := "@goscript/" + pkgObj.Path()
|
|
1477
|
+
fileImports[pkgName] = &fileImport{
|
|
1478
|
+
importPath: tsImportPath,
|
|
1479
|
+
importVars: make(map[string]struct{}),
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
a.SyntheticImportsPerFile[filePath] = fileImports
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// collectPackageFromType recursively collects packages referenced by a type.
|
|
1488
|
+
func (a *Analysis) collectPackageFromType(t types.Type, currentPkg *types.Package, packagesToAdd map[string]*types.Package) {
|
|
1489
|
+
switch typ := t.(type) {
|
|
1490
|
+
case *types.Named:
|
|
1491
|
+
pkg := typ.Obj().Pkg()
|
|
1492
|
+
if pkg != nil && pkg != currentPkg {
|
|
1493
|
+
packagesToAdd[pkg.Name()] = pkg
|
|
1494
|
+
}
|
|
1495
|
+
// Check type arguments for generics
|
|
1496
|
+
if typ.TypeArgs() != nil {
|
|
1497
|
+
for i := 0; i < typ.TypeArgs().Len(); i++ {
|
|
1498
|
+
a.collectPackageFromType(typ.TypeArgs().At(i), currentPkg, packagesToAdd)
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
case *types.Interface:
|
|
1502
|
+
// For interfaces, we need to check embedded interfaces and method signatures
|
|
1503
|
+
for i := 0; i < typ.NumEmbeddeds(); i++ {
|
|
1504
|
+
a.collectPackageFromType(typ.EmbeddedType(i), currentPkg, packagesToAdd)
|
|
1505
|
+
}
|
|
1506
|
+
for i := 0; i < typ.NumExplicitMethods(); i++ {
|
|
1507
|
+
method := typ.ExplicitMethod(i)
|
|
1508
|
+
a.collectPackageFromType(method.Type(), currentPkg, packagesToAdd)
|
|
1509
|
+
}
|
|
1510
|
+
case *types.Pointer:
|
|
1511
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1512
|
+
case *types.Slice:
|
|
1513
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1514
|
+
case *types.Array:
|
|
1515
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1516
|
+
case *types.Map:
|
|
1517
|
+
a.collectPackageFromType(typ.Key(), currentPkg, packagesToAdd)
|
|
1518
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1519
|
+
case *types.Chan:
|
|
1520
|
+
a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
|
|
1521
|
+
case *types.Signature:
|
|
1522
|
+
// Collect from parameters
|
|
1523
|
+
if typ.Params() != nil {
|
|
1524
|
+
for i := 0; i < typ.Params().Len(); i++ {
|
|
1525
|
+
a.collectPackageFromType(typ.Params().At(i).Type(), currentPkg, packagesToAdd)
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
// Collect from results
|
|
1529
|
+
if typ.Results() != nil {
|
|
1530
|
+
for i := 0; i < typ.Results().Len(); i++ {
|
|
1531
|
+
a.collectPackageFromType(typ.Results().At(i).Type(), currentPkg, packagesToAdd)
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1213
1537
|
// AnalyzePackageImports performs package-level analysis to collect function definitions
|
|
1214
1538
|
// and calls across all files in the package for auto-import generation
|
|
1215
1539
|
func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
@@ -1221,19 +1545,50 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1221
1545
|
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1222
1546
|
|
|
1223
1547
|
var functions []string
|
|
1224
|
-
var
|
|
1548
|
+
var typeNames []string
|
|
1549
|
+
var variables []string
|
|
1225
1550
|
for _, decl := range syntax.Decls {
|
|
1226
1551
|
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
1227
|
-
//
|
|
1552
|
+
// Collect top-level functions (not methods)
|
|
1228
1553
|
if funcDecl.Recv == nil {
|
|
1229
1554
|
functions = append(functions, funcDecl.Name.Name)
|
|
1555
|
+
} else {
|
|
1556
|
+
// Check if this is a method on a wrapper type (named basic type)
|
|
1557
|
+
// If so, it will be compiled as TypeName_MethodName function
|
|
1558
|
+
if len(funcDecl.Recv.List) > 0 {
|
|
1559
|
+
recvType := funcDecl.Recv.List[0].Type
|
|
1560
|
+
// Handle pointer receiver (*Type)
|
|
1561
|
+
if starExpr, ok := recvType.(*ast.StarExpr); ok {
|
|
1562
|
+
recvType = starExpr.X
|
|
1563
|
+
}
|
|
1564
|
+
if recvIdent, ok := recvType.(*ast.Ident); ok {
|
|
1565
|
+
// Check if this receiver type is a wrapper type
|
|
1566
|
+
if obj := pkg.TypesInfo.Uses[recvIdent]; obj != nil {
|
|
1567
|
+
if typeName, ok := obj.(*types.TypeName); ok {
|
|
1568
|
+
if namedType, ok := typeName.Type().(*types.Named); ok {
|
|
1569
|
+
if _, ok := namedType.Underlying().(*types.Basic); ok {
|
|
1570
|
+
// This is a method on a wrapper type
|
|
1571
|
+
funcName := recvIdent.Name + "_" + funcDecl.Name.Name
|
|
1572
|
+
functions = append(functions, funcName)
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1230
1579
|
}
|
|
1231
1580
|
}
|
|
1232
1581
|
if genDecl, ok := decl.(*ast.GenDecl); ok {
|
|
1233
1582
|
// Collect type declarations
|
|
1234
1583
|
for _, spec := range genDecl.Specs {
|
|
1235
1584
|
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
1236
|
-
|
|
1585
|
+
typeNames = append(typeNames, typeSpec.Name.Name)
|
|
1586
|
+
}
|
|
1587
|
+
// Collect variable/constant declarations
|
|
1588
|
+
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
1589
|
+
for _, name := range valueSpec.Names {
|
|
1590
|
+
variables = append(variables, name.Name)
|
|
1591
|
+
}
|
|
1237
1592
|
}
|
|
1238
1593
|
}
|
|
1239
1594
|
}
|
|
@@ -1242,8 +1597,11 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1242
1597
|
if len(functions) > 0 {
|
|
1243
1598
|
analysis.FunctionDefs[baseFileName] = functions
|
|
1244
1599
|
}
|
|
1245
|
-
if len(
|
|
1246
|
-
analysis.TypeDefs[baseFileName] =
|
|
1600
|
+
if len(typeNames) > 0 {
|
|
1601
|
+
analysis.TypeDefs[baseFileName] = typeNames
|
|
1602
|
+
}
|
|
1603
|
+
if len(variables) > 0 {
|
|
1604
|
+
analysis.VariableDefs[baseFileName] = variables
|
|
1247
1605
|
}
|
|
1248
1606
|
}
|
|
1249
1607
|
|
|
@@ -1343,6 +1701,172 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
|
|
|
1343
1701
|
}
|
|
1344
1702
|
}
|
|
1345
1703
|
|
|
1704
|
+
// Fourth pass: analyze variable references and determine which need imports
|
|
1705
|
+
for i, syntax := range pkg.Syntax {
|
|
1706
|
+
fileName := pkg.CompiledGoFiles[i]
|
|
1707
|
+
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1708
|
+
|
|
1709
|
+
// Find all variable references in this file
|
|
1710
|
+
varRefsFromOtherFiles := make(map[string][]string)
|
|
1711
|
+
|
|
1712
|
+
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1713
|
+
// Look for identifier references
|
|
1714
|
+
if ident, ok := n.(*ast.Ident); ok {
|
|
1715
|
+
// Check if this identifier refers to a package-level variable
|
|
1716
|
+
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
1717
|
+
if varObj, ok := obj.(*types.Var); ok {
|
|
1718
|
+
// Only track package-level variables (not function parameters or local vars)
|
|
1719
|
+
if varObj.Parent() == pkg.Types.Scope() {
|
|
1720
|
+
varName := ident.Name
|
|
1721
|
+
|
|
1722
|
+
// Check if this variable is defined in the current file
|
|
1723
|
+
currentFileVars := analysis.VariableDefs[baseFileName]
|
|
1724
|
+
isDefinedInCurrentFile := slices.Contains(currentFileVars, varName)
|
|
1725
|
+
|
|
1726
|
+
// If not defined in current file, find which file defines it
|
|
1727
|
+
if !isDefinedInCurrentFile {
|
|
1728
|
+
for sourceFile, vars := range analysis.VariableDefs {
|
|
1729
|
+
if sourceFile == baseFileName {
|
|
1730
|
+
continue // Skip current file
|
|
1731
|
+
}
|
|
1732
|
+
if slices.Contains(vars, varName) {
|
|
1733
|
+
// Found the variable in another file
|
|
1734
|
+
if varRefsFromOtherFiles[sourceFile] == nil {
|
|
1735
|
+
varRefsFromOtherFiles[sourceFile] = []string{}
|
|
1736
|
+
}
|
|
1737
|
+
// Check if already added to avoid duplicates
|
|
1738
|
+
found := slices.Contains(varRefsFromOtherFiles[sourceFile], varName)
|
|
1739
|
+
if !found {
|
|
1740
|
+
varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], varName)
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
// Also check for constants
|
|
1748
|
+
if constObj, ok := obj.(*types.Const); ok {
|
|
1749
|
+
// Only track package-level constants
|
|
1750
|
+
if constObj.Parent() == pkg.Types.Scope() {
|
|
1751
|
+
constName := ident.Name
|
|
1752
|
+
|
|
1753
|
+
// Check if this constant is defined in the current file
|
|
1754
|
+
currentFileVars := analysis.VariableDefs[baseFileName]
|
|
1755
|
+
isDefinedInCurrentFile := slices.Contains(currentFileVars, constName)
|
|
1756
|
+
|
|
1757
|
+
// If not defined in current file, find which file defines it
|
|
1758
|
+
if !isDefinedInCurrentFile {
|
|
1759
|
+
for sourceFile, vars := range analysis.VariableDefs {
|
|
1760
|
+
if sourceFile == baseFileName {
|
|
1761
|
+
continue // Skip current file
|
|
1762
|
+
}
|
|
1763
|
+
if slices.Contains(vars, constName) {
|
|
1764
|
+
// Found the constant in another file
|
|
1765
|
+
if varRefsFromOtherFiles[sourceFile] == nil {
|
|
1766
|
+
varRefsFromOtherFiles[sourceFile] = []string{}
|
|
1767
|
+
}
|
|
1768
|
+
// Check if already added to avoid duplicates
|
|
1769
|
+
found := slices.Contains(varRefsFromOtherFiles[sourceFile], constName)
|
|
1770
|
+
if !found {
|
|
1771
|
+
varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], constName)
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return true
|
|
1781
|
+
})
|
|
1782
|
+
|
|
1783
|
+
if len(varRefsFromOtherFiles) > 0 {
|
|
1784
|
+
analysis.VariableCalls[baseFileName] = varRefsFromOtherFiles
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// Fifth pass: analyze method calls on wrapper types (named basic types with methods)
|
|
1789
|
+
// These generate TypeName_MethodName function calls that need to be imported
|
|
1790
|
+
for i, syntax := range pkg.Syntax {
|
|
1791
|
+
fileName := pkg.CompiledGoFiles[i]
|
|
1792
|
+
baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
|
|
1793
|
+
|
|
1794
|
+
// Find all method calls on wrapper types in this file
|
|
1795
|
+
ast.Inspect(syntax, func(n ast.Node) bool {
|
|
1796
|
+
callExpr, ok := n.(*ast.CallExpr)
|
|
1797
|
+
if !ok {
|
|
1798
|
+
return true
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Check if this is a method call (selector expression)
|
|
1802
|
+
selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
1803
|
+
if !ok {
|
|
1804
|
+
return true
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// Get the type of the receiver
|
|
1808
|
+
receiverType := pkg.TypesInfo.TypeOf(selectorExpr.X)
|
|
1809
|
+
if receiverType == nil {
|
|
1810
|
+
return true
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// Check if this is a wrapper type (named type with basic underlying type and methods)
|
|
1814
|
+
namedType, ok := receiverType.(*types.Named)
|
|
1815
|
+
if !ok {
|
|
1816
|
+
return true
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// Check if it has a basic underlying type
|
|
1820
|
+
if _, ok := namedType.Underlying().(*types.Basic); !ok {
|
|
1821
|
+
return true
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// Check if this type is defined in the same package
|
|
1825
|
+
obj := namedType.Obj()
|
|
1826
|
+
if obj == nil || obj.Pkg() == nil || obj.Pkg() != pkg.Types {
|
|
1827
|
+
return true // Not from this package
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// Check if this type has the method being called
|
|
1831
|
+
methodName := selectorExpr.Sel.Name
|
|
1832
|
+
found := false
|
|
1833
|
+
for j := 0; j < namedType.NumMethods(); j++ {
|
|
1834
|
+
if namedType.Method(j).Name() == methodName {
|
|
1835
|
+
found = true
|
|
1836
|
+
break
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
if !found {
|
|
1840
|
+
return true
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Generate the function name: TypeName_MethodName
|
|
1844
|
+
funcName := obj.Name() + "_" + methodName
|
|
1845
|
+
|
|
1846
|
+
// Find which file defines this function
|
|
1847
|
+
for sourceFile, funcs := range analysis.FunctionDefs {
|
|
1848
|
+
if sourceFile == baseFileName {
|
|
1849
|
+
continue // Skip current file
|
|
1850
|
+
}
|
|
1851
|
+
if slices.Contains(funcs, funcName) {
|
|
1852
|
+
// Found the function in another file
|
|
1853
|
+
if analysis.FunctionCalls[baseFileName] == nil {
|
|
1854
|
+
analysis.FunctionCalls[baseFileName] = make(map[string][]string)
|
|
1855
|
+
}
|
|
1856
|
+
if analysis.FunctionCalls[baseFileName][sourceFile] == nil {
|
|
1857
|
+
analysis.FunctionCalls[baseFileName][sourceFile] = []string{}
|
|
1858
|
+
}
|
|
1859
|
+
// Check if already added to avoid duplicates
|
|
1860
|
+
if !slices.Contains(analysis.FunctionCalls[baseFileName][sourceFile], funcName) {
|
|
1861
|
+
analysis.FunctionCalls[baseFileName][sourceFile] = append(analysis.FunctionCalls[baseFileName][sourceFile], funcName)
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
return true
|
|
1867
|
+
})
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1346
1870
|
return analysis
|
|
1347
1871
|
}
|
|
1348
1872
|
|
|
@@ -1546,6 +2070,7 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
|
|
|
1546
2070
|
shadowingInfo := &ShadowingInfo{
|
|
1547
2071
|
ShadowedVariables: make(map[string]types.Object),
|
|
1548
2072
|
TempVariables: make(map[string]string),
|
|
2073
|
+
TypeShadowedVars: make(map[string]string),
|
|
1549
2074
|
}
|
|
1550
2075
|
|
|
1551
2076
|
hasShadowing := false
|
|
@@ -1563,12 +2088,60 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
|
|
|
1563
2088
|
v.findVariableUsageInExpr(rhsExpr, lhsVarNames, shadowingInfo, &hasShadowing)
|
|
1564
2089
|
}
|
|
1565
2090
|
|
|
2091
|
+
// Check for type shadowing: variable name matches a type name used in its initialization
|
|
2092
|
+
// e.g., field := field{...} where the variable 'field' shadows the type 'field'
|
|
2093
|
+
if assignStmt.Tok == token.DEFINE {
|
|
2094
|
+
for i, lhsExpr := range assignStmt.Lhs {
|
|
2095
|
+
if i < len(assignStmt.Rhs) {
|
|
2096
|
+
if lhsIdent, ok := lhsExpr.(*ast.Ident); ok && lhsIdent.Name != "_" {
|
|
2097
|
+
if typeName := v.findTypeShadowing(lhsIdent.Name, assignStmt.Rhs[i]); typeName != "" {
|
|
2098
|
+
shadowingInfo.TypeShadowedVars[lhsIdent.Name] = lhsIdent.Name + "_"
|
|
2099
|
+
hasShadowing = true
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
1566
2106
|
if hasShadowing {
|
|
1567
2107
|
return shadowingInfo
|
|
1568
2108
|
}
|
|
1569
2109
|
return nil
|
|
1570
2110
|
}
|
|
1571
2111
|
|
|
2112
|
+
// findTypeShadowing checks if the given variable name matches a type name used in the RHS expression.
|
|
2113
|
+
// Returns the type name if shadowing is detected, empty string otherwise.
|
|
2114
|
+
func (v *analysisVisitor) findTypeShadowing(varName string, rhsExpr ast.Expr) string {
|
|
2115
|
+
// Handle address-of expressions: field := &field{...}
|
|
2116
|
+
if unary, ok := rhsExpr.(*ast.UnaryExpr); ok && unary.Op == token.AND {
|
|
2117
|
+
rhsExpr = unary.X
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
// Check if RHS is a composite literal with a type name matching varName
|
|
2121
|
+
compLit, ok := rhsExpr.(*ast.CompositeLit)
|
|
2122
|
+
if !ok {
|
|
2123
|
+
return ""
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// Get the type name from the composite literal
|
|
2127
|
+
var typeName string
|
|
2128
|
+
switch t := compLit.Type.(type) {
|
|
2129
|
+
case *ast.Ident:
|
|
2130
|
+
typeName = t.Name
|
|
2131
|
+
case *ast.SelectorExpr:
|
|
2132
|
+
// pkg.Type - just use the type name part
|
|
2133
|
+
typeName = t.Sel.Name
|
|
2134
|
+
default:
|
|
2135
|
+
return ""
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// Check if variable name matches type name
|
|
2139
|
+
if typeName == varName {
|
|
2140
|
+
return typeName
|
|
2141
|
+
}
|
|
2142
|
+
return ""
|
|
2143
|
+
}
|
|
2144
|
+
|
|
1572
2145
|
// findVariableUsageInExpr recursively searches for variable usage in an expression
|
|
1573
2146
|
func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map[string]*ast.Ident, shadowingInfo *ShadowingInfo, hasShadowing *bool) {
|
|
1574
2147
|
if expr == nil {
|
|
@@ -2173,8 +2746,17 @@ func (v *analysisVisitor) analyzeAllMethodsAsync() {
|
|
|
2173
2746
|
v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
|
|
2174
2747
|
}
|
|
2175
2748
|
|
|
2749
|
+
// Track async-returning variables BEFORE analyzing function literals
|
|
2750
|
+
// This detects variables assigned from higher-order functions with async function literal args
|
|
2751
|
+
// e.g., indirect := sync.OnceValue(asyncFunc)
|
|
2752
|
+
// This must happen first so that function literals containing calls to these variables
|
|
2753
|
+
// will be correctly identified as async.
|
|
2754
|
+
v.trackAsyncReturningVarsAllFiles()
|
|
2755
|
+
|
|
2176
2756
|
// Finally, analyze function literals in the current package only
|
|
2177
2757
|
// (external packages' function literals are not accessible)
|
|
2758
|
+
// This must run AFTER trackAsyncReturningVarsAllFiles so that function literals
|
|
2759
|
+
// containing calls to async-returning variables are correctly marked as async.
|
|
2178
2760
|
v.analyzeFunctionLiteralsAsync(v.pkg)
|
|
2179
2761
|
}
|
|
2180
2762
|
|
|
@@ -2190,6 +2772,21 @@ func (v *analysisVisitor) analyzeFunctionLiteralsAsync(pkg *packages.Package) {
|
|
|
2190
2772
|
}
|
|
2191
2773
|
}
|
|
2192
2774
|
|
|
2775
|
+
// trackAsyncReturningVarsAllFiles scans all files for assignment statements
|
|
2776
|
+
// and marks variables that are assigned from higher-order functions with async function literal args
|
|
2777
|
+
func (v *analysisVisitor) trackAsyncReturningVarsAllFiles() {
|
|
2778
|
+
for _, file := range v.pkg.Syntax {
|
|
2779
|
+
ast.Inspect(file, func(n ast.Node) bool {
|
|
2780
|
+
if assignStmt, ok := n.(*ast.AssignStmt); ok {
|
|
2781
|
+
if len(assignStmt.Lhs) == 1 && len(assignStmt.Rhs) == 1 {
|
|
2782
|
+
v.trackAsyncReturningVar(assignStmt.Lhs[0], assignStmt.Rhs[0])
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
return true
|
|
2786
|
+
})
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2193
2790
|
// analyzeFunctionLiteralAsync determines if a function literal is async and stores the result
|
|
2194
2791
|
func (v *analysisVisitor) analyzeFunctionLiteralAsync(funcLit *ast.FuncLit, pkg *packages.Package) {
|
|
2195
2792
|
// Check if already analyzed
|
|
@@ -2641,6 +3238,11 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
|
|
|
2641
3238
|
result := v.isFunctionAsync(funcObj, pkg)
|
|
2642
3239
|
return result
|
|
2643
3240
|
}
|
|
3241
|
+
// Check if this is a variable that returns async values
|
|
3242
|
+
// (e.g., indirect := sync.OnceValue(asyncFunc))
|
|
3243
|
+
if v.analysis.IsAsyncReturningVar(obj) {
|
|
3244
|
+
return true
|
|
3245
|
+
}
|
|
2644
3246
|
}
|
|
2645
3247
|
|
|
2646
3248
|
case *ast.SelectorExpr:
|