goscript 0.0.62 → 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 CHANGED
@@ -19,45 +19,42 @@ GoScript is an experimental **Go to TypeScript compiler** that translates Go cod
19
19
 
20
20
  Write once, run everywhere. Share your Go algorithms, business logic, and data structures seamlessly between your backend and frontend without maintaining two codebases.
21
21
 
22
- 🔬 **Experimental features being developed:**
22
+ **Use cases:**
23
+
23
24
  - Sharing business logic between Go services and web apps
24
25
  - Porting Go algorithms to run in browsers
25
26
  - Building TypeScript libraries from existing Go code
26
27
 
27
28
  Go has powerful concurrency support and an excellent standard library. GoScript brings these capabilities to TypeScript with as simple and readable of a translation as possible.
28
29
 
29
- ⚠️ **Current development status:**
30
- GoScript is working on compiling a subset of Go:
31
- - ✅ Basic structs, interfaces, methods, and functions
32
- - ✅ Channels and goroutines (translating to async/await)
33
- - ✅ Slice semantics, maps, and built-in types
34
- - ✅ Standard control flow (if, for, switch, select, range, etc.)
35
- - 🚧 Basic reflection support
36
- - 🚧 Standard library support
30
+ **✅ What works:**
37
31
 
38
- **Known limitations in this preview:**
39
- - Uses JavaScript `number` type (64-bit float, not Go's int types)
40
- - No pointer arithmetic (`uintptr`) or `unsafe` package
41
- - No complex numbers
42
- - Limited standard library (working on it)
43
- - Performance not yet optimized
32
+ - Structs, interfaces, methods, and functions with full value semantics
33
+ - Channels and goroutines (translated to async/await with function coloring)
34
+ - Pointers and addressability (via VarRef system)
35
+ - Slices, maps, and built-in types
36
+ - Control flow (if, for, switch, select, range, defer, etc.)
37
+ - Type assertions and interface implementations
38
+ - Closures and anonymous functions
44
39
 
45
- **This is a prototype!** Expect bugs and missing features. Please contribute!
40
+ **🚧 In progress:**
46
41
 
47
- ### ⚠️ Development Preview
42
+ - Reflection support
43
+ - Standard library coverage
44
+ - Generics
48
45
 
49
- **This project is currently in active development and should be considered experimental.** GoScript is a work-in-progress prototype that may have bugs, incomplete features, and breaking changes. We're actively iterating on the compiler and welcome your feedback!
46
+ **Known limitations:**
50
47
 
51
- 🐛 **Found an issue?** Please [open an issue](https://github.com/aperturerobotics/goscript/issues) and we'll fix it.
48
+ - Uses JavaScript `number` type (64-bit float, not Go's int types)
49
+ - No pointer arithmetic (`uintptr`) or `unsafe` package
50
+ - No complex numbers
52
51
 
53
- 🤖 **AI** This prototype is heavily AI-written, but I plan to manually rewrite the codebase by hand once it reaches a working state for advanced use cases. AI Producer, human Reducer.
52
+ 📖 **Learn more:** [Design document](./design/DESIGN.md) | [Architecture explainer](./docs/explainer.md) | [Compliance tests](./tests/README.md)
54
53
 
55
- 📖 **Learn more:** [Design document](./design/DESIGN.md) | [Compliance tests](./compliance/COMPLIANCE.md)
54
+ 🐛 **Found an issue?** Please [open an issue](https://github.com/aperturerobotics/goscript/issues).
56
55
 
57
56
  ## 🚀 Try It
58
57
 
59
- > **Warning:** This is experimental software. Features may be incomplete or broken. Please report any issues!
60
-
61
58
  ### Prerequisites
62
59
 
63
60
  GoScript requires [Bun](https://bun.sh) to be installed for running compliance tests:
@@ -70,11 +67,13 @@ curl -fsSL https://bun.sh/install | bash
70
67
  ### Installation
71
68
 
72
69
  **Option 1: Go Install**
70
+
73
71
  ```bash
74
72
  go install github.com/aperturerobotics/goscript/cmd/goscript@latest
75
73
  ```
76
74
 
77
75
  **Option 2: NPM** (if available)
76
+
78
77
  ```bash
79
78
  npm install -g goscript
80
79
  ```
@@ -86,8 +85,6 @@ npm install -g goscript
86
85
  goscript compile --package . --output ./dist
87
86
  ```
88
87
 
89
- **Note:** Many Go packages may not compile successfully yet. Start with simple code and gradually test more complex features.
90
-
91
88
  ## 📦 Using Generated Code in Your Project
92
89
 
93
90
  After compiling your Go code to TypeScript, you'll need to set up your project appropriately.
@@ -116,6 +113,7 @@ Create or update your `tsconfig.json` with these settings:
116
113
  ```
117
114
 
118
115
  **Important requirements:**
116
+
119
117
  - **`target: "ES2022"` or newer** - Required for `Disposable` and other features
120
118
  - **`lib: ["esnext.disposable"]`** - Enables TypeScript's disposable types for resource management
121
119
  - **`baseUrl` and `paths`** - Allows TypeScript to resolve `@goscript/*` imports
@@ -132,12 +130,14 @@ goscript compile --package ./my-go-code --output ./dist
132
130
  ```
133
131
 
134
132
  **Options:**
133
+
135
134
  - `--package <path>` - Go package to compile (default: ".")
136
135
  - `--output <dir>` - Output directory for TypeScript files
137
136
 
138
137
  ### Programmatic API
139
138
 
140
139
  **Go:**
140
+
141
141
  ```go
142
142
  import "github.com/aperturerobotics/goscript/compiler"
143
143
 
@@ -147,24 +147,26 @@ _, err = comp.CompilePackages(ctx, "your/package/path")
147
147
  ```
148
148
 
149
149
  **Node.js:**
150
+
150
151
  ```typescript
151
152
  import { compile } from 'goscript'
152
153
 
153
154
  await compile({
154
155
  pkg: './my-go-package',
155
- output: './dist'
156
+ output: './dist',
156
157
  })
157
158
  ```
158
159
 
159
160
  ### Frontend Frameworks
160
161
 
161
162
  **React + GoScript:**
163
+
162
164
  ```typescript
163
165
  import { NewCalculator } from '@goscript/myapp/calculator'
164
166
 
165
167
  function CalculatorApp() {
166
168
  const [calc] = useState(() => NewCalculator())
167
-
169
+
168
170
  const handleAdd = () => {
169
171
  const result = calc.Add(5, 3)
170
172
  setResult(result)
@@ -175,13 +177,12 @@ function CalculatorApp() {
175
177
  ```
176
178
 
177
179
  **Vue + GoScript:**
180
+
178
181
  ```vue
179
182
  <script setup lang="ts">
180
183
  import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
181
184
 
182
- const users = ref([
183
- NewUser(1, "Alice", "alice@example.com")
184
- ])
185
+ const users = ref([NewUser(1, 'Alice', 'alice@example.com')])
185
186
 
186
187
  const searchUser = (email: string) => {
187
188
  return FindUserByEmail(users.value, email)
@@ -189,14 +190,14 @@ const searchUser = (email: string) => {
189
190
  </script>
190
191
  ```
191
192
 
192
-
193
193
  ## 💡 See It In Action
194
194
 
195
- > **Disclaimer:** These examples represent the target functionality. Your mileage may vary with the current development preview.
195
+ See the [example/app](./example/app) for a full todo list application using GoScript with tRPC, Drizzle ORM, and React, or [example/simple](./example/simple) for a comprehensive demo of language features.
196
196
 
197
197
  ### Example: User Management
198
198
 
199
199
  **Go Code** (`user.go`):
200
+
200
201
  ```go
201
202
  package main
202
203
 
@@ -225,20 +226,22 @@ func FindUserByEmail(users []*User, email string) *User {
225
226
  ```
226
227
 
227
228
  **Compile it:**
229
+
228
230
  ```bash
229
231
  goscript compile --package . --output ./dist
230
232
  ```
231
233
 
232
234
  **Generated TypeScript** (`user.gs.ts`):
235
+
233
236
  ```typescript
234
237
  export class User {
235
238
  public ID: number = 0
236
- public Name: string = ""
237
- public Email: string = ""
239
+ public Name: string = ''
240
+ public Email: string = ''
238
241
 
239
242
  public IsValid(): boolean {
240
243
  const u = this
241
- return u.Name !== "" && u.Email !== ""
244
+ return u.Name !== '' && u.Email !== ''
242
245
  }
243
246
 
244
247
  constructor(init?: Partial<User>) {
@@ -261,26 +264,28 @@ export function FindUserByEmail(users: User[], email: string): User | null {
261
264
  ```
262
265
 
263
266
  **Use in your frontend:**
267
+
264
268
  ```typescript
265
269
  import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
266
270
 
267
271
  // Same logic, now in TypeScript!
268
272
  const users = [
269
- NewUser(1, "Alice", "alice@example.com"),
270
- NewUser(2, "Bob", "bob@example.com")
273
+ NewUser(1, 'Alice', 'alice@example.com'),
274
+ NewUser(2, 'Bob', 'bob@example.com'),
271
275
  ]
272
276
 
273
- const alice = FindUserByEmail(users, "alice@example.com")
277
+ const alice = FindUserByEmail(users, 'alice@example.com')
274
278
  console.log(alice?.IsValid()) // true
275
279
  ```
276
280
 
277
281
  ### Example: Async Processing with Channels
278
282
 
279
283
  **Go Code:**
284
+
280
285
  ```go
281
286
  func ProcessMessages(messages []string) chan string {
282
287
  results := make(chan string, len(messages))
283
-
288
+
284
289
  for _, msg := range messages {
285
290
  go func(m string) {
286
291
  // Simulate processing
@@ -288,34 +293,36 @@ func ProcessMessages(messages []string) chan string {
288
293
  results <- processed
289
294
  }(msg)
290
295
  }
291
-
296
+
292
297
  return results
293
298
  }
294
299
  ```
295
300
 
296
301
  **Generated TypeScript:**
302
+
297
303
  ```typescript
298
304
  export function ProcessMessages(messages: string[]): $.Channel<string> {
299
- let results = $.makeChannel<string>(messages.length, "")
300
-
305
+ let results = $.makeChannel<string>(messages.length, '')
306
+
301
307
  for (let msg of messages) {
302
308
  queueMicrotask(async (m: string) => {
303
- let processed = "" + m
309
+ let processed = '' + m
304
310
  await results.send(processed)
305
311
  })(msg)
306
312
  }
307
-
313
+
308
314
  return results
309
315
  }
310
316
  ```
311
317
 
312
318
  **Use with async/await:**
319
+
313
320
  ```typescript
314
321
  import { ProcessMessages } from '@goscript/myapp/processor'
315
322
 
316
323
  async function handleMessages() {
317
- const channel = ProcessMessages(["hello", "world", "goscript"])
318
-
324
+ const channel = ProcessMessages(['hello', 'world', 'goscript'])
325
+
319
326
  // Receive processed messages
320
327
  for (let i = 0; i < 3; i++) {
321
328
  const result = await channel.receive()
@@ -327,7 +334,7 @@ async function handleMessages() {
327
334
  ## 🤝 How You Can Help
328
335
 
329
336
  - Try GoScript on your code and [report issues](https://github.com/aperturerobotics/goscript/issues)
330
- - Check the [compliance tests](./compliance/COMPLIANCE.md) for current progress
337
+ - Check the [compliance tests](./tests/README.md) for current progress
331
338
  - Contribute test cases for edge cases you discover
332
339
 
333
340
  ## License
@@ -126,10 +126,6 @@ type Analysis struct {
126
126
  // Imports stores the imports for the file
127
127
  Imports map[string]*fileImport
128
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
-
133
129
  // Cmap stores the comment map for the file
134
130
  Cmap ast.CommentMap
135
131
 
@@ -169,6 +165,16 @@ type Analysis struct {
169
165
  // MethodAsyncStatus stores the async status of all methods analyzed
170
166
  // This is computed once during analysis and reused during code generation
171
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
172
178
  }
173
179
 
174
180
  // PackageAnalysis holds cross-file analysis data for a package
@@ -205,19 +211,20 @@ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
205
211
  }
206
212
 
207
213
  return &Analysis{
208
- VariableUsage: make(map[types.Object]*VariableUsageInfo),
209
- Imports: make(map[string]*fileImport),
210
- SyntheticImports: make(map[string]*fileImport),
211
- FunctionData: make(map[types.Object]*FunctionInfo),
212
- NodeData: make(map[ast.Node]*NodeInfo),
213
- FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
214
- ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
215
- FunctionAssignments: make(map[types.Object]ast.Node),
216
- AsyncReturningVars: make(map[types.Object]bool),
217
- NamedBasicTypes: make(map[types.Type]bool),
218
- AllPackages: allPackages,
219
- InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
220
- MethodAsyncStatus: make(map[MethodKey]bool),
214
+ VariableUsage: make(map[types.Object]*VariableUsageInfo),
215
+ Imports: make(map[string]*fileImport),
216
+ FunctionData: make(map[types.Object]*FunctionInfo),
217
+ NodeData: make(map[ast.Node]*NodeInfo),
218
+ FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
219
+ ReflectedFunctions: make(map[ast.Node]*ReflectedFunctionInfo),
220
+ FunctionAssignments: make(map[types.Object]ast.Node),
221
+ AsyncReturningVars: make(map[types.Object]bool),
222
+ NamedBasicTypes: make(map[types.Type]bool),
223
+ AllPackages: allPackages,
224
+ InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
225
+ MethodAsyncStatus: make(map[MethodKey]bool),
226
+ ReferencedTypesPerFile: make(map[string]map[*types.Named]bool),
227
+ SyntheticImportsPerFile: make(map[string]map[string]*fileImport),
221
228
  }
222
229
  }
223
230
 
@@ -492,6 +499,10 @@ type analysisVisitor struct {
492
499
 
493
500
  // currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
494
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
495
506
  }
496
507
 
497
508
  // getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
@@ -524,6 +535,18 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
524
535
  if n.Tok == token.VAR {
525
536
  for _, spec := range n.Specs {
526
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
+
527
550
  // Process each declared variable (LHS)
528
551
  for i, lhsIdent := range valueSpec.Names {
529
552
  if lhsIdent.Name == "_" {
@@ -813,9 +836,30 @@ func (v *analysisVisitor) visitCallExpr(n *ast.CallExpr) ast.Visitor {
813
836
  // Check for implicit address-taking in method calls with pointer receivers
814
837
  v.checkImplicitAddressTaking(n)
815
838
 
839
+ // Check for address-of expressions in function arguments
840
+ v.checkAddressOfInArguments(n)
841
+
816
842
  return v
817
843
  }
818
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
+
819
863
  // checkImplicitAddressTaking detects when a method call with a pointer receiver
820
864
  // is called on a non-pointer variable, which requires implicit address-taking.
821
865
  // Example: var s MySlice; s.Add(10) where Add has receiver *MySlice
@@ -1027,12 +1071,28 @@ func (v *analysisVisitor) visitDeclStmt(n *ast.DeclStmt) ast.Visitor {
1027
1071
  // Check if we're inside a function (either FuncDecl or FuncLit)
1028
1072
  isInsideFunction := v.currentFuncDecl != nil || v.currentFuncLit != nil
1029
1073
 
1030
- if isInsideFunction {
1031
- // Mark all specs in this declaration as being inside a function
1032
- for _, spec := range genDecl.Specs {
1074
+ for _, spec := range genDecl.Specs {
1075
+ if isInsideFunction {
1076
+ // Mark all specs in this declaration as being inside a function
1033
1077
  nodeInfo := v.analysis.ensureNodeData(spec)
1034
1078
  nodeInfo.IsInsideFunction = true
1035
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
+ }
1036
1096
  }
1037
1097
  }
1038
1098
  return v
@@ -1061,6 +1121,9 @@ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) as
1061
1121
  return v
1062
1122
  }
1063
1123
 
1124
+ // Track the asserted type for synthetic import filtering
1125
+ v.trackTypeReference(assertedType)
1126
+
1064
1127
  // Check if the asserted type is an interface
1065
1128
  interfaceType, isInterface := assertedType.Underlying().(*types.Interface)
1066
1129
  if !isInterface {
@@ -1098,10 +1161,36 @@ func (v *analysisVisitor) visitTypeAssertExpr(typeAssert *ast.TypeAssertExpr) as
1098
1161
  return v
1099
1162
  }
1100
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
+
1101
1183
  // visitCompositeLit analyzes composite literals for address-of expressions
1102
1184
  // This is important for detecting cases like: arr := []interface{}{value1, &value2}
1103
1185
  // where value2 needs to be marked as NeedsVarRef due to the &value2 usage
1104
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
+
1105
1194
  // Analyze each element of the composite literal
1106
1195
  for _, elt := range compLit.Elts {
1107
1196
  // Handle both direct elements and key-value pairs
@@ -1261,7 +1350,11 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
1261
1350
  }
1262
1351
 
1263
1352
  // First pass: analyze all declarations and statements across all files
1264
- for _, file := range pkg.Syntax {
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
+ }
1265
1358
  ast.Walk(visitor, file)
1266
1359
  }
1267
1360
 
@@ -1300,105 +1393,93 @@ func AnalyzePackageFiles(pkg *packages.Package, allPackages map[string]*packages
1300
1393
  return analysis
1301
1394
  }
1302
1395
 
1303
- // addImportsForPromotedMethods scans all struct types in the package for embedded fields
1396
+ // addImportsForPromotedMethods scans struct types that are actually referenced in each file
1304
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.
1305
1399
  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() {
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 {
1339
1410
  continue
1340
1411
  }
1341
1412
 
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()
1413
+ // Check if it's a struct
1414
+ structType, ok := namedType.Underlying().(*types.Struct)
1415
+ if !ok {
1416
+ continue
1348
1417
  }
1349
1418
 
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)
1419
+ // Look for embedded fields
1420
+ for i := 0; i < structType.NumFields(); i++ {
1421
+ field := structType.Field(i)
1422
+ if !field.Embedded() {
1423
+ continue
1356
1424
  }
1357
- }
1358
- embeddedMethodSet := types.NewMethodSet(methodSetType)
1359
1425
 
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
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()
1367
1432
  }
1368
1433
 
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)
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)
1374
1440
  }
1375
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
+ }
1376
1452
 
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)
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
+ }
1382
1467
  }
1383
1468
  }
1384
1469
  }
1385
1470
  }
1386
- }
1387
1471
 
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
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
1402
1483
  }
1403
1484
  }
1404
1485
  }