goscript 0.0.32 → 0.0.34
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 +2 -2
- package/compiler/assignment.go +26 -0
- package/compiler/builtin_test.go +2 -0
- package/compiler/compiler.go +12 -2
- package/compiler/compiler_test.go +0 -53
- package/compiler/expr-call.go +229 -2
- package/compiler/expr.go +66 -1
- package/compiler/lit.go +1 -1
- package/compiler/spec.go +6 -0
- package/compiler/stmt-assign.go +106 -90
- package/compiler/stmt-for.go +78 -1
- package/compiler/stmt-range.go +333 -461
- package/compiler/stmt.go +20 -0
- package/compiler/type.go +11 -8
- package/dist/gs/builtin/builtin.d.ts +7 -0
- package/dist/gs/builtin/builtin.js +30 -0
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/map.d.ts +4 -4
- package/dist/gs/builtin/map.js +12 -6
- package/dist/gs/builtin/map.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +7 -7
- package/dist/gs/builtin/slice.js +19 -9
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/maps/index.d.ts +2 -0
- package/dist/gs/maps/index.js +3 -0
- package/dist/gs/maps/index.js.map +1 -0
- package/dist/gs/maps/iter.gs.d.ts +7 -0
- package/dist/gs/maps/iter.gs.js +65 -0
- package/dist/gs/maps/iter.gs.js.map +1 -0
- package/dist/gs/maps/maps.gs.d.ts +7 -0
- package/dist/gs/maps/maps.gs.js +79 -0
- package/dist/gs/maps/maps.gs.js.map +1 -0
- package/dist/gs/slices/slices.d.ts +6 -0
- package/dist/gs/slices/slices.js +8 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/gs/builtin/builtin.ts +38 -0
- package/gs/builtin/map.ts +10 -9
- package/gs/builtin/slice.ts +23 -11
- package/gs/maps/index.ts +2 -0
- package/gs/maps/iter.gs.ts +71 -0
- package/gs/maps/maps.gs.ts +87 -0
- package/gs/slices/slices.ts +9 -0
- package/package.json +1 -1
package/compiler/analysis.go
CHANGED
|
@@ -864,8 +864,8 @@ func AnalyzeFile(file *ast.File, pkg *packages.Package, analysis *Analysis, cmap
|
|
|
864
864
|
importVars: make(map[string]struct{}),
|
|
865
865
|
}
|
|
866
866
|
|
|
867
|
-
// Use the import name or
|
|
868
|
-
key := path
|
|
867
|
+
// Use the import name or package name as the key
|
|
868
|
+
key := packageNameFromGoPath(path)
|
|
869
869
|
if name != "" {
|
|
870
870
|
key = name
|
|
871
871
|
}
|
package/compiler/assignment.go
CHANGED
|
@@ -197,6 +197,7 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
|
|
|
197
197
|
currentIsMapIndex := false
|
|
198
198
|
if indexExpr, ok := l.(*ast.IndexExpr); ok {
|
|
199
199
|
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
200
|
+
// Check if it's a concrete map type
|
|
200
201
|
if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
201
202
|
currentIsMapIndex = true
|
|
202
203
|
if i == 0 {
|
|
@@ -213,6 +214,31 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
|
|
|
213
214
|
}
|
|
214
215
|
c.tsw.WriteLiterally(", ")
|
|
215
216
|
// Value will be added after operator and RHS
|
|
217
|
+
} else if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
|
|
218
|
+
// Check if the type parameter is constrained to be a map type
|
|
219
|
+
constraint := typeParam.Constraint()
|
|
220
|
+
if constraint != nil {
|
|
221
|
+
underlying := constraint.Underlying()
|
|
222
|
+
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
223
|
+
if hasMapConstraint(iface) {
|
|
224
|
+
currentIsMapIndex = true
|
|
225
|
+
if i == 0 {
|
|
226
|
+
isMapIndexLHS = true
|
|
227
|
+
}
|
|
228
|
+
// Use mapSet helper for type parameter constrained to map
|
|
229
|
+
c.tsw.WriteLiterally("$.mapSet(")
|
|
230
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
231
|
+
return err
|
|
232
|
+
}
|
|
233
|
+
c.tsw.WriteLiterally(", ")
|
|
234
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
235
|
+
return err
|
|
236
|
+
}
|
|
237
|
+
c.tsw.WriteLiterally(", ")
|
|
238
|
+
// Value will be added after operator and RHS
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
216
242
|
}
|
|
217
243
|
}
|
|
218
244
|
}
|
package/compiler/builtin_test.go
CHANGED
package/compiler/compiler.go
CHANGED
|
@@ -892,14 +892,24 @@ func (c *GoToTSCompiler) WriteDoc(doc *ast.CommentGroup) {
|
|
|
892
892
|
}
|
|
893
893
|
|
|
894
894
|
// sanitizeIdentifier checks if an identifier is a JavaScript/TypeScript reserved word
|
|
895
|
-
//
|
|
896
|
-
// when Go identifiers conflict with JS/TS keywords.
|
|
895
|
+
// or conflicts with built-in types, and transforms it if needed. This prevents
|
|
896
|
+
// compilation errors when Go identifiers conflict with JS/TS keywords or built-ins.
|
|
897
897
|
func (c *GoToTSCompiler) sanitizeIdentifier(name string) string {
|
|
898
898
|
// Don't sanitize boolean literals - they are valid in both Go and JS/TS
|
|
899
899
|
if name == "true" || name == "false" {
|
|
900
900
|
return name
|
|
901
901
|
}
|
|
902
902
|
|
|
903
|
+
// Handle TypeScript built-in types that conflict with Go type parameter names
|
|
904
|
+
builtinTypes := map[string]string{
|
|
905
|
+
"Map": "MapType",
|
|
906
|
+
// Add other built-in types as needed
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if replacement, exists := builtinTypes[name]; exists {
|
|
910
|
+
return replacement
|
|
911
|
+
}
|
|
912
|
+
|
|
903
913
|
// List of JavaScript/TypeScript reserved words that could conflict
|
|
904
914
|
reservedWords := map[string]bool{
|
|
905
915
|
"abstract": true,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package compiler_test
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
4
|
"os"
|
|
6
5
|
"os/exec"
|
|
7
6
|
"path/filepath"
|
|
@@ -12,9 +11,7 @@ import (
|
|
|
12
11
|
"sync/atomic"
|
|
13
12
|
"testing"
|
|
14
13
|
|
|
15
|
-
"github.com/aperturerobotics/goscript/compiler"
|
|
16
14
|
"github.com/aperturerobotics/goscript/compliance"
|
|
17
|
-
"github.com/sirupsen/logrus"
|
|
18
15
|
)
|
|
19
16
|
|
|
20
17
|
// NOTE: this is here instead of compliance/compliance_test.go so coverage ends up in this package.
|
|
@@ -140,53 +137,3 @@ func getParentGoModulePath() (string, error) {
|
|
|
140
137
|
}
|
|
141
138
|
return strings.TrimSpace(string(output)), nil
|
|
142
139
|
}
|
|
143
|
-
|
|
144
|
-
func TestUnsafePackageCompilation(t *testing.T) {
|
|
145
|
-
// Create a temporary directory for the test output
|
|
146
|
-
tempDir, err := os.MkdirTemp("", "goscript-test-unsafe")
|
|
147
|
-
if err != nil {
|
|
148
|
-
t.Fatalf("Failed to create temp dir: %v", err)
|
|
149
|
-
}
|
|
150
|
-
defer os.RemoveAll(tempDir)
|
|
151
|
-
|
|
152
|
-
// Setup logger
|
|
153
|
-
log := logrus.New()
|
|
154
|
-
log.SetLevel(logrus.DebugLevel)
|
|
155
|
-
le := logrus.NewEntry(log)
|
|
156
|
-
|
|
157
|
-
// Test with AllDependencies=true and DisableEmitBuiltin=false to ensure handwritten packages are copied
|
|
158
|
-
config := &compiler.Config{
|
|
159
|
-
OutputPath: tempDir,
|
|
160
|
-
AllDependencies: true,
|
|
161
|
-
DisableEmitBuiltin: false, // This ensures handwritten packages are copied to output
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
comp, err := compiler.NewCompiler(config, le, nil)
|
|
165
|
-
if err != nil {
|
|
166
|
-
t.Fatalf("Failed to create compiler: %v", err)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Try to compile a package that has dependencies that import unsafe
|
|
170
|
-
// We'll use "sync/atomic" which imports unsafe but doesn't have a handwritten equivalent
|
|
171
|
-
result, err := comp.CompilePackages(context.Background(), "sync/atomic")
|
|
172
|
-
// This should now succeed since we have a handwritten unsafe package
|
|
173
|
-
if err != nil {
|
|
174
|
-
t.Fatalf("Expected compilation to succeed with handwritten unsafe package, but it failed: %v", err)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Verify that the unsafe package was copied (not compiled) since it has a handwritten equivalent
|
|
178
|
-
if !slices.Contains(result.CopiedPackages, "unsafe") {
|
|
179
|
-
t.Errorf("Expected unsafe package to be in CopiedPackages, but it wasn't. CopiedPackages: %v", result.CopiedPackages)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Verify that sync/atomic was compiled
|
|
183
|
-
if !slices.Contains(result.CompiledPackages, "sync/atomic") {
|
|
184
|
-
t.Errorf("Expected sync/atomic package to be in CompiledPackages, but it wasn't. CompiledPackages: %v", result.CompiledPackages)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Check that the unsafe package directory exists in the output
|
|
188
|
-
unsafePath := filepath.Join(tempDir, "@goscript/unsafe")
|
|
189
|
-
if _, err := os.Stat(unsafePath); os.IsNotExist(err) {
|
|
190
|
-
t.Errorf("unsafe package directory was not created at %s", unsafePath)
|
|
191
|
-
}
|
|
192
|
-
}
|
package/compiler/expr-call.go
CHANGED
|
@@ -437,6 +437,222 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
|
+
// Handle instantiated generic types: make(GenericType[TypeArg], ...)
|
|
441
|
+
// This handles cases like: make(Ints[int64]) where Ints[T] is a generic type
|
|
442
|
+
if indexExpr, ok := exp.Args[0].(*ast.IndexExpr); ok {
|
|
443
|
+
// Get the type information for the instantiated generic type
|
|
444
|
+
if typ := c.pkg.TypesInfo.TypeOf(indexExpr); typ != nil {
|
|
445
|
+
// Check the underlying type of the instantiated generic type
|
|
446
|
+
underlying := typ.Underlying()
|
|
447
|
+
|
|
448
|
+
// Handle instantiated generic map types: make(GenericMap[K, V])
|
|
449
|
+
if mapType, isMap := underlying.(*types.Map); isMap {
|
|
450
|
+
c.tsw.WriteLiterally("$.makeMap<")
|
|
451
|
+
c.WriteGoType(mapType.Key(), GoTypeContextGeneral) // Write the key type
|
|
452
|
+
c.tsw.WriteLiterally(", ")
|
|
453
|
+
c.WriteGoType(mapType.Elem(), GoTypeContextGeneral) // Write the value type
|
|
454
|
+
c.tsw.WriteLiterally(">()")
|
|
455
|
+
return nil // Handled make for instantiated generic map type
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Handle instantiated generic slice types: make(GenericSlice[T], len, cap)
|
|
459
|
+
if sliceType, isSlice := underlying.(*types.Slice); isSlice {
|
|
460
|
+
goElemType := sliceType.Elem()
|
|
461
|
+
|
|
462
|
+
// Check if it's an instantiated generic type with []byte underlying type
|
|
463
|
+
if basicElem, isBasic := goElemType.(*types.Basic); isBasic && basicElem.Kind() == types.Uint8 {
|
|
464
|
+
c.tsw.WriteLiterally("new Uint8Array(")
|
|
465
|
+
if len(exp.Args) >= 2 {
|
|
466
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Length
|
|
467
|
+
return err
|
|
468
|
+
}
|
|
469
|
+
// Capacity argument for make([]byte, len, cap) is ignored for new Uint8Array(len)
|
|
470
|
+
} else {
|
|
471
|
+
// If no length is provided, default to 0
|
|
472
|
+
c.tsw.WriteLiterally("0")
|
|
473
|
+
}
|
|
474
|
+
c.tsw.WriteLiterally(")")
|
|
475
|
+
return nil // Handled make for instantiated generic []byte type
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Handle other instantiated generic slice types
|
|
479
|
+
c.tsw.WriteLiterally("$.makeSlice<")
|
|
480
|
+
c.WriteGoType(goElemType, GoTypeContextGeneral) // Write the element type
|
|
481
|
+
c.tsw.WriteLiterally(">(")
|
|
482
|
+
|
|
483
|
+
if len(exp.Args) >= 2 {
|
|
484
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Length
|
|
485
|
+
return err
|
|
486
|
+
}
|
|
487
|
+
if len(exp.Args) == 3 {
|
|
488
|
+
c.tsw.WriteLiterally(", ")
|
|
489
|
+
if err := c.WriteValueExpr(exp.Args[2]); err != nil { // Capacity
|
|
490
|
+
return err
|
|
491
|
+
}
|
|
492
|
+
} else if len(exp.Args) > 3 {
|
|
493
|
+
return errors.New("makeSlice expects 2 or 3 arguments")
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
// If no length is provided, default to 0
|
|
497
|
+
c.tsw.WriteLiterally("0")
|
|
498
|
+
}
|
|
499
|
+
c.tsw.WriteLiterally(")")
|
|
500
|
+
return nil // Handled make for instantiated generic slice type
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Handle instantiated generic channel types: make(GenericChannel[T], bufferSize)
|
|
504
|
+
if chanType, isChan := underlying.(*types.Chan); isChan {
|
|
505
|
+
c.tsw.WriteLiterally("$.makeChannel<")
|
|
506
|
+
c.WriteGoType(chanType.Elem(), GoTypeContextGeneral)
|
|
507
|
+
c.tsw.WriteLiterally(">(")
|
|
508
|
+
|
|
509
|
+
// If buffer size is provided, add it
|
|
510
|
+
if len(exp.Args) >= 2 {
|
|
511
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil {
|
|
512
|
+
return fmt.Errorf("failed to write buffer size in makeChannel: %w", err)
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
// Default to 0 (unbuffered channel)
|
|
516
|
+
c.tsw.WriteLiterally("0")
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
c.tsw.WriteLiterally(", ") // Add comma for zero value argument
|
|
520
|
+
|
|
521
|
+
// Write the zero value for the channel's element type
|
|
522
|
+
if chanType.Elem().String() == "struct{}" {
|
|
523
|
+
c.tsw.WriteLiterally("{}")
|
|
524
|
+
} else {
|
|
525
|
+
c.WriteZeroValueForType(chanType.Elem())
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Add direction parameter
|
|
529
|
+
c.tsw.WriteLiterally(", ")
|
|
530
|
+
|
|
531
|
+
// Determine channel direction
|
|
532
|
+
switch chanType.Dir() {
|
|
533
|
+
case types.SendRecv:
|
|
534
|
+
c.tsw.WriteLiterally("'both'")
|
|
535
|
+
case types.SendOnly:
|
|
536
|
+
c.tsw.WriteLiterally("'send'")
|
|
537
|
+
case types.RecvOnly:
|
|
538
|
+
c.tsw.WriteLiterally("'receive'")
|
|
539
|
+
default:
|
|
540
|
+
c.tsw.WriteLiterally("'both'") // Default to bidirectional
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
c.tsw.WriteLiterally(")")
|
|
544
|
+
return nil // Handled make for instantiated generic channel type
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
// Handle selector expressions: make(pkg.TypeName, ...)
|
|
549
|
+
// This handles cases like: make(fstest.MapFS) where fstest.MapFS is map[string]*MapFile
|
|
550
|
+
if selectorExpr, ok := exp.Args[0].(*ast.SelectorExpr); ok {
|
|
551
|
+
// Get the type information for the selector expression
|
|
552
|
+
if typ := c.pkg.TypesInfo.TypeOf(selectorExpr); typ != nil {
|
|
553
|
+
// Check the underlying type of the selector expression
|
|
554
|
+
underlying := typ.Underlying()
|
|
555
|
+
|
|
556
|
+
// Handle selector expression map types: make(pkg.MapType)
|
|
557
|
+
if mapType, isMap := underlying.(*types.Map); isMap {
|
|
558
|
+
c.tsw.WriteLiterally("$.makeMap<")
|
|
559
|
+
c.WriteGoType(mapType.Key(), GoTypeContextGeneral) // Write the key type
|
|
560
|
+
c.tsw.WriteLiterally(", ")
|
|
561
|
+
c.WriteGoType(mapType.Elem(), GoTypeContextGeneral) // Write the value type
|
|
562
|
+
c.tsw.WriteLiterally(">()")
|
|
563
|
+
return nil // Handled make for selector expression map type
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Handle selector expression slice types: make(pkg.SliceType, len, cap)
|
|
567
|
+
if sliceType, isSlice := underlying.(*types.Slice); isSlice {
|
|
568
|
+
goElemType := sliceType.Elem()
|
|
569
|
+
|
|
570
|
+
// Check if it's a selector expression with []byte underlying type
|
|
571
|
+
if basicElem, isBasic := goElemType.(*types.Basic); isBasic && basicElem.Kind() == types.Uint8 {
|
|
572
|
+
c.tsw.WriteLiterally("new Uint8Array(")
|
|
573
|
+
if len(exp.Args) >= 2 {
|
|
574
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Length
|
|
575
|
+
return err
|
|
576
|
+
}
|
|
577
|
+
// Capacity argument for make([]byte, len, cap) is ignored for new Uint8Array(len)
|
|
578
|
+
} else {
|
|
579
|
+
// If no length is provided, default to 0
|
|
580
|
+
c.tsw.WriteLiterally("0")
|
|
581
|
+
}
|
|
582
|
+
c.tsw.WriteLiterally(")")
|
|
583
|
+
return nil // Handled make for selector expression []byte type
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Handle other selector expression slice types
|
|
587
|
+
c.tsw.WriteLiterally("$.makeSlice<")
|
|
588
|
+
c.WriteGoType(goElemType, GoTypeContextGeneral) // Write the element type
|
|
589
|
+
c.tsw.WriteLiterally(">(")
|
|
590
|
+
|
|
591
|
+
if len(exp.Args) >= 2 {
|
|
592
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Length
|
|
593
|
+
return err
|
|
594
|
+
}
|
|
595
|
+
if len(exp.Args) == 3 {
|
|
596
|
+
c.tsw.WriteLiterally(", ")
|
|
597
|
+
if err := c.WriteValueExpr(exp.Args[2]); err != nil { // Capacity
|
|
598
|
+
return err
|
|
599
|
+
}
|
|
600
|
+
} else if len(exp.Args) > 3 {
|
|
601
|
+
return errors.New("makeSlice expects 2 or 3 arguments")
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
// If no length is provided, default to 0
|
|
605
|
+
c.tsw.WriteLiterally("0")
|
|
606
|
+
}
|
|
607
|
+
c.tsw.WriteLiterally(")")
|
|
608
|
+
return nil // Handled make for selector expression slice type
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Handle selector expression channel types: make(pkg.ChannelType, bufferSize)
|
|
612
|
+
if chanType, isChan := underlying.(*types.Chan); isChan {
|
|
613
|
+
c.tsw.WriteLiterally("$.makeChannel<")
|
|
614
|
+
c.WriteGoType(chanType.Elem(), GoTypeContextGeneral)
|
|
615
|
+
c.tsw.WriteLiterally(">(")
|
|
616
|
+
|
|
617
|
+
// If buffer size is provided, add it
|
|
618
|
+
if len(exp.Args) >= 2 {
|
|
619
|
+
if err := c.WriteValueExpr(exp.Args[1]); err != nil {
|
|
620
|
+
return fmt.Errorf("failed to write buffer size in makeChannel: %w", err)
|
|
621
|
+
}
|
|
622
|
+
} else {
|
|
623
|
+
// Default to 0 (unbuffered channel)
|
|
624
|
+
c.tsw.WriteLiterally("0")
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
c.tsw.WriteLiterally(", ") // Add comma for zero value argument
|
|
628
|
+
|
|
629
|
+
// Write the zero value for the channel's element type
|
|
630
|
+
if chanType.Elem().String() == "struct{}" {
|
|
631
|
+
c.tsw.WriteLiterally("{}")
|
|
632
|
+
} else {
|
|
633
|
+
c.WriteZeroValueForType(chanType.Elem())
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Add direction parameter
|
|
637
|
+
c.tsw.WriteLiterally(", ")
|
|
638
|
+
|
|
639
|
+
// Determine channel direction
|
|
640
|
+
switch chanType.Dir() {
|
|
641
|
+
case types.SendRecv:
|
|
642
|
+
c.tsw.WriteLiterally("'both'")
|
|
643
|
+
case types.SendOnly:
|
|
644
|
+
c.tsw.WriteLiterally("'send'")
|
|
645
|
+
case types.RecvOnly:
|
|
646
|
+
c.tsw.WriteLiterally("'receive'")
|
|
647
|
+
default:
|
|
648
|
+
c.tsw.WriteLiterally("'both'") // Default to bidirectional
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
c.tsw.WriteLiterally(")")
|
|
652
|
+
return nil // Handled make for selector expression channel type
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
440
656
|
// Fallthrough for unhandled make calls (e.g., channels)
|
|
441
657
|
return errors.New("unhandled make call")
|
|
442
658
|
case "string":
|
|
@@ -771,12 +987,16 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
771
987
|
}
|
|
772
988
|
c.tsw.WriteLiterally(")")
|
|
773
989
|
} else {
|
|
774
|
-
// Not an identifier (e.g., method call on a value)
|
|
990
|
+
// Not an identifier (e.g., method call on a value, function call result)
|
|
775
991
|
if err := c.WriteValueExpr(expFun); err != nil {
|
|
776
992
|
return fmt.Errorf("failed to write method expression in call: %w", err)
|
|
777
993
|
}
|
|
778
994
|
|
|
779
|
-
if
|
|
995
|
+
// Check if this is a function call that returns a function (e.g., simpleIterator(m)())
|
|
996
|
+
// Add non-null assertion since the returned function could be null
|
|
997
|
+
if _, isCallExpr := expFun.(*ast.CallExpr); isCallExpr {
|
|
998
|
+
c.tsw.WriteLiterally("!")
|
|
999
|
+
} else if funType := c.pkg.TypesInfo.TypeOf(expFun); funType != nil {
|
|
780
1000
|
if _, ok := funType.Underlying().(*types.Signature); ok {
|
|
781
1001
|
// Check if this is a function parameter identifier that needs not-null assertion
|
|
782
1002
|
if ident, isIdent := expFun.(*ast.Ident); isIdent {
|
|
@@ -791,6 +1011,13 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
|
|
|
791
1011
|
} else if _, isNamed := funType.(*types.Named); isNamed {
|
|
792
1012
|
c.tsw.WriteLiterally("!")
|
|
793
1013
|
}
|
|
1014
|
+
} else {
|
|
1015
|
+
// Check if the function type is nullable (e.g., func(...) | null)
|
|
1016
|
+
// This handles cases where a function call returns a nullable function
|
|
1017
|
+
funTypeStr := funType.String()
|
|
1018
|
+
if strings.Contains(funTypeStr, "| null") || strings.Contains(funTypeStr, "null |") {
|
|
1019
|
+
c.tsw.WriteLiterally("!")
|
|
1020
|
+
}
|
|
794
1021
|
}
|
|
795
1022
|
}
|
|
796
1023
|
}
|
package/compiler/expr.go
CHANGED
|
@@ -52,7 +52,7 @@ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
|
52
52
|
|
|
53
53
|
// Generate the zero value as the default value for mapGet
|
|
54
54
|
c.WriteZeroValueForType(mapType.Elem())
|
|
55
|
-
c.tsw.WriteLiterally(")")
|
|
55
|
+
c.tsw.WriteLiterally(")[0]") // Extract the value from the tuple
|
|
56
56
|
return nil
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -77,6 +77,31 @@ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
|
77
77
|
if constraint != nil {
|
|
78
78
|
underlying := constraint.Underlying()
|
|
79
79
|
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
80
|
+
// Check if this is a map constraint (like ~map[K]V)
|
|
81
|
+
if hasMapConstraint(iface) {
|
|
82
|
+
// This is a map type parameter, use map access
|
|
83
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
84
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
85
|
+
return err
|
|
86
|
+
}
|
|
87
|
+
c.tsw.WriteLiterally(", ")
|
|
88
|
+
if err := c.WriteValueExpr(exp.Index); err != nil {
|
|
89
|
+
return err
|
|
90
|
+
}
|
|
91
|
+
c.tsw.WriteLiterally(", ")
|
|
92
|
+
|
|
93
|
+
// Generate the zero value as the default value for mapGet
|
|
94
|
+
// For type parameters, we need to get the value type from the constraint
|
|
95
|
+
mapValueType := getMapValueTypeFromConstraint(iface)
|
|
96
|
+
if mapValueType != nil {
|
|
97
|
+
c.WriteZeroValueForType(mapValueType)
|
|
98
|
+
} else {
|
|
99
|
+
c.tsw.WriteLiterally("null")
|
|
100
|
+
}
|
|
101
|
+
c.tsw.WriteLiterally(")[0]") // Extract the value from the tuple
|
|
102
|
+
return nil
|
|
103
|
+
}
|
|
104
|
+
|
|
80
105
|
// Check if this is a mixed string/byte constraint (like string | []byte)
|
|
81
106
|
if hasMixedStringByteConstraint(iface) {
|
|
82
107
|
// For mixed constraints, use specialized function that returns number (byte value)
|
|
@@ -694,3 +719,43 @@ func (c *GoToTSCompiler) WriteKeyValueExpr(exp *ast.KeyValueExpr) error {
|
|
|
694
719
|
}
|
|
695
720
|
return nil
|
|
696
721
|
}
|
|
722
|
+
|
|
723
|
+
// hasMapConstraint checks if an interface constraint includes map types
|
|
724
|
+
// For constraints like ~map[K]V, this returns true
|
|
725
|
+
func hasMapConstraint(iface *types.Interface) bool {
|
|
726
|
+
// Check if the interface has type terms that include map types
|
|
727
|
+
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
|
728
|
+
embedded := iface.EmbeddedType(i)
|
|
729
|
+
if union, ok := embedded.(*types.Union); ok {
|
|
730
|
+
for j := 0; j < union.Len(); j++ {
|
|
731
|
+
term := union.Term(j)
|
|
732
|
+
if _, isMap := term.Type().Underlying().(*types.Map); isMap {
|
|
733
|
+
return true
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
} else if _, isMap := embedded.Underlying().(*types.Map); isMap {
|
|
737
|
+
return true
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return false
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// getMapValueTypeFromConstraint extracts the value type from a map constraint
|
|
744
|
+
// For constraints like ~map[K]V, this returns V
|
|
745
|
+
func getMapValueTypeFromConstraint(iface *types.Interface) types.Type {
|
|
746
|
+
// Check if the interface has type terms that include map types
|
|
747
|
+
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
|
748
|
+
embedded := iface.EmbeddedType(i)
|
|
749
|
+
if union, ok := embedded.(*types.Union); ok {
|
|
750
|
+
for j := 0; j < union.Len(); j++ {
|
|
751
|
+
term := union.Term(j)
|
|
752
|
+
if mapType, isMap := term.Type().Underlying().(*types.Map); isMap {
|
|
753
|
+
return mapType.Elem()
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
} else if mapType, isMap := embedded.Underlying().(*types.Map); isMap {
|
|
757
|
+
return mapType.Elem()
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return nil
|
|
761
|
+
}
|
package/compiler/lit.go
CHANGED
|
@@ -118,7 +118,7 @@ func (c *GoToTSCompiler) WriteFuncLitValue(exp *ast.FuncLit) error {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// Write function body
|
|
121
|
-
if err := c.WriteStmtBlock(exp.Body,
|
|
121
|
+
if err := c.WriteStmtBlock(exp.Body, true); err != nil {
|
|
122
122
|
return fmt.Errorf("failed to write block statement: %w", err)
|
|
123
123
|
}
|
|
124
124
|
|
package/compiler/spec.go
CHANGED
|
@@ -414,6 +414,12 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
|
|
|
414
414
|
if err := c.WriteValueExpr(a.Name); err != nil {
|
|
415
415
|
return err
|
|
416
416
|
}
|
|
417
|
+
|
|
418
|
+
// Write type parameters if present (for generics)
|
|
419
|
+
if a.TypeParams != nil {
|
|
420
|
+
c.WriteTypeParameters(a.TypeParams)
|
|
421
|
+
}
|
|
422
|
+
|
|
417
423
|
c.tsw.WriteLiterally(" = ")
|
|
418
424
|
c.WriteTypeExpr(a.Type) // The aliased type
|
|
419
425
|
c.tsw.WriteLine(";")
|