goscript 0.0.22 → 0.0.23
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/cmd/goscript/cmd_compile.go +2 -2
- package/compiler/analysis.go +229 -51
- package/compiler/assignment.go +6 -1
- package/compiler/compiler.go +41 -18
- package/compiler/compiler_test.go +36 -8
- package/compiler/composite-lit.go +25 -10
- package/compiler/decl.go +36 -0
- package/compiler/expr-call.go +116 -60
- package/compiler/expr-selector.go +22 -2
- package/compiler/expr-type.go +131 -4
- package/compiler/expr-value.go +7 -37
- package/compiler/expr.go +247 -12
- package/compiler/field.go +3 -3
- package/compiler/lit.go +34 -2
- package/compiler/primitive.go +8 -2
- package/compiler/spec-struct.go +137 -6
- package/compiler/spec-value.go +50 -18
- package/compiler/spec.go +12 -3
- package/compiler/stmt-assign.go +29 -1
- package/compiler/stmt-range.go +9 -11
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +129 -244
- package/compiler/type-assert.go +125 -379
- package/compiler/type.go +187 -125
- package/package.json +5 -5
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2379
- package/dist/builtin/builtin.d.ts +0 -513
- package/dist/builtin/builtin.js +0 -1686
- package/dist/builtin/builtin.js.map +0 -1
|
@@ -9,8 +9,8 @@ import (
|
|
|
9
9
|
"github.com/pkg/errors"
|
|
10
10
|
"github.com/sirupsen/logrus"
|
|
11
11
|
|
|
12
|
-
// _ ensure we include the
|
|
13
|
-
_ "github.com/aperturerobotics/goscript
|
|
12
|
+
// _ ensure we include the gs package
|
|
13
|
+
_ "github.com/aperturerobotics/goscript"
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
var (
|
package/compiler/analysis.go
CHANGED
|
@@ -41,6 +41,21 @@ type VariableUsageInfo struct {
|
|
|
41
41
|
Destinations []AssignmentInfo
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// FunctionInfo consolidates function-related tracking data.
|
|
45
|
+
type FunctionInfo struct {
|
|
46
|
+
IsAsync bool
|
|
47
|
+
NamedReturns []string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// NodeInfo consolidates node-related tracking data.
|
|
51
|
+
type NodeInfo struct {
|
|
52
|
+
NeedsDefer bool
|
|
53
|
+
InAsyncContext bool
|
|
54
|
+
IsBareReturn bool
|
|
55
|
+
EnclosingFuncDecl *ast.FuncDecl
|
|
56
|
+
EnclosingFuncLit *ast.FuncLit
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
// Analysis holds information gathered during the analysis phase of the Go code compilation.
|
|
45
60
|
// This data is used to make decisions about how to generate TypeScript code.
|
|
46
61
|
// Analysis is read-only after being built and should not be modified during code generation.
|
|
@@ -55,25 +70,25 @@ type Analysis struct {
|
|
|
55
70
|
// Cmap stores the comment map for the file
|
|
56
71
|
Cmap ast.CommentMap
|
|
57
72
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
AsyncFuncs map[types.Object]bool
|
|
73
|
+
// FunctionData consolidates function-related tracking into one map
|
|
74
|
+
FunctionData map[types.Object]*FunctionInfo
|
|
61
75
|
|
|
62
|
-
//
|
|
63
|
-
|
|
76
|
+
// NodeData consolidates node-related tracking into one map
|
|
77
|
+
NodeData map[ast.Node]*NodeInfo
|
|
64
78
|
|
|
65
|
-
//
|
|
66
|
-
|
|
79
|
+
// Keep specialized maps that serve different purposes
|
|
80
|
+
// FuncLitData tracks function literal specific data since they don't have types.Object
|
|
81
|
+
FuncLitData map[*ast.FuncLit]*FunctionInfo
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
// NewAnalysis creates a new Analysis instance.
|
|
70
85
|
func NewAnalysis() *Analysis {
|
|
71
86
|
return &Analysis{
|
|
72
|
-
VariableUsage:
|
|
73
|
-
Imports:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
VariableUsage: make(map[types.Object]*VariableUsageInfo),
|
|
88
|
+
Imports: make(map[string]*fileImport),
|
|
89
|
+
FunctionData: make(map[types.Object]*FunctionInfo),
|
|
90
|
+
NodeData: make(map[ast.Node]*NodeInfo),
|
|
91
|
+
FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
|
|
@@ -82,7 +97,11 @@ func (a *Analysis) NeedsDefer(node ast.Node) bool {
|
|
|
82
97
|
if node == nil {
|
|
83
98
|
return false
|
|
84
99
|
}
|
|
85
|
-
|
|
100
|
+
nodeInfo := a.NodeData[node]
|
|
101
|
+
if nodeInfo == nil {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
return nodeInfo.NeedsDefer
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
// IsInAsyncFunction returns whether the given node is inside an async function.
|
|
@@ -90,7 +109,11 @@ func (a *Analysis) IsInAsyncFunction(node ast.Node) bool {
|
|
|
90
109
|
if node == nil {
|
|
91
110
|
return false
|
|
92
111
|
}
|
|
93
|
-
|
|
112
|
+
nodeInfo := a.NodeData[node]
|
|
113
|
+
if nodeInfo == nil {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
return nodeInfo.InAsyncContext
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
// IsAsyncFunc returns whether the given object represents an async function.
|
|
@@ -98,7 +121,11 @@ func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
|
|
|
98
121
|
if obj == nil {
|
|
99
122
|
return false
|
|
100
123
|
}
|
|
101
|
-
|
|
124
|
+
funcInfo := a.FunctionData[obj]
|
|
125
|
+
if funcInfo == nil {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
return funcInfo.IsAsync
|
|
102
129
|
}
|
|
103
130
|
|
|
104
131
|
// IsFuncLitAsync checks if a function literal is async based on our analysis.
|
|
@@ -106,8 +133,16 @@ func (a *Analysis) IsFuncLitAsync(funcLit *ast.FuncLit) bool {
|
|
|
106
133
|
if funcLit == nil {
|
|
107
134
|
return false
|
|
108
135
|
}
|
|
109
|
-
//
|
|
110
|
-
|
|
136
|
+
// Check function literal specific data first
|
|
137
|
+
if funcInfo := a.FuncLitData[funcLit]; funcInfo != nil {
|
|
138
|
+
return funcInfo.IsAsync
|
|
139
|
+
}
|
|
140
|
+
// Fall back to node data for backwards compatibility
|
|
141
|
+
nodeInfo := a.NodeData[funcLit]
|
|
142
|
+
if nodeInfo == nil {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
return nodeInfo.InAsyncContext
|
|
111
146
|
}
|
|
112
147
|
|
|
113
148
|
// NeedsBoxed returns whether the given object needs to be boxed.
|
|
@@ -302,6 +337,12 @@ type analysisVisitor struct {
|
|
|
302
337
|
|
|
303
338
|
// currentFuncObj tracks the object of the function declaration we're currently analyzing
|
|
304
339
|
currentFuncObj types.Object
|
|
340
|
+
|
|
341
|
+
// currentFuncDecl tracks the *ast.FuncDecl of the function we're currently analyzing.
|
|
342
|
+
currentFuncDecl *ast.FuncDecl
|
|
343
|
+
|
|
344
|
+
// currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
|
|
345
|
+
currentFuncLit *ast.FuncLit
|
|
305
346
|
}
|
|
306
347
|
|
|
307
348
|
// getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
|
|
@@ -324,8 +365,11 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
324
365
|
return nil
|
|
325
366
|
}
|
|
326
367
|
|
|
327
|
-
//
|
|
328
|
-
v.analysis.
|
|
368
|
+
// Initialize and store async state for the current node
|
|
369
|
+
if v.analysis.NodeData[node] == nil {
|
|
370
|
+
v.analysis.NodeData[node] = &NodeInfo{}
|
|
371
|
+
}
|
|
372
|
+
v.analysis.NodeData[node].InAsyncContext = v.inAsyncFunction
|
|
329
373
|
|
|
330
374
|
switch n := node.(type) {
|
|
331
375
|
case *ast.GenDecl:
|
|
@@ -393,24 +437,40 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
393
437
|
return v
|
|
394
438
|
|
|
395
439
|
case *ast.FuncDecl:
|
|
396
|
-
//
|
|
440
|
+
// Save original states to restore after visiting
|
|
441
|
+
originalInAsync := v.inAsyncFunction
|
|
442
|
+
originalFuncObj := v.currentFuncObj
|
|
443
|
+
originalFuncDecl := v.currentFuncDecl
|
|
444
|
+
originalFuncLit := v.currentFuncLit
|
|
445
|
+
originalReceiver := v.currentReceiver
|
|
446
|
+
|
|
447
|
+
// Reset for current function
|
|
397
448
|
v.currentFuncName = n.Name.Name
|
|
449
|
+
v.currentFuncDecl = n
|
|
450
|
+
v.currentFuncLit = nil
|
|
451
|
+
v.currentReceiver = nil
|
|
452
|
+
|
|
453
|
+
// Determine if this function declaration is async based on its body
|
|
398
454
|
isAsync := false
|
|
399
455
|
if n.Body != nil {
|
|
400
456
|
containsAsyncOps := v.containsAsyncOperations(n.Body)
|
|
401
457
|
if containsAsyncOps {
|
|
402
458
|
// Get the object for this function declaration
|
|
403
459
|
if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
|
|
404
|
-
v.analysis.
|
|
460
|
+
v.analysis.FunctionData[obj] = &FunctionInfo{
|
|
461
|
+
IsAsync: true,
|
|
462
|
+
NamedReturns: v.getNamedReturns(n),
|
|
463
|
+
}
|
|
464
|
+
isAsync = true
|
|
405
465
|
}
|
|
406
|
-
isAsync = true
|
|
407
466
|
}
|
|
408
467
|
}
|
|
409
|
-
v.analysis.
|
|
468
|
+
if v.analysis.NodeData[n] == nil {
|
|
469
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
470
|
+
}
|
|
471
|
+
v.analysis.NodeData[n].InAsyncContext = isAsync
|
|
410
472
|
|
|
411
473
|
// Set current receiver if this is a method
|
|
412
|
-
originalReceiver := v.currentReceiver
|
|
413
|
-
v.currentReceiver = nil // Reset for current function
|
|
414
474
|
if n.Recv != nil && len(n.Recv.List) > 0 {
|
|
415
475
|
// Assuming a single receiver for simplicity for now
|
|
416
476
|
if len(n.Recv.List[0].Names) > 0 {
|
|
@@ -427,22 +487,41 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
427
487
|
}
|
|
428
488
|
}
|
|
429
489
|
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
490
|
+
// Store named return variables
|
|
491
|
+
if n.Type != nil && n.Type.Results != nil {
|
|
492
|
+
var namedReturns []string
|
|
493
|
+
for _, field := range n.Type.Results.List {
|
|
494
|
+
for _, name := range field.Names {
|
|
495
|
+
namedReturns = append(namedReturns, name.Name)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if len(namedReturns) > 0 {
|
|
499
|
+
if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
|
|
500
|
+
if v.analysis.FunctionData[obj] == nil {
|
|
501
|
+
v.analysis.FunctionData[obj] = &FunctionInfo{}
|
|
502
|
+
}
|
|
503
|
+
v.analysis.FunctionData[obj].NamedReturns = namedReturns
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
433
507
|
|
|
434
508
|
// Update visitor state for this function
|
|
435
509
|
v.inAsyncFunction = isAsync
|
|
436
510
|
v.currentFuncObj = v.pkg.TypesInfo.ObjectOf(n.Name)
|
|
437
|
-
v.analysis.
|
|
511
|
+
v.analysis.NodeData[n].InAsyncContext = isAsync // Ensure FuncDecl node itself is marked
|
|
438
512
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
v.
|
|
442
|
-
|
|
513
|
+
if n.Body != nil {
|
|
514
|
+
// Check if the body contains any defer statements
|
|
515
|
+
if v.containsDefer(n.Body) {
|
|
516
|
+
if v.analysis.NodeData[n] == nil {
|
|
517
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
518
|
+
}
|
|
519
|
+
v.analysis.NodeData[n].NeedsDefer = true
|
|
520
|
+
}
|
|
443
521
|
|
|
444
|
-
|
|
445
|
-
|
|
522
|
+
// Visit the body with updated state
|
|
523
|
+
ast.Walk(v, n.Body)
|
|
524
|
+
}
|
|
446
525
|
|
|
447
526
|
// Restore states after visiting
|
|
448
527
|
defer func() {
|
|
@@ -450,21 +529,52 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
450
529
|
v.inAsyncFunction = originalInAsync
|
|
451
530
|
v.currentReceiver = originalReceiver
|
|
452
531
|
v.currentFuncObj = originalFuncObj
|
|
532
|
+
v.currentFuncDecl = originalFuncDecl
|
|
533
|
+
v.currentFuncLit = originalFuncLit
|
|
453
534
|
}()
|
|
454
535
|
return nil // Stop traversal here, ast.Walk handled the body
|
|
455
536
|
|
|
456
537
|
case *ast.FuncLit:
|
|
538
|
+
// Save original inAsyncFunction state to restore after visiting
|
|
539
|
+
originalInAsync := v.inAsyncFunction
|
|
540
|
+
originalFuncDecl := v.currentFuncDecl
|
|
541
|
+
originalFuncLit := v.currentFuncLit
|
|
542
|
+
|
|
543
|
+
// Set current function literal
|
|
544
|
+
v.currentFuncDecl = nil
|
|
545
|
+
v.currentFuncLit = n
|
|
546
|
+
|
|
457
547
|
// Determine if this function literal is async based on its body
|
|
458
548
|
isAsync := v.containsAsyncOperations(n.Body)
|
|
459
|
-
v.analysis.
|
|
549
|
+
if v.analysis.NodeData[n] == nil {
|
|
550
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
551
|
+
}
|
|
552
|
+
v.analysis.NodeData[n].InAsyncContext = isAsync
|
|
553
|
+
|
|
554
|
+
// Store named return variables for function literal
|
|
555
|
+
if n.Type != nil && n.Type.Results != nil {
|
|
556
|
+
var namedReturns []string
|
|
557
|
+
for _, field := range n.Type.Results.List {
|
|
558
|
+
for _, name := range field.Names {
|
|
559
|
+
namedReturns = append(namedReturns, name.Name)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if len(namedReturns) > 0 {
|
|
563
|
+
v.analysis.FuncLitData[n] = &FunctionInfo{
|
|
564
|
+
IsAsync: isAsync,
|
|
565
|
+
NamedReturns: namedReturns,
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
460
569
|
|
|
461
|
-
// Save original inAsyncFunction state to restore after visiting
|
|
462
|
-
originalInAsync := v.inAsyncFunction
|
|
463
570
|
v.inAsyncFunction = isAsync
|
|
464
571
|
|
|
465
572
|
// Check if the body contains any defer statements
|
|
466
573
|
if n.Body != nil && v.containsDefer(n.Body) {
|
|
467
|
-
v.analysis.
|
|
574
|
+
if v.analysis.NodeData[n] == nil {
|
|
575
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
576
|
+
}
|
|
577
|
+
v.analysis.NodeData[n].NeedsDefer = true
|
|
468
578
|
}
|
|
469
579
|
|
|
470
580
|
// Visit the body with updated state
|
|
@@ -472,16 +582,27 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
472
582
|
|
|
473
583
|
// Restore inAsyncFunction state after visiting
|
|
474
584
|
v.inAsyncFunction = originalInAsync
|
|
585
|
+
v.currentFuncDecl = originalFuncDecl
|
|
586
|
+
v.currentFuncLit = originalFuncLit
|
|
475
587
|
return nil // Stop traversal here, ast.Walk handled the body
|
|
476
588
|
|
|
477
589
|
case *ast.BlockStmt:
|
|
590
|
+
if n == nil || len(n.List) == 0 {
|
|
591
|
+
break
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Initialize NodeData for this block
|
|
595
|
+
if v.analysis.NodeData[n] == nil {
|
|
596
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
597
|
+
}
|
|
598
|
+
|
|
478
599
|
// Check for defer statements in this block
|
|
479
600
|
if v.containsDefer(n) {
|
|
480
|
-
v.analysis.
|
|
601
|
+
v.analysis.NodeData[n].NeedsDefer = true
|
|
481
602
|
}
|
|
482
603
|
|
|
483
604
|
// Store async state for this block
|
|
484
|
-
v.analysis.
|
|
605
|
+
v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
|
|
485
606
|
|
|
486
607
|
return v
|
|
487
608
|
|
|
@@ -500,12 +621,18 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
500
621
|
if obj := v.pkg.TypesInfo.Uses[funcIdent]; obj != nil && v.analysis.IsAsyncFunc(obj) {
|
|
501
622
|
// We're calling an async function, so mark current function as async if we're in one
|
|
502
623
|
if v.currentFuncObj != nil {
|
|
503
|
-
v.analysis.
|
|
624
|
+
v.analysis.FunctionData[v.currentFuncObj] = &FunctionInfo{
|
|
625
|
+
IsAsync: true,
|
|
626
|
+
NamedReturns: v.getNamedReturns(v.currentFuncDecl),
|
|
627
|
+
}
|
|
504
628
|
v.inAsyncFunction = true // Update visitor state
|
|
505
629
|
// Mark the FuncDecl node itself if possible (might need to store the node too)
|
|
506
|
-
for nodeAst := range v.analysis.
|
|
630
|
+
for nodeAst := range v.analysis.NodeData { // Find the node to update
|
|
507
631
|
if fd, ok := nodeAst.(*ast.FuncDecl); ok && v.pkg.TypesInfo.ObjectOf(fd.Name) == v.currentFuncObj {
|
|
508
|
-
v.analysis.
|
|
632
|
+
if v.analysis.NodeData[nodeAst] == nil {
|
|
633
|
+
v.analysis.NodeData[nodeAst] = &NodeInfo{}
|
|
634
|
+
}
|
|
635
|
+
v.analysis.NodeData[nodeAst].InAsyncContext = true
|
|
509
636
|
}
|
|
510
637
|
}
|
|
511
638
|
}
|
|
@@ -513,7 +640,10 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
513
640
|
}
|
|
514
641
|
|
|
515
642
|
// Store async state for this call expression
|
|
516
|
-
v.analysis.
|
|
643
|
+
if v.analysis.NodeData[n] == nil {
|
|
644
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
645
|
+
}
|
|
646
|
+
v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
|
|
517
647
|
|
|
518
648
|
return v
|
|
519
649
|
|
|
@@ -610,14 +740,46 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
|
610
740
|
}
|
|
611
741
|
return v // Continue traversal
|
|
612
742
|
|
|
613
|
-
case *ast.
|
|
614
|
-
//
|
|
615
|
-
|
|
743
|
+
case *ast.ReturnStmt:
|
|
744
|
+
// Initialize NodeData for return statement
|
|
745
|
+
if v.analysis.NodeData[n] == nil {
|
|
746
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
747
|
+
}
|
|
616
748
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
749
|
+
// Record the enclosing function/literal for this return statement
|
|
750
|
+
if v.currentFuncDecl != nil {
|
|
751
|
+
v.analysis.NodeData[n].EnclosingFuncDecl = v.currentFuncDecl
|
|
752
|
+
} else if v.currentFuncLit != nil {
|
|
753
|
+
v.analysis.NodeData[n].EnclosingFuncLit = v.currentFuncLit
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Check if it's a bare return
|
|
757
|
+
if len(n.Results) == 0 {
|
|
758
|
+
if v.currentFuncDecl != nil {
|
|
759
|
+
// Check if the enclosing function declaration has named returns
|
|
760
|
+
if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
|
|
761
|
+
if _, ok := v.analysis.FunctionData[obj]; ok {
|
|
762
|
+
if v.analysis.NodeData[n] == nil {
|
|
763
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
764
|
+
}
|
|
765
|
+
v.analysis.NodeData[n].IsBareReturn = true
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} else if v.currentFuncLit != nil {
|
|
769
|
+
// Check if the enclosing function literal has named returns
|
|
770
|
+
if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
|
|
771
|
+
if v.analysis.NodeData[n] == nil {
|
|
772
|
+
v.analysis.NodeData[n] = &NodeInfo{}
|
|
773
|
+
}
|
|
774
|
+
v.analysis.NodeData[n].IsBareReturn = true
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return v // Continue traversal
|
|
620
779
|
}
|
|
780
|
+
|
|
781
|
+
// For all other nodes, continue traversal
|
|
782
|
+
return v
|
|
621
783
|
}
|
|
622
784
|
|
|
623
785
|
// containsAsyncOperations checks if a node contains any async operations like channel operations.
|
|
@@ -666,6 +828,9 @@ func (v *analysisVisitor) containsDefer(block *ast.BlockStmt) bool {
|
|
|
666
828
|
hasDefer := false
|
|
667
829
|
|
|
668
830
|
ast.Inspect(block, func(n ast.Node) bool {
|
|
831
|
+
if n == nil {
|
|
832
|
+
return true
|
|
833
|
+
}
|
|
669
834
|
if _, ok := n.(*ast.DeferStmt); ok {
|
|
670
835
|
hasDefer = true
|
|
671
836
|
return false
|
|
@@ -724,3 +889,16 @@ func AnalyzeFile(file *ast.File, pkg *packages.Package, analysis *Analysis, cmap
|
|
|
724
889
|
// Walk the AST with our visitor
|
|
725
890
|
ast.Walk(visitor, file)
|
|
726
891
|
}
|
|
892
|
+
|
|
893
|
+
// getNamedReturns retrieves the named returns for a function
|
|
894
|
+
func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
|
|
895
|
+
var namedReturns []string
|
|
896
|
+
if funcDecl.Type != nil && funcDecl.Type.Results != nil {
|
|
897
|
+
for _, field := range funcDecl.Type.Results.List {
|
|
898
|
+
for _, name := range field.Names {
|
|
899
|
+
namedReturns = append(namedReturns, name.Name)
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return namedReturns
|
|
904
|
+
}
|
package/compiler/assignment.go
CHANGED
|
@@ -100,7 +100,7 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
|
|
|
100
100
|
if lhsObj != nil {
|
|
101
101
|
c.tsw.WriteLiterally(": ")
|
|
102
102
|
c.tsw.WriteLiterally("$.Box<")
|
|
103
|
-
c.WriteGoType(lhsObj.Type())
|
|
103
|
+
c.WriteGoType(lhsObj.Type(), GoTypeContextGeneral)
|
|
104
104
|
c.tsw.WriteLiterally(">")
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -177,6 +177,11 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
|
|
|
177
177
|
return err
|
|
178
178
|
}
|
|
179
179
|
c.tsw.WriteLiterally("]")
|
|
180
|
+
} else if callExpr, isCallExpr := r.(*ast.CallExpr); isCallExpr {
|
|
181
|
+
// If the RHS is a function call, write it as a call
|
|
182
|
+
if err := c.WriteCallExpr(callExpr); err != nil {
|
|
183
|
+
return err
|
|
184
|
+
}
|
|
180
185
|
} else {
|
|
181
186
|
// Normal case - write the entire expression
|
|
182
187
|
if err := c.WriteValueExpr(r); err != nil {
|
package/compiler/compiler.go
CHANGED
|
@@ -8,8 +8,10 @@ import (
|
|
|
8
8
|
"go/types"
|
|
9
9
|
"os"
|
|
10
10
|
"path/filepath"
|
|
11
|
+
"slices"
|
|
11
12
|
"strings"
|
|
12
13
|
|
|
14
|
+
gs "github.com/aperturerobotics/goscript"
|
|
13
15
|
"github.com/sirupsen/logrus"
|
|
14
16
|
"golang.org/x/tools/go/packages"
|
|
15
17
|
)
|
|
@@ -88,6 +90,12 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
|
|
|
88
90
|
return fmt.Errorf("failed to load packages: %w", err)
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
// build a list of packages that patterns matched
|
|
94
|
+
patternPkgPaths := make([]string, 0, len(pkgs))
|
|
95
|
+
for _, pkg := range pkgs {
|
|
96
|
+
patternPkgPaths = append(patternPkgPaths, pkg.PkgPath)
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
// If AllDependencies is true, we need to collect all dependencies
|
|
92
100
|
if c.config.AllDependencies {
|
|
93
101
|
// Create a set to track processed packages by their ID
|
|
@@ -140,6 +148,18 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
|
|
|
140
148
|
|
|
141
149
|
// Compile all packages
|
|
142
150
|
for _, pkg := range pkgs {
|
|
151
|
+
// Check if the package has a handwritten equivalent
|
|
152
|
+
if !slices.Contains(patternPkgPaths, pkg.PkgPath) {
|
|
153
|
+
_, gsErr := gs.GsOverrides.ReadDir("gs/" + pkg.PkgPath)
|
|
154
|
+
if gsErr != nil && !os.IsNotExist(gsErr) {
|
|
155
|
+
return gsErr
|
|
156
|
+
}
|
|
157
|
+
if gsErr == nil {
|
|
158
|
+
c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
143
163
|
// Skip packages that failed to load
|
|
144
164
|
if len(pkg.Errors) > 0 {
|
|
145
165
|
c.le.WithError(pkg.Errors[0]).Warnf("Skipping package %s due to errors", pkg.PkgPath)
|
|
@@ -328,8 +348,8 @@ func NewFileCompiler(
|
|
|
328
348
|
// top-level declarations in the Go file.
|
|
329
349
|
func (c *FileCompiler) Compile(ctx context.Context) error {
|
|
330
350
|
f := c.ast
|
|
331
|
-
|
|
332
351
|
pkgPath := c.pkg.PkgPath
|
|
352
|
+
|
|
333
353
|
outputFilePath := TranslateGoFilePathToTypescriptFilePath(pkgPath, filepath.Base(c.fullPath))
|
|
334
354
|
outputFilePathAbs := filepath.Join(c.compilerConfig.OutputPathRoot, outputFilePath)
|
|
335
355
|
|
|
@@ -349,7 +369,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
|
|
|
349
369
|
goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis)
|
|
350
370
|
|
|
351
371
|
// Add import for the goscript runtime using namespace import and alias
|
|
352
|
-
c.codeWriter.
|
|
372
|
+
c.codeWriter.WriteLinef("import * as $ from %q;", "@goscript/builtin/builtin.js")
|
|
353
373
|
c.codeWriter.WriteLine("") // Add a newline after the import
|
|
354
374
|
|
|
355
375
|
if err := goWriter.WriteDecls(f.Decls); err != nil {
|
|
@@ -368,7 +388,7 @@ type GoToTSCompiler struct {
|
|
|
368
388
|
|
|
369
389
|
pkg *packages.Package
|
|
370
390
|
|
|
371
|
-
analysis *Analysis
|
|
391
|
+
analysis *Analysis
|
|
372
392
|
}
|
|
373
393
|
|
|
374
394
|
// It initializes the compiler with a `TSCodeWriter` for output,
|
|
@@ -437,28 +457,31 @@ func (c *GoToTSCompiler) WriteCaseClause(exp *ast.CaseClause) error {
|
|
|
437
457
|
c.tsw.WriteLine("")
|
|
438
458
|
} else {
|
|
439
459
|
// Case with expressions
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
460
|
+
// For Go's `case expr1, expr2:`, we translate to:
|
|
461
|
+
// case expr1:
|
|
462
|
+
// case expr2:
|
|
463
|
+
// ... body ...
|
|
464
|
+
// break
|
|
465
|
+
for _, caseExpr := range exp.List {
|
|
466
|
+
c.tsw.WriteLiterally("case ")
|
|
467
|
+
if err := c.WriteValueExpr(caseExpr); err != nil {
|
|
446
468
|
return fmt.Errorf("failed to write case clause expression: %w", err)
|
|
447
469
|
}
|
|
470
|
+
c.tsw.WriteLiterally(":")
|
|
471
|
+
c.tsw.WriteLine("")
|
|
448
472
|
}
|
|
449
|
-
c.tsw.WriteLiterally(":")
|
|
450
|
-
c.tsw.WriteLine("")
|
|
451
473
|
}
|
|
452
474
|
|
|
475
|
+
// The body is written once, after all case labels for this clause.
|
|
476
|
+
// Indentation for the body starts here.
|
|
453
477
|
c.tsw.Indent(1)
|
|
454
|
-
// Write the body of the case clause
|
|
455
478
|
for _, stmt := range exp.Body {
|
|
456
479
|
if err := c.WriteStmt(stmt); err != nil {
|
|
457
480
|
return fmt.Errorf("failed to write statement in case clause body: %w", err)
|
|
458
481
|
}
|
|
459
482
|
}
|
|
460
|
-
// Add break statement (Go's switch has implicit breaks)
|
|
461
|
-
c.tsw.WriteLine("break")
|
|
483
|
+
// Add break statement (Go's switch has implicit breaks, TS needs explicit break)
|
|
484
|
+
c.tsw.WriteLine("break")
|
|
462
485
|
c.tsw.Indent(-1)
|
|
463
486
|
return nil
|
|
464
487
|
}
|
|
@@ -527,11 +550,11 @@ func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *as
|
|
|
527
550
|
} else {
|
|
528
551
|
// If both are blank, just await the call and return
|
|
529
552
|
if okIsBlank {
|
|
530
|
-
c.tsw.WriteLiterally("await ")
|
|
553
|
+
c.tsw.WriteLiterally("await $.chanRecvWithOk(")
|
|
531
554
|
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
532
555
|
return fmt.Errorf("failed to write channel expression in receive: %w", err)
|
|
533
556
|
}
|
|
534
|
-
c.tsw.WriteLiterally("
|
|
557
|
+
c.tsw.WriteLiterally(")")
|
|
535
558
|
c.tsw.WriteLine("")
|
|
536
559
|
return nil // Nothing to assign
|
|
537
560
|
}
|
|
@@ -549,11 +572,11 @@ func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *as
|
|
|
549
572
|
// Write the destructuring assignment/declaration
|
|
550
573
|
c.tsw.WriteLiterally(keyword) // "const " or ""
|
|
551
574
|
c.tsw.WriteLiterally(destructuringPattern)
|
|
552
|
-
c.tsw.WriteLiterally(" = await ")
|
|
575
|
+
c.tsw.WriteLiterally(" = await $.chanRecvWithOk(")
|
|
553
576
|
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
554
577
|
return fmt.Errorf("failed to write channel expression in receive: %w", err)
|
|
555
578
|
}
|
|
556
|
-
c.tsw.WriteLiterally("
|
|
579
|
+
c.tsw.WriteLiterally(")")
|
|
557
580
|
c.tsw.WriteLine("")
|
|
558
581
|
|
|
559
582
|
return nil
|
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
"path/filepath"
|
|
7
7
|
"runtime"
|
|
8
8
|
"slices"
|
|
9
|
+
"strings"
|
|
9
10
|
"sync"
|
|
10
11
|
"sync/atomic"
|
|
11
12
|
"testing"
|
|
@@ -38,7 +39,7 @@ func TestCompliance(t *testing.T) {
|
|
|
38
39
|
testPath := filepath.Join(testsDir, dir.Name())
|
|
39
40
|
goFiles, err := filepath.Glob(filepath.Join(testPath, "*.go"))
|
|
40
41
|
if err != nil || len(goFiles) == 0 {
|
|
41
|
-
t.Errorf("no .go files found in %s", testPath)
|
|
42
|
+
// t.Errorf("no .go files found in %s", testPath)
|
|
42
43
|
continue
|
|
43
44
|
}
|
|
44
45
|
testPaths = append(testPaths, testPath)
|
|
@@ -97,15 +98,42 @@ func TestCompliance(t *testing.T) {
|
|
|
97
98
|
t.SkipNow()
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
// Get parent module path for the global typecheck
|
|
102
|
+
parentModPath, err := getParentGoModulePath()
|
|
103
|
+
if err != nil {
|
|
104
|
+
t.Fatalf("Failed to determine parent Go module path: %v", err)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Create global typecheck tsconfig
|
|
108
|
+
tsconfigPath := compliance.WriteGlobalTypeCheckConfig(t, parentModPath, workspaceDir)
|
|
109
|
+
|
|
110
|
+
// Run TypeScript type checking
|
|
111
|
+
typecheckDir := filepath.Dir(tsconfigPath)
|
|
112
|
+
cmd := exec.Command("tsc", "--project", filepath.Base(tsconfigPath))
|
|
113
|
+
cmd.Dir = typecheckDir
|
|
114
|
+
|
|
115
|
+
// Set up PATH to include node_modules/.bin
|
|
116
|
+
nodeBinDir := filepath.Join(workspaceDir, "node_modules", ".bin")
|
|
117
|
+
currentPath := os.Getenv("PATH")
|
|
118
|
+
newPath := nodeBinDir + string(os.PathListSeparator) + currentPath
|
|
119
|
+
cmd.Env = append(os.Environ(), "PATH="+newPath)
|
|
105
120
|
|
|
106
|
-
err
|
|
121
|
+
output, err := cmd.CombinedOutput()
|
|
107
122
|
if err != nil {
|
|
108
|
-
t.Errorf("
|
|
123
|
+
t.Errorf("Global TypeScript type checking failed: %v\noutput:\n%s", err, string(output))
|
|
124
|
+
} else {
|
|
125
|
+
t.Logf("Global TypeScript type checking passed")
|
|
109
126
|
}
|
|
110
127
|
})
|
|
111
128
|
}
|
|
129
|
+
|
|
130
|
+
// getParentGoModulePath is a helper function to get the parent Go module path
|
|
131
|
+
// This is similar to the one in compliance.go but simplified for use in tests
|
|
132
|
+
func getParentGoModulePath() (string, error) {
|
|
133
|
+
cmd := exec.Command("go", "list", "-m")
|
|
134
|
+
output, err := cmd.Output()
|
|
135
|
+
if err != nil {
|
|
136
|
+
return "", err
|
|
137
|
+
}
|
|
138
|
+
return strings.TrimSpace(string(output)), nil
|
|
139
|
+
}
|