goscript 0.0.61 → 0.0.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +9 -0
  2. package/compiler/analysis.go +536 -15
  3. package/compiler/assignment.go +72 -0
  4. package/compiler/compiler.go +64 -11
  5. package/compiler/composite-lit.go +29 -8
  6. package/compiler/decl.go +20 -11
  7. package/compiler/expr-call-async.go +26 -1
  8. package/compiler/expr-call-builtins.go +60 -4
  9. package/compiler/expr-call-type-conversion.go +37 -5
  10. package/compiler/expr-call.go +16 -3
  11. package/compiler/expr-selector.go +35 -2
  12. package/compiler/expr-type.go +12 -2
  13. package/compiler/expr.go +37 -0
  14. package/compiler/index.test.ts +3 -1
  15. package/compiler/lit.go +13 -4
  16. package/compiler/spec-struct.go +30 -8
  17. package/compiler/spec-value.go +2 -2
  18. package/compiler/spec.go +21 -4
  19. package/compiler/stmt-assign.go +71 -0
  20. package/compiler/stmt-range.go +2 -2
  21. package/compiler/stmt.go +128 -0
  22. package/compiler/type-utils.go +40 -1
  23. package/compiler/type.go +50 -12
  24. package/dist/gs/builtin/builtin.d.ts +8 -1
  25. package/dist/gs/builtin/builtin.js +26 -1
  26. package/dist/gs/builtin/builtin.js.map +1 -1
  27. package/dist/gs/builtin/errors.d.ts +1 -0
  28. package/dist/gs/builtin/errors.js +8 -0
  29. package/dist/gs/builtin/errors.js.map +1 -1
  30. package/dist/gs/builtin/slice.d.ts +5 -4
  31. package/dist/gs/builtin/slice.js +45 -14
  32. package/dist/gs/builtin/slice.js.map +1 -1
  33. package/dist/gs/builtin/type.d.ts +23 -2
  34. package/dist/gs/builtin/type.js +125 -0
  35. package/dist/gs/builtin/type.js.map +1 -1
  36. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  37. package/dist/gs/bytes/reader.gs.js +1 -1
  38. package/dist/gs/bytes/reader.gs.js.map +1 -1
  39. package/dist/gs/reflect/index.d.ts +2 -2
  40. package/dist/gs/reflect/index.js +1 -1
  41. package/dist/gs/reflect/index.js.map +1 -1
  42. package/dist/gs/reflect/map.d.ts +3 -2
  43. package/dist/gs/reflect/map.js +37 -3
  44. package/dist/gs/reflect/map.js.map +1 -1
  45. package/dist/gs/reflect/type.d.ts +50 -12
  46. package/dist/gs/reflect/type.js +820 -27
  47. package/dist/gs/reflect/type.js.map +1 -1
  48. package/dist/gs/reflect/types.d.ts +11 -12
  49. package/dist/gs/reflect/types.js +26 -15
  50. package/dist/gs/reflect/types.js.map +1 -1
  51. package/dist/gs/reflect/value.d.ts +4 -4
  52. package/dist/gs/reflect/value.js +8 -2
  53. package/dist/gs/reflect/value.js.map +1 -1
  54. package/dist/gs/slices/slices.d.ts +21 -0
  55. package/dist/gs/slices/slices.js +48 -0
  56. package/dist/gs/slices/slices.js.map +1 -1
  57. package/dist/gs/sync/atomic/type.gs.d.ts +2 -2
  58. package/dist/gs/sync/atomic/type.gs.js +12 -2
  59. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  60. package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
  61. package/dist/gs/unicode/utf8/utf8.js +10 -6
  62. package/dist/gs/unicode/utf8/utf8.js.map +1 -1
  63. package/go.mod +4 -4
  64. package/go.sum +8 -8
  65. package/gs/builtin/builtin.ts +27 -2
  66. package/gs/builtin/errors.ts +12 -0
  67. package/gs/builtin/slice.ts +71 -7
  68. package/gs/builtin/type.ts +159 -2
  69. package/gs/bytes/reader.gs.ts +2 -2
  70. package/gs/math/hypot.gs.test.ts +3 -1
  71. package/gs/math/pow10.gs.test.ts +5 -4
  72. package/gs/reflect/index.ts +3 -2
  73. package/gs/reflect/map.test.ts +7 -6
  74. package/gs/reflect/map.ts +49 -7
  75. package/gs/reflect/type.ts +1053 -54
  76. package/gs/reflect/types.ts +34 -21
  77. package/gs/reflect/value.ts +12 -6
  78. package/gs/slices/slices.ts +55 -0
  79. package/gs/sync/atomic/type.gs.ts +14 -5
  80. package/gs/unicode/utf8/utf8.ts +12 -8
  81. package/package.json +13 -13
package/README.md CHANGED
@@ -58,6 +58,15 @@ GoScript is working on compiling a subset of Go:
58
58
 
59
59
  > **Warning:** This is experimental software. Features may be incomplete or broken. Please report any issues!
60
60
 
61
+ ### Prerequisites
62
+
63
+ GoScript requires [Bun](https://bun.sh) to be installed for running compliance tests:
64
+
65
+ ```bash
66
+ # Install Bun
67
+ curl -fsSL https://bun.sh/install | bash
68
+ ```
69
+
61
70
  ### Installation
62
71
 
63
72
  **Option 1: Go Install**
@@ -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
@@ -122,6 +126,10 @@ type Analysis struct {
122
126
  // Imports stores the imports for the file
123
127
  Imports map[string]*fileImport
124
128
 
129
+ // SyntheticImports stores imports that need to be written but were not in the source file.
130
+ // These are typically needed for promoted methods from embedded structs.
131
+ SyntheticImports map[string]*fileImport
132
+
125
133
  // Cmap stores the comment map for the file
126
134
  Cmap ast.CommentMap
127
135
 
@@ -141,6 +149,11 @@ type Analysis struct {
141
149
  // FunctionAssignments tracks which function literals are assigned to which variables
142
150
  FunctionAssignments map[types.Object]ast.Node
143
151
 
152
+ // AsyncReturningVars tracks variables whose function type returns async values
153
+ // This happens when a variable is assigned from a higher-order function (like sync.OnceValue)
154
+ // that receives an async function literal as an argument
155
+ AsyncReturningVars map[types.Object]bool
156
+
144
157
  // NamedBasicTypes tracks types that should be implemented as type aliases with standalone functions
145
158
  // This includes named types with basic underlying types (like uint32, string) that have methods
146
159
  NamedBasicTypes map[types.Type]bool
@@ -175,6 +188,14 @@ type PackageAnalysis struct {
175
188
  // TypeCalls maps file names to the types they reference from other files
176
189
  // Key: filename (without .go extension), Value: map[sourceFile][]typeNames
177
190
  TypeCalls map[string]map[string][]string
191
+
192
+ // VariableDefs maps file names to the package-level variables defined in that file
193
+ // Key: filename (without .go extension), Value: list of variable names
194
+ VariableDefs map[string][]string
195
+
196
+ // VariableCalls maps file names to the package-level variables they reference from other files
197
+ // Key: filename (without .go extension), Value: map[sourceFile][]variableNames
198
+ VariableCalls map[string]map[string][]string
178
199
  }
179
200
 
180
201
  // NewAnalysis creates a new Analysis instance.
@@ -184,16 +205,17 @@ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
184
205
  }
185
206
 
186
207
  return &Analysis{
187
- VariableUsage: make(map[types.Object]*VariableUsageInfo),
188
- Imports: make(map[string]*fileImport),
189
- FunctionData: make(map[types.Object]*FunctionInfo),
190
- NodeData: make(map[ast.Node]*NodeInfo),
191
- FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
192
- ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
193
- FunctionAssignments: make(map[types.Object]ast.Node),
194
- // PackageMetadata removed - using MethodAsyncStatus only
195
- NamedBasicTypes: make(map[types.Type]bool),
196
- AllPackages: allPackages,
208
+ VariableUsage: make(map[types.Object]*VariableUsageInfo),
209
+ Imports: make(map[string]*fileImport),
210
+ SyntheticImports: make(map[string]*fileImport),
211
+ FunctionData: make(map[types.Object]*FunctionInfo),
212
+ NodeData: make(map[ast.Node]*NodeInfo),
213
+ FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
214
+ ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
215
+ FunctionAssignments: make(map[types.Object]ast.Node),
216
+ AsyncReturningVars: make(map[types.Object]bool),
217
+ NamedBasicTypes: make(map[types.Type]bool),
218
+ AllPackages: allPackages,
197
219
  InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
198
220
  MethodAsyncStatus: make(map[MethodKey]bool),
199
221
  }
@@ -206,6 +228,8 @@ func NewPackageAnalysis() *PackageAnalysis {
206
228
  FunctionCalls: make(map[string]map[string][]string),
207
229
  TypeDefs: make(map[string][]string),
208
230
  TypeCalls: make(map[string]map[string][]string),
231
+ VariableDefs: make(map[string][]string),
232
+ VariableCalls: make(map[string]map[string][]string),
209
233
  }
210
234
  }
211
235
 
@@ -313,6 +337,15 @@ func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
313
337
  return false
314
338
  }
315
339
 
340
+ // IsAsyncReturningVar returns whether the given variable holds a function that returns async values.
341
+ // This is true when the variable is assigned from a higher-order function that receives an async function literal.
342
+ func (a *Analysis) IsAsyncReturningVar(obj types.Object) bool {
343
+ if obj == nil {
344
+ return false
345
+ }
346
+ return a.AsyncReturningVars[obj]
347
+ }
348
+
316
349
  func (a *Analysis) IsReceiverUsed(obj types.Object) bool {
317
350
  if obj == nil {
318
351
  return false
@@ -909,9 +942,63 @@ func (v *analysisVisitor) visitAssignStmt(n *ast.AssignStmt) ast.Visitor {
909
942
  }
910
943
  }
911
944
 
945
+ // NOTE: Async-returning variable tracking (trackAsyncReturningVar) is done in a separate pass
946
+ // after function literals are analyzed for async status. See trackAsyncReturningVarsAllFiles.
947
+
912
948
  return v
913
949
  }
914
950
 
951
+ // trackAsyncReturningVar tracks variables that are assigned from higher-order function calls
952
+ // where one of the arguments is an async function literal.
953
+ // Pattern: x := higherOrderFunc(asyncFuncLit)
954
+ // This is needed because when sync.OnceValue(asyncFunc) is called, the result is a function
955
+ // that returns a Promise, and callers of x() need to await the result.
956
+ func (v *analysisVisitor) trackAsyncReturningVar(lhs ast.Expr, rhs ast.Expr) {
957
+ // LHS must be an identifier
958
+ lhsIdent, ok := lhs.(*ast.Ident)
959
+ if !ok {
960
+ return
961
+ }
962
+
963
+ // RHS must be a call expression
964
+ callExpr, ok := rhs.(*ast.CallExpr)
965
+ if !ok {
966
+ return
967
+ }
968
+
969
+ // The result type of the call must be a function type
970
+ rhsType := v.pkg.TypesInfo.TypeOf(rhs)
971
+ if rhsType == nil {
972
+ return
973
+ }
974
+ _, isFunc := rhsType.Underlying().(*types.Signature)
975
+ if !isFunc {
976
+ return
977
+ }
978
+
979
+ // Check if any argument is an async function literal
980
+ // Use containsAsyncOperationsComplete to check the function body directly
981
+ // rather than relying on the InAsyncContext flag which may not be set yet
982
+ hasAsyncArg := false
983
+ for _, arg := range callExpr.Args {
984
+ if funcLit, ok := arg.(*ast.FuncLit); ok {
985
+ if funcLit.Body != nil && v.containsAsyncOperationsComplete(funcLit.Body, v.pkg) {
986
+ hasAsyncArg = true
987
+ break
988
+ }
989
+ }
990
+ }
991
+
992
+ if !hasAsyncArg {
993
+ return
994
+ }
995
+
996
+ // Mark the LHS variable as returning async values
997
+ if obj := v.pkg.TypesInfo.ObjectOf(lhsIdent); obj != nil {
998
+ v.analysis.AsyncReturningVars[obj] = true
999
+ }
1000
+ }
1001
+
915
1002
  // visitReturnStmt handles return statement analysis
916
1003
  func (v *analysisVisitor) visitReturnStmt(n *ast.ReturnStmt) ast.Visitor {
917
1004
  nodeInfo := v.analysis.ensureNodeData(n)
@@ -1207,9 +1294,165 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
1207
1294
  // Interface implementation async status is now updated on-demand in IsInterfaceMethodAsync
1208
1295
  visitor.analyzeAllMethodsAsync()
1209
1296
 
1297
+ // Fourth pass: collect imports needed by promoted methods from embedded structs
1298
+ analysis.addImportsForPromotedMethods(pkg)
1299
+
1210
1300
  return analysis
1211
1301
  }
1212
1302
 
1303
+ // addImportsForPromotedMethods scans all struct types in the package for embedded fields
1304
+ // and adds imports for any packages referenced by the promoted methods' parameter/return types.
1305
+ func (a *Analysis) addImportsForPromotedMethods(pkg *packages.Package) {
1306
+ // Collect all package names we need to add
1307
+ packagesToAdd := make(map[string]*types.Package)
1308
+
1309
+ // Iterate through all type definitions in the package
1310
+ scope := pkg.Types.Scope()
1311
+ for _, name := range scope.Names() {
1312
+ obj := scope.Lookup(name)
1313
+ if obj == nil {
1314
+ continue
1315
+ }
1316
+
1317
+ // Check if it's a type definition
1318
+ typeName, ok := obj.(*types.TypeName)
1319
+ if !ok {
1320
+ continue
1321
+ }
1322
+
1323
+ // Get the underlying type
1324
+ namedType, ok := typeName.Type().(*types.Named)
1325
+ if !ok {
1326
+ continue
1327
+ }
1328
+
1329
+ // Check if it's a struct
1330
+ structType, ok := namedType.Underlying().(*types.Struct)
1331
+ if !ok {
1332
+ continue
1333
+ }
1334
+
1335
+ // Look for embedded fields
1336
+ for i := 0; i < structType.NumFields(); i++ {
1337
+ field := structType.Field(i)
1338
+ if !field.Embedded() {
1339
+ continue
1340
+ }
1341
+
1342
+ // Get the type of the embedded field
1343
+ embeddedType := field.Type()
1344
+
1345
+ // Handle pointer to embedded type
1346
+ if ptr, ok := embeddedType.(*types.Pointer); ok {
1347
+ embeddedType = ptr.Elem()
1348
+ }
1349
+
1350
+ // Use method set to get all promoted methods including pointer receiver methods
1351
+ // This matches Go's behavior where embedding T promotes both T and *T methods
1352
+ methodSetType := embeddedType
1353
+ if _, isPtr := embeddedType.(*types.Pointer); !isPtr {
1354
+ if _, isInterface := embeddedType.Underlying().(*types.Interface); !isInterface {
1355
+ methodSetType = types.NewPointer(embeddedType)
1356
+ }
1357
+ }
1358
+ embeddedMethodSet := types.NewMethodSet(methodSetType)
1359
+
1360
+ // Scan all methods in the method set
1361
+ for j := 0; j < embeddedMethodSet.Len(); j++ {
1362
+ selection := embeddedMethodSet.At(j)
1363
+ method := selection.Obj()
1364
+ sig, ok := method.Type().(*types.Signature)
1365
+ if !ok {
1366
+ continue
1367
+ }
1368
+
1369
+ // Scan parameters
1370
+ if sig.Params() != nil {
1371
+ for k := 0; k < sig.Params().Len(); k++ {
1372
+ param := sig.Params().At(k)
1373
+ a.collectPackageFromType(param.Type(), pkg.Types, packagesToAdd)
1374
+ }
1375
+ }
1376
+
1377
+ // Scan results
1378
+ if sig.Results() != nil {
1379
+ for k := 0; k < sig.Results().Len(); k++ {
1380
+ result := sig.Results().At(k)
1381
+ a.collectPackageFromType(result.Type(), pkg.Types, packagesToAdd)
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ // Add collected packages to imports (both regular and synthetic)
1389
+ // Always add to SyntheticImports - the file compiler will check if
1390
+ // the import was already written from the AST to avoid duplicates
1391
+ for pkgName, pkgObj := range packagesToAdd {
1392
+ tsImportPath := "@goscript/" + pkgObj.Path()
1393
+ fileImp := &fileImport{
1394
+ importPath: tsImportPath,
1395
+ importVars: make(map[string]struct{}),
1396
+ }
1397
+ // Add to SyntheticImports unconditionally
1398
+ a.SyntheticImports[pkgName] = fileImp
1399
+ // Also add to Imports if not already present
1400
+ if _, exists := a.Imports[pkgName]; !exists {
1401
+ a.Imports[pkgName] = fileImp
1402
+ }
1403
+ }
1404
+ }
1405
+
1406
+ // collectPackageFromType recursively collects packages referenced by a type.
1407
+ func (a *Analysis) collectPackageFromType(t types.Type, currentPkg *types.Package, packagesToAdd map[string]*types.Package) {
1408
+ switch typ := t.(type) {
1409
+ case *types.Named:
1410
+ pkg := typ.Obj().Pkg()
1411
+ if pkg != nil && pkg != currentPkg {
1412
+ packagesToAdd[pkg.Name()] = pkg
1413
+ }
1414
+ // Check type arguments for generics
1415
+ if typ.TypeArgs() != nil {
1416
+ for i := 0; i < typ.TypeArgs().Len(); i++ {
1417
+ a.collectPackageFromType(typ.TypeArgs().At(i), currentPkg, packagesToAdd)
1418
+ }
1419
+ }
1420
+ case *types.Interface:
1421
+ // For interfaces, we need to check embedded interfaces and method signatures
1422
+ for i := 0; i < typ.NumEmbeddeds(); i++ {
1423
+ a.collectPackageFromType(typ.EmbeddedType(i), currentPkg, packagesToAdd)
1424
+ }
1425
+ for i := 0; i < typ.NumExplicitMethods(); i++ {
1426
+ method := typ.ExplicitMethod(i)
1427
+ a.collectPackageFromType(method.Type(), currentPkg, packagesToAdd)
1428
+ }
1429
+ case *types.Pointer:
1430
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1431
+ case *types.Slice:
1432
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1433
+ case *types.Array:
1434
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1435
+ case *types.Map:
1436
+ a.collectPackageFromType(typ.Key(), currentPkg, packagesToAdd)
1437
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1438
+ case *types.Chan:
1439
+ a.collectPackageFromType(typ.Elem(), currentPkg, packagesToAdd)
1440
+ case *types.Signature:
1441
+ // Collect from parameters
1442
+ if typ.Params() != nil {
1443
+ for i := 0; i < typ.Params().Len(); i++ {
1444
+ a.collectPackageFromType(typ.Params().At(i).Type(), currentPkg, packagesToAdd)
1445
+ }
1446
+ }
1447
+ // Collect from results
1448
+ if typ.Results() != nil {
1449
+ for i := 0; i < typ.Results().Len(); i++ {
1450
+ a.collectPackageFromType(typ.Results().At(i).Type(), currentPkg, packagesToAdd)
1451
+ }
1452
+ }
1453
+ }
1454
+ }
1455
+
1213
1456
  // AnalyzePackageImports performs package-level analysis to collect function definitions
1214
1457
  // and calls across all files in the package for auto-import generation
1215
1458
  func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
@@ -1221,19 +1464,50 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1221
1464
  baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
1222
1465
 
1223
1466
  var functions []string
1224
- var types []string
1467
+ var typeNames []string
1468
+ var variables []string
1225
1469
  for _, decl := range syntax.Decls {
1226
1470
  if funcDecl, ok := decl.(*ast.FuncDecl); ok {
1227
- // Only collect top-level functions (not methods)
1471
+ // Collect top-level functions (not methods)
1228
1472
  if funcDecl.Recv == nil {
1229
1473
  functions = append(functions, funcDecl.Name.Name)
1474
+ } else {
1475
+ // Check if this is a method on a wrapper type (named basic type)
1476
+ // If so, it will be compiled as TypeName_MethodName function
1477
+ if len(funcDecl.Recv.List) > 0 {
1478
+ recvType := funcDecl.Recv.List[0].Type
1479
+ // Handle pointer receiver (*Type)
1480
+ if starExpr, ok := recvType.(*ast.StarExpr); ok {
1481
+ recvType = starExpr.X
1482
+ }
1483
+ if recvIdent, ok := recvType.(*ast.Ident); ok {
1484
+ // Check if this receiver type is a wrapper type
1485
+ if obj := pkg.TypesInfo.Uses[recvIdent]; obj != nil {
1486
+ if typeName, ok := obj.(*types.TypeName); ok {
1487
+ if namedType, ok := typeName.Type().(*types.Named); ok {
1488
+ if _, ok := namedType.Underlying().(*types.Basic); ok {
1489
+ // This is a method on a wrapper type
1490
+ funcName := recvIdent.Name + "_" + funcDecl.Name.Name
1491
+ functions = append(functions, funcName)
1492
+ }
1493
+ }
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1230
1498
  }
1231
1499
  }
1232
1500
  if genDecl, ok := decl.(*ast.GenDecl); ok {
1233
1501
  // Collect type declarations
1234
1502
  for _, spec := range genDecl.Specs {
1235
1503
  if typeSpec, ok := spec.(*ast.TypeSpec); ok {
1236
- types = append(types, typeSpec.Name.Name)
1504
+ typeNames = append(typeNames, typeSpec.Name.Name)
1505
+ }
1506
+ // Collect variable/constant declarations
1507
+ if valueSpec, ok := spec.(*ast.ValueSpec); ok {
1508
+ for _, name := range valueSpec.Names {
1509
+ variables = append(variables, name.Name)
1510
+ }
1237
1511
  }
1238
1512
  }
1239
1513
  }
@@ -1242,8 +1516,11 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1242
1516
  if len(functions) > 0 {
1243
1517
  analysis.FunctionDefs[baseFileName] = functions
1244
1518
  }
1245
- if len(types) > 0 {
1246
- analysis.TypeDefs[baseFileName] = types
1519
+ if len(typeNames) > 0 {
1520
+ analysis.TypeDefs[baseFileName] = typeNames
1521
+ }
1522
+ if len(variables) > 0 {
1523
+ analysis.VariableDefs[baseFileName] = variables
1247
1524
  }
1248
1525
  }
1249
1526
 
@@ -1343,6 +1620,172 @@ func AnalyzePackageImports(pkg *packages.Package) *PackageAnalysis {
1343
1620
  }
1344
1621
  }
1345
1622
 
1623
+ // Fourth pass: analyze variable references and determine which need imports
1624
+ for i, syntax := range pkg.Syntax {
1625
+ fileName := pkg.CompiledGoFiles[i]
1626
+ baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
1627
+
1628
+ // Find all variable references in this file
1629
+ varRefsFromOtherFiles := make(map[string][]string)
1630
+
1631
+ ast.Inspect(syntax, func(n ast.Node) bool {
1632
+ // Look for identifier references
1633
+ if ident, ok := n.(*ast.Ident); ok {
1634
+ // Check if this identifier refers to a package-level variable
1635
+ if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
1636
+ if varObj, ok := obj.(*types.Var); ok {
1637
+ // Only track package-level variables (not function parameters or local vars)
1638
+ if varObj.Parent() == pkg.Types.Scope() {
1639
+ varName := ident.Name
1640
+
1641
+ // Check if this variable is defined in the current file
1642
+ currentFileVars := analysis.VariableDefs[baseFileName]
1643
+ isDefinedInCurrentFile := slices.Contains(currentFileVars, varName)
1644
+
1645
+ // If not defined in current file, find which file defines it
1646
+ if !isDefinedInCurrentFile {
1647
+ for sourceFile, vars := range analysis.VariableDefs {
1648
+ if sourceFile == baseFileName {
1649
+ continue // Skip current file
1650
+ }
1651
+ if slices.Contains(vars, varName) {
1652
+ // Found the variable in another file
1653
+ if varRefsFromOtherFiles[sourceFile] == nil {
1654
+ varRefsFromOtherFiles[sourceFile] = []string{}
1655
+ }
1656
+ // Check if already added to avoid duplicates
1657
+ found := slices.Contains(varRefsFromOtherFiles[sourceFile], varName)
1658
+ if !found {
1659
+ varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], varName)
1660
+ }
1661
+ }
1662
+ }
1663
+ }
1664
+ }
1665
+ }
1666
+ // Also check for constants
1667
+ if constObj, ok := obj.(*types.Const); ok {
1668
+ // Only track package-level constants
1669
+ if constObj.Parent() == pkg.Types.Scope() {
1670
+ constName := ident.Name
1671
+
1672
+ // Check if this constant is defined in the current file
1673
+ currentFileVars := analysis.VariableDefs[baseFileName]
1674
+ isDefinedInCurrentFile := slices.Contains(currentFileVars, constName)
1675
+
1676
+ // If not defined in current file, find which file defines it
1677
+ if !isDefinedInCurrentFile {
1678
+ for sourceFile, vars := range analysis.VariableDefs {
1679
+ if sourceFile == baseFileName {
1680
+ continue // Skip current file
1681
+ }
1682
+ if slices.Contains(vars, constName) {
1683
+ // Found the constant in another file
1684
+ if varRefsFromOtherFiles[sourceFile] == nil {
1685
+ varRefsFromOtherFiles[sourceFile] = []string{}
1686
+ }
1687
+ // Check if already added to avoid duplicates
1688
+ found := slices.Contains(varRefsFromOtherFiles[sourceFile], constName)
1689
+ if !found {
1690
+ varRefsFromOtherFiles[sourceFile] = append(varRefsFromOtherFiles[sourceFile], constName)
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ }
1696
+ }
1697
+ }
1698
+ }
1699
+ return true
1700
+ })
1701
+
1702
+ if len(varRefsFromOtherFiles) > 0 {
1703
+ analysis.VariableCalls[baseFileName] = varRefsFromOtherFiles
1704
+ }
1705
+ }
1706
+
1707
+ // Fifth pass: analyze method calls on wrapper types (named basic types with methods)
1708
+ // These generate TypeName_MethodName function calls that need to be imported
1709
+ for i, syntax := range pkg.Syntax {
1710
+ fileName := pkg.CompiledGoFiles[i]
1711
+ baseFileName := strings.TrimSuffix(filepath.Base(fileName), ".go")
1712
+
1713
+ // Find all method calls on wrapper types in this file
1714
+ ast.Inspect(syntax, func(n ast.Node) bool {
1715
+ callExpr, ok := n.(*ast.CallExpr)
1716
+ if !ok {
1717
+ return true
1718
+ }
1719
+
1720
+ // Check if this is a method call (selector expression)
1721
+ selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
1722
+ if !ok {
1723
+ return true
1724
+ }
1725
+
1726
+ // Get the type of the receiver
1727
+ receiverType := pkg.TypesInfo.TypeOf(selectorExpr.X)
1728
+ if receiverType == nil {
1729
+ return true
1730
+ }
1731
+
1732
+ // Check if this is a wrapper type (named type with basic underlying type and methods)
1733
+ namedType, ok := receiverType.(*types.Named)
1734
+ if !ok {
1735
+ return true
1736
+ }
1737
+
1738
+ // Check if it has a basic underlying type
1739
+ if _, ok := namedType.Underlying().(*types.Basic); !ok {
1740
+ return true
1741
+ }
1742
+
1743
+ // Check if this type is defined in the same package
1744
+ obj := namedType.Obj()
1745
+ if obj == nil || obj.Pkg() == nil || obj.Pkg() != pkg.Types {
1746
+ return true // Not from this package
1747
+ }
1748
+
1749
+ // Check if this type has the method being called
1750
+ methodName := selectorExpr.Sel.Name
1751
+ found := false
1752
+ for j := 0; j < namedType.NumMethods(); j++ {
1753
+ if namedType.Method(j).Name() == methodName {
1754
+ found = true
1755
+ break
1756
+ }
1757
+ }
1758
+ if !found {
1759
+ return true
1760
+ }
1761
+
1762
+ // Generate the function name: TypeName_MethodName
1763
+ funcName := obj.Name() + "_" + methodName
1764
+
1765
+ // Find which file defines this function
1766
+ for sourceFile, funcs := range analysis.FunctionDefs {
1767
+ if sourceFile == baseFileName {
1768
+ continue // Skip current file
1769
+ }
1770
+ if slices.Contains(funcs, funcName) {
1771
+ // Found the function in another file
1772
+ if analysis.FunctionCalls[baseFileName] == nil {
1773
+ analysis.FunctionCalls[baseFileName] = make(map[string][]string)
1774
+ }
1775
+ if analysis.FunctionCalls[baseFileName][sourceFile] == nil {
1776
+ analysis.FunctionCalls[baseFileName][sourceFile] = []string{}
1777
+ }
1778
+ // Check if already added to avoid duplicates
1779
+ if !slices.Contains(analysis.FunctionCalls[baseFileName][sourceFile], funcName) {
1780
+ analysis.FunctionCalls[baseFileName][sourceFile] = append(analysis.FunctionCalls[baseFileName][sourceFile], funcName)
1781
+ }
1782
+ }
1783
+ }
1784
+
1785
+ return true
1786
+ })
1787
+ }
1788
+
1346
1789
  return analysis
1347
1790
  }
1348
1791
 
@@ -1546,6 +1989,7 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
1546
1989
  shadowingInfo := &ShadowingInfo{
1547
1990
  ShadowedVariables: make(map[string]types.Object),
1548
1991
  TempVariables: make(map[string]string),
1992
+ TypeShadowedVars: make(map[string]string),
1549
1993
  }
1550
1994
 
1551
1995
  hasShadowing := false
@@ -1563,12 +2007,60 @@ func (v *analysisVisitor) detectVariableShadowing(assignStmt *ast.AssignStmt) *S
1563
2007
  v.findVariableUsageInExpr(rhsExpr, lhsVarNames, shadowingInfo, &hasShadowing)
1564
2008
  }
1565
2009
 
2010
+ // Check for type shadowing: variable name matches a type name used in its initialization
2011
+ // e.g., field := field{...} where the variable 'field' shadows the type 'field'
2012
+ if assignStmt.Tok == token.DEFINE {
2013
+ for i, lhsExpr := range assignStmt.Lhs {
2014
+ if i < len(assignStmt.Rhs) {
2015
+ if lhsIdent, ok := lhsExpr.(*ast.Ident); ok && lhsIdent.Name != "_" {
2016
+ if typeName := v.findTypeShadowing(lhsIdent.Name, assignStmt.Rhs[i]); typeName != "" {
2017
+ shadowingInfo.TypeShadowedVars[lhsIdent.Name] = lhsIdent.Name + "_"
2018
+ hasShadowing = true
2019
+ }
2020
+ }
2021
+ }
2022
+ }
2023
+ }
2024
+
1566
2025
  if hasShadowing {
1567
2026
  return shadowingInfo
1568
2027
  }
1569
2028
  return nil
1570
2029
  }
1571
2030
 
2031
+ // findTypeShadowing checks if the given variable name matches a type name used in the RHS expression.
2032
+ // Returns the type name if shadowing is detected, empty string otherwise.
2033
+ func (v *analysisVisitor) findTypeShadowing(varName string, rhsExpr ast.Expr) string {
2034
+ // Handle address-of expressions: field := &field{...}
2035
+ if unary, ok := rhsExpr.(*ast.UnaryExpr); ok && unary.Op == token.AND {
2036
+ rhsExpr = unary.X
2037
+ }
2038
+
2039
+ // Check if RHS is a composite literal with a type name matching varName
2040
+ compLit, ok := rhsExpr.(*ast.CompositeLit)
2041
+ if !ok {
2042
+ return ""
2043
+ }
2044
+
2045
+ // Get the type name from the composite literal
2046
+ var typeName string
2047
+ switch t := compLit.Type.(type) {
2048
+ case *ast.Ident:
2049
+ typeName = t.Name
2050
+ case *ast.SelectorExpr:
2051
+ // pkg.Type - just use the type name part
2052
+ typeName = t.Sel.Name
2053
+ default:
2054
+ return ""
2055
+ }
2056
+
2057
+ // Check if variable name matches type name
2058
+ if typeName == varName {
2059
+ return typeName
2060
+ }
2061
+ return ""
2062
+ }
2063
+
1572
2064
  // findVariableUsageInExpr recursively searches for variable usage in an expression
1573
2065
  func (v *analysisVisitor) findVariableUsageInExpr(expr ast.Expr, lhsVarNames map[string]*ast.Ident, shadowingInfo *ShadowingInfo, hasShadowing *bool) {
1574
2066
  if expr == nil {
@@ -2173,8 +2665,17 @@ func (v *analysisVisitor) analyzeAllMethodsAsync() {
2173
2665
  v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
2174
2666
  }
2175
2667
 
2668
+ // Track async-returning variables BEFORE analyzing function literals
2669
+ // This detects variables assigned from higher-order functions with async function literal args
2670
+ // e.g., indirect := sync.OnceValue(asyncFunc)
2671
+ // This must happen first so that function literals containing calls to these variables
2672
+ // will be correctly identified as async.
2673
+ v.trackAsyncReturningVarsAllFiles()
2674
+
2176
2675
  // Finally, analyze function literals in the current package only
2177
2676
  // (external packages' function literals are not accessible)
2677
+ // This must run AFTER trackAsyncReturningVarsAllFiles so that function literals
2678
+ // containing calls to async-returning variables are correctly marked as async.
2178
2679
  v.analyzeFunctionLiteralsAsync(v.pkg)
2179
2680
  }
2180
2681
 
@@ -2190,6 +2691,21 @@ func (v *analysisVisitor) analyzeFunctionLiteralsAsync(pkg *packages.Package) {
2190
2691
  }
2191
2692
  }
2192
2693
 
2694
+ // trackAsyncReturningVarsAllFiles scans all files for assignment statements
2695
+ // and marks variables that are assigned from higher-order functions with async function literal args
2696
+ func (v *analysisVisitor) trackAsyncReturningVarsAllFiles() {
2697
+ for _, file := range v.pkg.Syntax {
2698
+ ast.Inspect(file, func(n ast.Node) bool {
2699
+ if assignStmt, ok := n.(*ast.AssignStmt); ok {
2700
+ if len(assignStmt.Lhs) == 1 && len(assignStmt.Rhs) == 1 {
2701
+ v.trackAsyncReturningVar(assignStmt.Lhs[0], assignStmt.Rhs[0])
2702
+ }
2703
+ }
2704
+ return true
2705
+ })
2706
+ }
2707
+ }
2708
+
2193
2709
  // analyzeFunctionLiteralAsync determines if a function literal is async and stores the result
2194
2710
  func (v *analysisVisitor) analyzeFunctionLiteralAsync(funcLit *ast.FuncLit, pkg *packages.Package) {
2195
2711
  // Check if already analyzed
@@ -2641,6 +3157,11 @@ func (v *analysisVisitor) isCallAsync(callExpr *ast.CallExpr, pkg *packages.Pack
2641
3157
  result := v.isFunctionAsync(funcObj, pkg)
2642
3158
  return result
2643
3159
  }
3160
+ // Check if this is a variable that returns async values
3161
+ // (e.g., indirect := sync.OnceValue(asyncFunc))
3162
+ if v.analysis.IsAsyncReturningVar(obj) {
3163
+ return true
3164
+ }
2644
3165
  }
2645
3166
 
2646
3167
  case *ast.SelectorExpr: