goscript 0.0.49 → 0.0.51
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/compiler/analysis.go +1021 -561
- package/compiler/analysis_test.go +12 -15
- package/compiler/compiler.go +75 -39
- package/compiler/decl.go +22 -0
- package/compiler/expr-call-async.go +239 -40
- package/compiler/expr-call-type-conversion.go +6 -10
- package/compiler/expr-call.go +58 -27
- package/compiler/sanitize.go +1 -2
- package/compiler/spec-struct.go +3 -3
- package/compiler/spec-value.go +2 -2
- package/compiler/spec.go +66 -43
- package/compiler/stmt-assign.go +7 -4
- package/compiler/stmt-select.go +52 -1
- package/compiler/stmt.go +63 -5
- package/compiler/type.go +16 -3
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/channel.d.ts +2 -2
- package/dist/gs/builtin/channel.js +12 -7
- package/dist/gs/builtin/channel.js.map +1 -1
- package/dist/gs/context/context.d.ts +16 -18
- package/dist/gs/context/context.js +23 -13
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/fmt/fmt.js +3 -1
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/reflect/type.js +5 -8
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/syscall/constants.d.ts +24 -0
- package/dist/gs/syscall/constants.js +27 -0
- package/dist/gs/syscall/constants.js.map +1 -0
- package/dist/gs/syscall/env.d.ts +6 -0
- package/dist/gs/syscall/env.js +43 -0
- package/dist/gs/syscall/env.js.map +1 -0
- package/dist/gs/syscall/errors.d.ts +111 -0
- package/dist/gs/syscall/errors.js +547 -0
- package/dist/gs/syscall/errors.js.map +1 -0
- package/dist/gs/syscall/fs.d.ts +29 -0
- package/dist/gs/syscall/fs.js +53 -0
- package/dist/gs/syscall/fs.js.map +1 -0
- package/dist/gs/syscall/index.d.ts +6 -80
- package/dist/gs/syscall/index.js +12 -168
- package/dist/gs/syscall/index.js.map +1 -1
- package/dist/gs/syscall/rawconn.d.ts +7 -0
- package/dist/gs/syscall/rawconn.js +19 -0
- package/dist/gs/syscall/rawconn.js.map +1 -0
- package/dist/gs/syscall/types.d.ts +12 -0
- package/dist/gs/syscall/types.js +2 -0
- package/dist/gs/syscall/types.js.map +1 -0
- package/dist/gs/time/time.d.ts +2 -1
- package/dist/gs/time/time.js +29 -19
- package/dist/gs/time/time.js.map +1 -1
- package/gs/builtin/builtin.ts +1 -7
- package/gs/builtin/channel.ts +18 -12
- package/gs/context/context.ts +63 -45
- package/gs/fmt/fmt.ts +5 -1
- package/gs/reflect/type.ts +5 -10
- package/gs/syscall/constants.ts +29 -0
- package/gs/syscall/env.ts +47 -0
- package/gs/syscall/errors.ts +658 -0
- package/gs/syscall/fs.ts +62 -0
- package/gs/syscall/index.ts +12 -207
- package/gs/syscall/rawconn.ts +23 -0
- package/gs/syscall/types.ts +18 -0
- package/gs/time/time.ts +35 -21
- package/package.json +2 -2
- package/gs/TODO.md +0 -129
|
@@ -160,6 +160,7 @@ func parseAndAnalyze(t *testing.T, code string) (*Analysis, map[string]types.Obj
|
|
|
160
160
|
Defs: make(map[*ast.Ident]types.Object),
|
|
161
161
|
Uses: make(map[*ast.Ident]types.Object),
|
|
162
162
|
},
|
|
163
|
+
Fset: fset,
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
// Type check the package
|
|
@@ -170,10 +171,8 @@ func parseAndAnalyze(t *testing.T, code string) (*Analysis, map[string]types.Obj
|
|
|
170
171
|
}
|
|
171
172
|
pkg.Types = typePkg
|
|
172
173
|
|
|
173
|
-
// Run analysis
|
|
174
|
-
analysis :=
|
|
175
|
-
cmap := ast.NewCommentMap(fset, file, file.Comments)
|
|
176
|
-
AnalyzeFile(file, pkg, analysis, cmap)
|
|
174
|
+
// Run package-level analysis
|
|
175
|
+
analysis := AnalyzePackageFiles(pkg, nil)
|
|
177
176
|
|
|
178
177
|
// Collect variable objects
|
|
179
178
|
objects := make(map[string]types.Object)
|
|
@@ -269,18 +268,16 @@ func TestWrapperTypeDetection(t *testing.T) {
|
|
|
269
268
|
t.Fatalf("Package has errors: %v", pkg.Errors[0])
|
|
270
269
|
}
|
|
271
270
|
|
|
272
|
-
// Run analysis
|
|
271
|
+
// Run package-level analysis
|
|
273
272
|
if len(pkg.Syntax) == 0 {
|
|
274
273
|
t.Fatal("No syntax files found")
|
|
275
274
|
}
|
|
276
275
|
|
|
277
|
-
analysis :=
|
|
278
|
-
cmap := ast.NewCommentMap(pkg.Fset, pkg.Syntax[0], pkg.Syntax[0].Comments)
|
|
279
|
-
AnalyzeFile(pkg.Syntax[0], pkg, analysis, cmap)
|
|
276
|
+
analysis := AnalyzePackageFiles(pkg, nil)
|
|
280
277
|
|
|
281
|
-
// Verify the
|
|
282
|
-
if analysis.
|
|
283
|
-
t.Error("
|
|
278
|
+
// Verify the NamedBasicTypes map was initialized
|
|
279
|
+
if analysis.NamedBasicTypes == nil {
|
|
280
|
+
t.Error("NamedBasicTypes map was not initialized")
|
|
284
281
|
}
|
|
285
282
|
|
|
286
283
|
// Test some type lookups to verify wrapper type detection works
|
|
@@ -290,7 +287,7 @@ func TestWrapperTypeDetection(t *testing.T) {
|
|
|
290
287
|
// Check if MyMode is detected as a wrapper type
|
|
291
288
|
if obj := scope.Lookup("MyMode"); obj != nil {
|
|
292
289
|
if typeName, ok := obj.(*types.TypeName); ok {
|
|
293
|
-
isWrapper := analysis.
|
|
290
|
+
isWrapper := analysis.IsNamedBasicType(typeName.Type())
|
|
294
291
|
if !isWrapper {
|
|
295
292
|
t.Errorf("MyMode should be detected as wrapper type, got %v", isWrapper)
|
|
296
293
|
}
|
|
@@ -301,7 +298,7 @@ func TestWrapperTypeDetection(t *testing.T) {
|
|
|
301
298
|
// Test that regular struct types are not detected as wrapper types
|
|
302
299
|
if obj := scope.Lookup("MyDir"); obj != nil {
|
|
303
300
|
if typeName, ok := obj.(*types.TypeName); ok {
|
|
304
|
-
isWrapper := analysis.
|
|
301
|
+
isWrapper := analysis.IsNamedBasicType(typeName.Type())
|
|
305
302
|
if isWrapper {
|
|
306
303
|
t.Errorf("MyDir should not be detected as wrapper type, got %v", isWrapper)
|
|
307
304
|
}
|
|
@@ -309,13 +306,13 @@ func TestWrapperTypeDetection(t *testing.T) {
|
|
|
309
306
|
}
|
|
310
307
|
}
|
|
311
308
|
|
|
312
|
-
t.Logf("Analysis completed successfully with %d
|
|
309
|
+
t.Logf("Analysis completed successfully with %d named basic types tracked", len(analysis.NamedBasicTypes))
|
|
313
310
|
}
|
|
314
311
|
|
|
315
312
|
// TestDiscoverGsPackages verifies that the discoverEmbeddedGsPackages function
|
|
316
313
|
// can find packages in the embedded gs/ directory
|
|
317
314
|
func TestDiscoverGsPackages(t *testing.T) {
|
|
318
|
-
analysis := NewAnalysis()
|
|
315
|
+
analysis := NewAnalysis(nil)
|
|
319
316
|
|
|
320
317
|
// Test package discovery using the embedded filesystem
|
|
321
318
|
packages := analysis.discoverEmbeddedGsPackages()
|
package/compiler/compiler.go
CHANGED
|
@@ -72,7 +72,11 @@ func NewCompiler(conf *Config, le *logrus.Entry, opts *packages.Config) (*Compil
|
|
|
72
72
|
packages.NeedTypesInfo |
|
|
73
73
|
packages.NeedTypesSizes
|
|
74
74
|
|
|
75
|
-
return &Compiler{
|
|
75
|
+
return &Compiler{
|
|
76
|
+
config: *conf,
|
|
77
|
+
le: le,
|
|
78
|
+
opts: *opts,
|
|
79
|
+
}, nil
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
// CompilationResult contains information about what was compiled
|
|
@@ -141,7 +145,6 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
|
|
|
141
145
|
// Check if this package has a handwritten equivalent
|
|
142
146
|
if hasHandwrittenEquivalent(pkg.PkgPath) {
|
|
143
147
|
// Add this package but don't visit its dependencies
|
|
144
|
-
// c.le.Debugf("Skipping dependencies of handwritten package: %s", pkg.PkgPath)
|
|
145
148
|
return
|
|
146
149
|
}
|
|
147
150
|
|
|
@@ -149,13 +152,11 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
|
|
|
149
152
|
for _, imp := range pkg.Imports {
|
|
150
153
|
// Skip protobuf-go-lite packages and their dependencies
|
|
151
154
|
if isProtobufGoLitePackage(imp.PkgPath) {
|
|
152
|
-
// c.le.Debugf("Skipping protobuf-go-lite package: %s", imp.PkgPath)
|
|
153
155
|
continue
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
// Skip packages that are only used by .pb.go files
|
|
157
159
|
if isPackageOnlyUsedByProtobufFiles(pkg, imp.PkgPath) {
|
|
158
|
-
// c.le.Debugf("Skipping package only used by .pb.go files: %s", imp.PkgPath)
|
|
159
160
|
continue
|
|
160
161
|
}
|
|
161
162
|
|
|
@@ -168,28 +169,39 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
|
|
|
168
169
|
visit(pkg)
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
//
|
|
172
|
-
|
|
172
|
+
// Now we have collected all dependencies, but they only have minimal information.
|
|
173
|
+
// We need to reload them with complete type information for compilation.
|
|
174
|
+
// Collect all package paths from the dependency graph
|
|
175
|
+
var pkgPaths []string
|
|
176
|
+
pkgPathSet := make(map[string]bool) // Use set to avoid duplicates
|
|
173
177
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if pkg.PkgPath != "" {
|
|
179
|
-
pkgPaths = append(pkgPaths, pkg.PkgPath)
|
|
180
|
-
}
|
|
178
|
+
for _, pkg := range allPkgs {
|
|
179
|
+
if pkg.PkgPath != "" && !pkgPathSet[pkg.PkgPath] {
|
|
180
|
+
pkgPaths = append(pkgPaths, pkg.PkgPath)
|
|
181
|
+
pkgPathSet[pkg.PkgPath] = true
|
|
181
182
|
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Reload all collected packages with complete type information
|
|
186
|
+
if len(pkgPaths) > 0 {
|
|
187
|
+
c.le.Debugf("Reloading %d packages with complete type information", len(pkgPaths))
|
|
182
188
|
|
|
183
|
-
// Reload all packages with full mode
|
|
184
|
-
// TODO: Can we get rid of this? This would be very slow!
|
|
185
189
|
fullOpts := c.opts
|
|
186
190
|
fullOpts.Context = ctx
|
|
191
|
+
// Use LoadAllSyntax to get complete type information, syntax trees, and type checking
|
|
187
192
|
fullOpts.Mode = packages.LoadAllSyntax
|
|
188
|
-
|
|
193
|
+
|
|
194
|
+
reloadedPkgs, err := packages.Load(&fullOpts, pkgPaths...)
|
|
189
195
|
if err != nil {
|
|
190
|
-
return fmt.Errorf("failed to reload packages with
|
|
196
|
+
return nil, fmt.Errorf("failed to reload packages with complete type information: %w", err)
|
|
191
197
|
}
|
|
192
|
-
|
|
198
|
+
|
|
199
|
+
// Replace the minimal packages with the fully loaded ones
|
|
200
|
+
pkgs = reloadedPkgs
|
|
201
|
+
} else {
|
|
202
|
+
// No packages to reload, use the original set
|
|
203
|
+
pkgs = allPkgs
|
|
204
|
+
}
|
|
193
205
|
}
|
|
194
206
|
|
|
195
207
|
// If DisableEmitBuiltin is false, we need to copy the builtin package to the output directory
|
|
@@ -206,6 +218,12 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
|
|
|
206
218
|
// Track which gs packages have been processed to avoid duplicates
|
|
207
219
|
processedGsPackages := make(map[string]bool)
|
|
208
220
|
|
|
221
|
+
// Create a map of all loaded packages for dependency analysis
|
|
222
|
+
allPackages := make(map[string]*packages.Package)
|
|
223
|
+
for _, pkg := range pkgs {
|
|
224
|
+
allPackages[pkg.PkgPath] = pkg
|
|
225
|
+
}
|
|
226
|
+
|
|
209
227
|
// Compile all packages
|
|
210
228
|
for _, pkg := range pkgs {
|
|
211
229
|
// Check if the package has a handwritten equivalent
|
|
@@ -237,7 +255,7 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
|
|
|
237
255
|
continue
|
|
238
256
|
}
|
|
239
257
|
|
|
240
|
-
pkgCompiler, err := NewPackageCompiler(c.le, &c.config, pkg)
|
|
258
|
+
pkgCompiler, err := NewPackageCompiler(c.le, &c.config, pkg, allPackages)
|
|
241
259
|
if err != nil {
|
|
242
260
|
return nil, fmt.Errorf("failed to create package compiler for %s: %w", pkg.PkgPath, err)
|
|
243
261
|
}
|
|
@@ -262,6 +280,7 @@ type PackageCompiler struct {
|
|
|
262
280
|
compilerConf *Config
|
|
263
281
|
outputPath string
|
|
264
282
|
pkg *packages.Package
|
|
283
|
+
allPackages map[string]*packages.Package
|
|
265
284
|
}
|
|
266
285
|
|
|
267
286
|
// NewPackageCompiler creates a new `PackageCompiler` for a given Go package.
|
|
@@ -272,12 +291,14 @@ func NewPackageCompiler(
|
|
|
272
291
|
le *logrus.Entry,
|
|
273
292
|
compilerConf *Config,
|
|
274
293
|
pkg *packages.Package,
|
|
294
|
+
allPackages map[string]*packages.Package,
|
|
275
295
|
) (*PackageCompiler, error) {
|
|
276
296
|
res := &PackageCompiler{
|
|
277
297
|
le: le,
|
|
278
298
|
pkg: pkg,
|
|
279
299
|
compilerConf: compilerConf,
|
|
280
300
|
outputPath: ComputeModulePath(compilerConf.OutputPath, pkg.PkgPath),
|
|
301
|
+
allPackages: allPackages,
|
|
281
302
|
}
|
|
282
303
|
|
|
283
304
|
return res, nil
|
|
@@ -304,7 +325,10 @@ func (c *PackageCompiler) Compile(ctx context.Context) error {
|
|
|
304
325
|
}
|
|
305
326
|
|
|
306
327
|
// Perform package-level analysis for auto-imports
|
|
307
|
-
packageAnalysis :=
|
|
328
|
+
packageAnalysis := AnalyzePackageImports(c.pkg)
|
|
329
|
+
|
|
330
|
+
// Perform comprehensive package-level analysis for code generation
|
|
331
|
+
analysis := AnalyzePackageFiles(c.pkg, c.allPackages)
|
|
308
332
|
|
|
309
333
|
// Track all compiled files for later generating the index.ts
|
|
310
334
|
compiledFiles := make([]string, 0, len(c.pkg.CompiledGoFiles))
|
|
@@ -342,7 +366,7 @@ func (c *PackageCompiler) Compile(ctx context.Context) error {
|
|
|
342
366
|
|
|
343
367
|
// log just the filename
|
|
344
368
|
c.le.Debugf("GS: %s", filepath.Base(fileName))
|
|
345
|
-
if err := c.CompileFile(ctx, fileName, f, packageAnalysis); err != nil {
|
|
369
|
+
if err := c.CompileFile(ctx, fileName, f, analysis, packageAnalysis); err != nil {
|
|
346
370
|
return err
|
|
347
371
|
}
|
|
348
372
|
|
|
@@ -392,6 +416,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
|
392
416
|
// Find which symbols this file exports
|
|
393
417
|
var valueSymbols []string
|
|
394
418
|
var typeSymbols []string
|
|
419
|
+
var structSymbols []string // New: Track structs separately
|
|
395
420
|
|
|
396
421
|
// Find the corresponding syntax file
|
|
397
422
|
for i, syntax := range c.pkg.Syntax {
|
|
@@ -408,21 +433,27 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
|
408
433
|
switch d := decl.(type) {
|
|
409
434
|
case *ast.FuncDecl:
|
|
410
435
|
if d.Recv == nil && d.Name.IsExported() {
|
|
411
|
-
valueSymbols = append(valueSymbols, d.Name.Name)
|
|
436
|
+
valueSymbols = append(valueSymbols, sanitizeIdentifier(d.Name.Name))
|
|
412
437
|
}
|
|
413
438
|
case *ast.GenDecl:
|
|
414
439
|
for _, spec := range d.Specs {
|
|
415
440
|
switch s := spec.(type) {
|
|
416
441
|
case *ast.TypeSpec:
|
|
417
442
|
if s.Name.IsExported() {
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
443
|
+
// Check if this is a struct type
|
|
444
|
+
if _, isStruct := s.Type.(*ast.StructType); isStruct {
|
|
445
|
+
// Structs become TypeScript classes and need both type and value exports
|
|
446
|
+
structSymbols = append(structSymbols, sanitizeIdentifier(s.Name.Name))
|
|
447
|
+
} else {
|
|
448
|
+
// Other type declarations (interfaces, type definitions, type aliases)
|
|
449
|
+
// become TypeScript types and must be exported with "export type"
|
|
450
|
+
typeSymbols = append(typeSymbols, sanitizeIdentifier(s.Name.Name))
|
|
451
|
+
}
|
|
421
452
|
}
|
|
422
453
|
case *ast.ValueSpec:
|
|
423
454
|
for _, name := range s.Names {
|
|
424
455
|
if name.IsExported() {
|
|
425
|
-
valueSymbols = append(valueSymbols, name.Name)
|
|
456
|
+
valueSymbols = append(valueSymbols, sanitizeIdentifier(name.Name))
|
|
426
457
|
}
|
|
427
458
|
}
|
|
428
459
|
}
|
|
@@ -442,6 +473,17 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
|
442
473
|
}
|
|
443
474
|
}
|
|
444
475
|
|
|
476
|
+
// Write struct exports (both as types and values)
|
|
477
|
+
if len(structSymbols) > 0 {
|
|
478
|
+
sort.Strings(structSymbols)
|
|
479
|
+
// Export classes as values (which makes them available as both types and values in TypeScript)
|
|
480
|
+
exportLine := fmt.Sprintf("export { %s } from \"./%s.js\"\n",
|
|
481
|
+
strings.Join(structSymbols, ", "), fileName)
|
|
482
|
+
if _, err := indexFile.WriteString(exportLine); err != nil {
|
|
483
|
+
return err
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
445
487
|
if len(typeSymbols) > 0 {
|
|
446
488
|
sort.Strings(typeSymbols)
|
|
447
489
|
exportLine := fmt.Sprintf("export type { %s } from \"./%s.js\"\n",
|
|
@@ -456,21 +498,11 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
|
456
498
|
}
|
|
457
499
|
|
|
458
500
|
// CompileFile handles the compilation of a single Go source file to TypeScript.
|
|
459
|
-
// It
|
|
460
|
-
//
|
|
461
|
-
// about varRefing, async functions, defer statements).
|
|
501
|
+
// It uses the pre-computed package-level analysis for accurate TypeScript generation
|
|
502
|
+
// (e.g., about varRefing, async functions, defer statements, receiver usage across files).
|
|
462
503
|
// Then, it creates a `FileCompiler` instance for the file and invokes its
|
|
463
504
|
// `Compile` method to generate the TypeScript code.
|
|
464
|
-
func (p *PackageCompiler) CompileFile(ctx context.Context, name string, syntax *ast.File, packageAnalysis *PackageAnalysis) error {
|
|
465
|
-
// Create a new analysis instance for per-file data
|
|
466
|
-
analysis := NewAnalysis()
|
|
467
|
-
|
|
468
|
-
// Create comment map for the file
|
|
469
|
-
cmap := ast.NewCommentMap(p.pkg.Fset, syntax, syntax.Comments)
|
|
470
|
-
|
|
471
|
-
// Analyze the file before compiling
|
|
472
|
-
AnalyzeFile(syntax, p.pkg, analysis, cmap)
|
|
473
|
-
|
|
505
|
+
func (p *PackageCompiler) CompileFile(ctx context.Context, name string, syntax *ast.File, analysis *Analysis, packageAnalysis *PackageAnalysis) error {
|
|
474
506
|
fileCompiler, err := NewFileCompiler(p.compilerConf, p.pkg, syntax, name, analysis, packageAnalysis)
|
|
475
507
|
if err != nil {
|
|
476
508
|
return err
|
|
@@ -644,6 +676,10 @@ type GoToTSCompiler struct {
|
|
|
644
676
|
pkg *packages.Package
|
|
645
677
|
|
|
646
678
|
analysis *Analysis
|
|
679
|
+
|
|
680
|
+
// awaitedCalls tracks which call expressions have already been processed
|
|
681
|
+
// to avoid adding double await keywords
|
|
682
|
+
awaitedCalls map[*ast.CallExpr]bool
|
|
647
683
|
}
|
|
648
684
|
|
|
649
685
|
// It initializes the compiler with a `TSCodeWriter` for output,
|
package/compiler/decl.go
CHANGED
|
@@ -3,6 +3,7 @@ package compiler
|
|
|
3
3
|
import (
|
|
4
4
|
"fmt"
|
|
5
5
|
"go/ast"
|
|
6
|
+
"go/types"
|
|
6
7
|
)
|
|
7
8
|
|
|
8
9
|
// WriteDecls iterates through a slice of Go top-level declarations (`ast.Decl`)
|
|
@@ -193,6 +194,27 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
|
|
|
193
194
|
var isAsync bool
|
|
194
195
|
if obj := c.pkg.TypesInfo.Defs[decl.Name]; obj != nil {
|
|
195
196
|
isAsync = c.analysis.IsAsyncFunc(obj)
|
|
197
|
+
|
|
198
|
+
// Check if this method must be async due to interface constraints
|
|
199
|
+
if !isAsync && decl.Recv != nil && len(decl.Recv.List) > 0 {
|
|
200
|
+
// Get the receiver type
|
|
201
|
+
receiverType := decl.Recv.List[0].Type
|
|
202
|
+
if starExpr, ok := receiverType.(*ast.StarExpr); ok {
|
|
203
|
+
receiverType = starExpr.X
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if ident, ok := receiverType.(*ast.Ident); ok {
|
|
207
|
+
// Get the named type for this receiver
|
|
208
|
+
if receiverObj := c.pkg.TypesInfo.Uses[ident]; receiverObj != nil {
|
|
209
|
+
if namedType, ok := receiverObj.Type().(*types.Named); ok {
|
|
210
|
+
// Check if this method must be async due to interface constraints
|
|
211
|
+
if c.analysis.MustBeAsyncDueToInterface(namedType, decl.Name.Name) {
|
|
212
|
+
isAsync = true
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
196
218
|
}
|
|
197
219
|
|
|
198
220
|
// Methods are typically public in the TS output
|
|
@@ -2,74 +2,273 @@ package compiler
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"go/ast"
|
|
5
|
+
"go/token"
|
|
5
6
|
"go/types"
|
|
6
7
|
"strings"
|
|
7
8
|
)
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// writeAsyncCallIfNeeded writes the await prefix for async function or method calls
|
|
11
|
+
// Returns true if await was written, false otherwise
|
|
12
|
+
func (c *GoToTSCompiler) writeAsyncCallIfNeeded(exp *ast.CallExpr) bool {
|
|
13
|
+
// Track if we've already processed this call expression to avoid double await
|
|
14
|
+
if c.awaitedCalls == nil {
|
|
15
|
+
c.awaitedCalls = make(map[*ast.CallExpr]bool)
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
c.tsw.WriteLiterally("await ")
|
|
18
|
-
return true
|
|
18
|
+
if c.awaitedCalls[exp] {
|
|
19
|
+
return false // Already processed this call
|
|
19
20
|
}
|
|
21
|
+
switch fun := exp.Fun.(type) {
|
|
22
|
+
case *ast.Ident:
|
|
23
|
+
// Function call (e.g., func())
|
|
24
|
+
if obj := c.pkg.TypesInfo.Uses[fun]; obj != nil && c.analysis.IsAsyncFunc(obj) {
|
|
25
|
+
c.awaitedCalls[exp] = true
|
|
26
|
+
c.tsw.WriteLiterally("await ")
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
return false
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
case *ast.SelectorExpr:
|
|
32
|
+
// Method call (e.g., obj.method())
|
|
33
|
+
var ident *ast.Ident
|
|
34
|
+
var identOk bool
|
|
35
|
+
|
|
36
|
+
// Handle both direct identifiers and pointer dereferences
|
|
37
|
+
switch x := fun.X.(type) {
|
|
38
|
+
case *ast.Ident:
|
|
39
|
+
ident, identOk = x, true
|
|
40
|
+
case *ast.StarExpr:
|
|
41
|
+
// Handle pointer dereference: (*p).method() or p.method() where p is a pointer
|
|
42
|
+
if id, isIdent := x.X.(*ast.Ident); isIdent {
|
|
43
|
+
ident, identOk = id, true
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if !identOk {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get the type of the receiver
|
|
54
|
+
obj := c.pkg.TypesInfo.Uses[ident]
|
|
55
|
+
if obj == nil {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
varObj, ok := obj.(*types.Var)
|
|
60
|
+
if !ok {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get the type name and package
|
|
65
|
+
var namedType *types.Named
|
|
66
|
+
var namedTypeOk bool
|
|
67
|
+
|
|
68
|
+
// Handle both direct named types and pointer to named types
|
|
69
|
+
switch t := varObj.Type().(type) {
|
|
70
|
+
case *types.Named:
|
|
71
|
+
namedType, namedTypeOk = t, true
|
|
72
|
+
case *types.Pointer:
|
|
73
|
+
if nt, isNamed := t.Elem().(*types.Named); isNamed {
|
|
74
|
+
namedType, namedTypeOk = nt, true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if !namedTypeOk {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
typeName := namedType.Obj().Name()
|
|
83
|
+
methodName := fun.Sel.Name
|
|
23
84
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
85
|
+
// Check if the type is from an imported package
|
|
86
|
+
typePkg := namedType.Obj().Pkg()
|
|
87
|
+
if typePkg != nil && typePkg != c.pkg.Types {
|
|
88
|
+
// Use the full package path from the type information (not just the package name)
|
|
89
|
+
pkgPath := typePkg.Path()
|
|
90
|
+
|
|
91
|
+
// Check if this method is async based on metadata
|
|
92
|
+
if c.analysis.IsMethodAsync(pkgPath, typeName, methodName) {
|
|
93
|
+
c.awaitedCalls[exp] = true
|
|
94
|
+
c.tsw.WriteLiterally("await ")
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// For local types, check if the method contains async operations at runtime
|
|
99
|
+
// to avoid circular dependencies during analysis
|
|
100
|
+
if c.isLocalMethodAsync(namedType, methodName) {
|
|
101
|
+
c.awaitedCalls[exp] = true
|
|
102
|
+
c.tsw.WriteLiterally("await ")
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
28
106
|
return false
|
|
29
|
-
}
|
|
30
107
|
|
|
31
|
-
|
|
32
|
-
ident, ok := selExpr.X.(*ast.Ident)
|
|
33
|
-
if !ok {
|
|
108
|
+
default:
|
|
34
109
|
return false
|
|
35
110
|
}
|
|
111
|
+
}
|
|
36
112
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
113
|
+
// isLocalMethodAsync checks if a local method contains async operations by inspecting its body
|
|
114
|
+
func (c *GoToTSCompiler) isLocalMethodAsync(namedType *types.Named, methodName string) bool {
|
|
115
|
+
// Find the method in the named type
|
|
116
|
+
for i := 0; i < namedType.NumMethods(); i++ {
|
|
117
|
+
method := namedType.Method(i)
|
|
118
|
+
if method.Name() == methodName {
|
|
119
|
+
// Find the method declaration in the AST
|
|
120
|
+
methodDecl := c.findMethodDecl(namedType, methodName)
|
|
121
|
+
if methodDecl != nil && methodDecl.Body != nil {
|
|
122
|
+
// Check if the method body contains async operations
|
|
123
|
+
return c.containsAsyncOperationsRuntime(methodDecl.Body)
|
|
124
|
+
}
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// findMethodDecl finds the AST declaration for a method on a named type
|
|
132
|
+
func (c *GoToTSCompiler) findMethodDecl(namedType *types.Named, methodName string) *ast.FuncDecl {
|
|
133
|
+
// Search through all files in the package for the method declaration
|
|
134
|
+
for _, file := range c.pkg.Syntax {
|
|
135
|
+
for _, decl := range file.Decls {
|
|
136
|
+
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
|
137
|
+
// Check if this is a method with the right name
|
|
138
|
+
if funcDecl.Name.Name == methodName && funcDecl.Recv != nil {
|
|
139
|
+
// Check if the receiver type matches
|
|
140
|
+
if c.isReceiverOfType(funcDecl, namedType) {
|
|
141
|
+
return funcDecl
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
41
146
|
}
|
|
147
|
+
return nil
|
|
148
|
+
}
|
|
42
149
|
|
|
43
|
-
|
|
44
|
-
|
|
150
|
+
// isReceiverOfType checks if a function declaration is a method of the given named type
|
|
151
|
+
func (c *GoToTSCompiler) isReceiverOfType(funcDecl *ast.FuncDecl, namedType *types.Named) bool {
|
|
152
|
+
if funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 {
|
|
45
153
|
return false
|
|
46
154
|
}
|
|
47
155
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if !ok {
|
|
156
|
+
recvField := funcDecl.Recv.List[0]
|
|
157
|
+
if len(recvField.Names) == 0 {
|
|
51
158
|
return false
|
|
52
159
|
}
|
|
53
160
|
|
|
54
|
-
|
|
55
|
-
|
|
161
|
+
recvIdent := recvField.Names[0]
|
|
162
|
+
if obj := c.pkg.TypesInfo.Defs[recvIdent]; obj != nil {
|
|
163
|
+
if varObj, ok := obj.(*types.Var); ok {
|
|
164
|
+
// Handle both direct named types and pointer to named types
|
|
165
|
+
var receiverNamedType *types.Named
|
|
166
|
+
switch t := varObj.Type().(type) {
|
|
167
|
+
case *types.Named:
|
|
168
|
+
receiverNamedType = t
|
|
169
|
+
case *types.Pointer:
|
|
170
|
+
if nt, isNamed := t.Elem().(*types.Named); isNamed {
|
|
171
|
+
receiverNamedType = nt
|
|
172
|
+
}
|
|
173
|
+
}
|
|
56
174
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
175
|
+
if receiverNamedType != nil {
|
|
176
|
+
// For generic types, compare the origin types and names
|
|
177
|
+
// since instantiated generics have different type objects
|
|
178
|
+
targetObj := namedType.Obj()
|
|
179
|
+
receiverObj := receiverNamedType.Obj()
|
|
180
|
+
|
|
181
|
+
// Compare by name and package path
|
|
182
|
+
return targetObj.Name() == receiverObj.Name() &&
|
|
183
|
+
targetObj.Pkg() == receiverObj.Pkg()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
61
186
|
}
|
|
62
187
|
|
|
63
|
-
|
|
64
|
-
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// containsAsyncOperationsRuntime checks if a node contains async operations at runtime
|
|
192
|
+
func (c *GoToTSCompiler) containsAsyncOperationsRuntime(node ast.Node) bool {
|
|
193
|
+
var hasAsync bool
|
|
194
|
+
|
|
195
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
196
|
+
if n == nil {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
switch s := n.(type) {
|
|
201
|
+
case *ast.SendStmt:
|
|
202
|
+
// Channel send operation (ch <- value)
|
|
203
|
+
hasAsync = true
|
|
204
|
+
return false
|
|
205
|
+
|
|
206
|
+
case *ast.UnaryExpr:
|
|
207
|
+
// Channel receive operation (<-ch)
|
|
208
|
+
if s.Op == token.ARROW {
|
|
209
|
+
hasAsync = true
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
case *ast.SelectStmt:
|
|
214
|
+
// Select statement with channel operations
|
|
215
|
+
hasAsync = true
|
|
216
|
+
return false
|
|
217
|
+
|
|
218
|
+
case *ast.CallExpr:
|
|
219
|
+
// Check if we're calling a function known to be async
|
|
220
|
+
if funcIdent, ok := s.Fun.(*ast.Ident); ok {
|
|
221
|
+
// Get the object for this function call
|
|
222
|
+
if obj := c.pkg.TypesInfo.Uses[funcIdent]; obj != nil && c.analysis.IsAsyncFunc(obj) {
|
|
223
|
+
hasAsync = true
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for method calls on imported types (similar to analysis.go logic)
|
|
229
|
+
if selExpr, ok := s.Fun.(*ast.SelectorExpr); ok {
|
|
230
|
+
if ident, ok := selExpr.X.(*ast.Ident); ok {
|
|
231
|
+
if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
232
|
+
if varObj, ok := obj.(*types.Var); ok {
|
|
233
|
+
var namedType *types.Named
|
|
234
|
+
switch t := varObj.Type().(type) {
|
|
235
|
+
case *types.Named:
|
|
236
|
+
namedType = t
|
|
237
|
+
case *types.Pointer:
|
|
238
|
+
if nt, isNamed := t.Elem().(*types.Named); isNamed {
|
|
239
|
+
namedType = nt
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if namedType != nil {
|
|
244
|
+
typeName := namedType.Obj().Name()
|
|
245
|
+
methodName := selExpr.Sel.Name
|
|
246
|
+
|
|
247
|
+
// Check if the type is from an imported package
|
|
248
|
+
if typePkg := namedType.Obj().Pkg(); typePkg != nil && typePkg != c.pkg.Types {
|
|
249
|
+
pkgPath := typePkg.Path()
|
|
250
|
+
if c.analysis.IsMethodAsync(pkgPath, typeName, methodName) {
|
|
251
|
+
hasAsync = true
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
// For local types, recursively check
|
|
256
|
+
if c.isLocalMethodAsync(namedType, methodName) {
|
|
257
|
+
hasAsync = true
|
|
258
|
+
return false
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
65
267
|
|
|
66
|
-
// Check if this method is async based on metadata
|
|
67
|
-
if c.analysis.IsMethodAsync(pkgName, typeName, methodName) {
|
|
68
|
-
c.tsw.WriteLiterally("await ")
|
|
69
268
|
return true
|
|
70
|
-
}
|
|
269
|
+
})
|
|
71
270
|
|
|
72
|
-
return
|
|
271
|
+
return hasAsync
|
|
73
272
|
}
|
|
74
273
|
|
|
75
274
|
// addNonNullAssertion adds ! for function calls that might return null
|