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 +54 -47
- package/compiler/analysis.go +182 -101
- package/compiler/analysis_test.go +3 -3
- package/compiler/assignment.go +28 -0
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +25 -18
- package/compiler/compiler_test.go +9 -9
- package/compiler/expr-call.go +10 -3
- package/compiler/expr.go +24 -0
- package/compiler/spec.go +4 -2
- package/compiler/stmt-assign.go +53 -0
- package/compiler/stmt.go +32 -14
- package/compiler/type-info.go +3 -5
- package/compiler/type.go +2 -2
- package/dist/gs/builtin/slice.js +43 -37
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/varRef.d.ts +3 -0
- package/dist/gs/builtin/varRef.js +6 -1
- package/dist/gs/builtin/varRef.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +4 -1
- package/dist/gs/reflect/type.js +91 -9
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.js +20 -2
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +3 -3
- package/dist/gs/sync/atomic/type.gs.js +5 -9
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/go.mod +2 -2
- package/go.sum +4 -0
- package/gs/builtin/slice.ts +55 -48
- package/gs/builtin/varRef.ts +8 -2
- package/gs/reflect/type.ts +103 -9
- package/gs/strconv/atoi.gs.ts +18 -2
- package/gs/sync/atomic/type.gs.ts +8 -12
- package/package.json +12 -3
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
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
|
-
|
|
40
|
+
**🚧 In progress:**
|
|
46
41
|
|
|
47
|
-
|
|
42
|
+
- Reflection support
|
|
43
|
+
- Standard library coverage
|
|
44
|
+
- Generics
|
|
48
45
|
|
|
49
|
-
**
|
|
46
|
+
**Known limitations:**
|
|
50
47
|
|
|
51
|
-
|
|
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
|
-
|
|
52
|
+
📖 **Learn more:** [Design document](./design/DESIGN.md) | [Architecture explainer](./docs/explainer.md) | [Compliance tests](./tests/README.md)
|
|
54
53
|
|
|
55
|
-
|
|
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
|
-
|
|
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 !==
|
|
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,
|
|
270
|
-
NewUser(2,
|
|
273
|
+
NewUser(1, 'Alice', 'alice@example.com'),
|
|
274
|
+
NewUser(2, 'Bob', 'bob@example.com'),
|
|
271
275
|
]
|
|
272
276
|
|
|
273
|
-
const alice = FindUserByEmail(users,
|
|
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 =
|
|
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([
|
|
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](./
|
|
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
|
package/compiler/analysis.go
CHANGED
|
@@ -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:
|
|
209
|
-
Imports:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
-
//
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
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
|
-
//
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
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
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
-
//
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
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
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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
|
}
|