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.
Files changed (43) hide show
  1. package/compiler/analysis.go +2 -2
  2. package/compiler/assignment.go +26 -0
  3. package/compiler/builtin_test.go +2 -0
  4. package/compiler/compiler.go +12 -2
  5. package/compiler/compiler_test.go +0 -53
  6. package/compiler/expr-call.go +229 -2
  7. package/compiler/expr.go +66 -1
  8. package/compiler/lit.go +1 -1
  9. package/compiler/spec.go +6 -0
  10. package/compiler/stmt-assign.go +106 -90
  11. package/compiler/stmt-for.go +78 -1
  12. package/compiler/stmt-range.go +333 -461
  13. package/compiler/stmt.go +20 -0
  14. package/compiler/type.go +11 -8
  15. package/dist/gs/builtin/builtin.d.ts +7 -0
  16. package/dist/gs/builtin/builtin.js +30 -0
  17. package/dist/gs/builtin/builtin.js.map +1 -1
  18. package/dist/gs/builtin/map.d.ts +4 -4
  19. package/dist/gs/builtin/map.js +12 -6
  20. package/dist/gs/builtin/map.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +7 -7
  22. package/dist/gs/builtin/slice.js +19 -9
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/maps/index.d.ts +2 -0
  25. package/dist/gs/maps/index.js +3 -0
  26. package/dist/gs/maps/index.js.map +1 -0
  27. package/dist/gs/maps/iter.gs.d.ts +7 -0
  28. package/dist/gs/maps/iter.gs.js +65 -0
  29. package/dist/gs/maps/iter.gs.js.map +1 -0
  30. package/dist/gs/maps/maps.gs.d.ts +7 -0
  31. package/dist/gs/maps/maps.gs.js +79 -0
  32. package/dist/gs/maps/maps.gs.js.map +1 -0
  33. package/dist/gs/slices/slices.d.ts +6 -0
  34. package/dist/gs/slices/slices.js +8 -0
  35. package/dist/gs/slices/slices.js.map +1 -1
  36. package/gs/builtin/builtin.ts +38 -0
  37. package/gs/builtin/map.ts +10 -9
  38. package/gs/builtin/slice.ts +23 -11
  39. package/gs/maps/index.ts +2 -0
  40. package/gs/maps/iter.gs.ts +71 -0
  41. package/gs/maps/maps.gs.ts +87 -0
  42. package/gs/slices/slices.ts +9 -0
  43. package/package.json +1 -1
@@ -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 path as the key
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
  }
@@ -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
  }
@@ -1,3 +1,5 @@
1
+ //go:build test_emit_builtin
2
+
1
3
  package compiler
2
4
 
3
5
  import (
@@ -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
- // and prefixes it with an underscore if it is. This prevents compilation errors
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
- }
@@ -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 funType := c.pkg.TypesInfo.TypeOf(expFun); funType != nil {
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, false); err != nil {
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(";")