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.
- package/README.md +9 -0
- package/compiler/analysis.go +536 -15
- package/compiler/assignment.go +72 -0
- package/compiler/compiler.go +64 -11
- 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 +16 -3
- package/compiler/expr-selector.go +35 -2
- package/compiler/expr-type.go +12 -2
- package/compiler/expr.go +37 -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 +21 -4
- package/compiler/stmt-assign.go +71 -0
- package/compiler/stmt-range.go +2 -2
- package/compiler/stmt.go +128 -0
- package/compiler/type-utils.go +40 -1
- package/compiler/type.go +50 -12
- package/dist/gs/builtin/builtin.d.ts +8 -1
- package/dist/gs/builtin/builtin.js +26 -1
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/errors.d.ts +1 -0
- package/dist/gs/builtin/errors.js +8 -0
- package/dist/gs/builtin/errors.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +5 -4
- package/dist/gs/builtin/slice.js +45 -14
- 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/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 +50 -12
- package/dist/gs/reflect/type.js +820 -27
- 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/sync/atomic/type.gs.d.ts +2 -2
- package/dist/gs/sync/atomic/type.gs.js +12 -2
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
- package/dist/gs/unicode/utf8/utf8.js +10 -6
- package/dist/gs/unicode/utf8/utf8.js.map +1 -1
- package/go.mod +4 -4
- package/go.sum +8 -8
- package/gs/builtin/builtin.ts +27 -2
- package/gs/builtin/errors.ts +12 -0
- package/gs/builtin/slice.ts +71 -7
- package/gs/builtin/type.ts +159 -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 +1053 -54
- package/gs/reflect/types.ts +34 -21
- package/gs/reflect/value.ts +12 -6
- package/gs/slices/slices.ts +55 -0
- package/gs/sync/atomic/type.gs.ts +14 -5
- package/gs/unicode/utf8/utf8.ts +12 -8
- 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**
|
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
|
|
@@ -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:
|
|
188
|
-
Imports:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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(
|
|
1246
|
-
analysis.TypeDefs[baseFileName] =
|
|
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:
|