goscript 0.0.61 → 0.0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +62 -46
  2. package/compiler/analysis.go +621 -19
  3. package/compiler/analysis_test.go +3 -3
  4. package/compiler/assignment.go +100 -0
  5. package/compiler/builtin_test.go +1 -1
  6. package/compiler/compiler.go +76 -16
  7. package/compiler/compiler_test.go +9 -9
  8. package/compiler/composite-lit.go +29 -8
  9. package/compiler/decl.go +20 -11
  10. package/compiler/expr-call-async.go +26 -1
  11. package/compiler/expr-call-builtins.go +60 -4
  12. package/compiler/expr-call-type-conversion.go +37 -5
  13. package/compiler/expr-call.go +26 -6
  14. package/compiler/expr-selector.go +35 -2
  15. package/compiler/expr-type.go +12 -2
  16. package/compiler/expr.go +61 -0
  17. package/compiler/index.test.ts +3 -1
  18. package/compiler/lit.go +13 -4
  19. package/compiler/spec-struct.go +30 -8
  20. package/compiler/spec-value.go +2 -2
  21. package/compiler/spec.go +23 -4
  22. package/compiler/stmt-assign.go +124 -0
  23. package/compiler/stmt-range.go +2 -2
  24. package/compiler/stmt.go +160 -14
  25. package/compiler/type-info.go +3 -5
  26. package/compiler/type-utils.go +40 -1
  27. package/compiler/type.go +52 -14
  28. package/dist/gs/builtin/builtin.d.ts +8 -1
  29. package/dist/gs/builtin/builtin.js +26 -1
  30. package/dist/gs/builtin/builtin.js.map +1 -1
  31. package/dist/gs/builtin/errors.d.ts +1 -0
  32. package/dist/gs/builtin/errors.js +8 -0
  33. package/dist/gs/builtin/errors.js.map +1 -1
  34. package/dist/gs/builtin/slice.d.ts +5 -4
  35. package/dist/gs/builtin/slice.js +88 -51
  36. package/dist/gs/builtin/slice.js.map +1 -1
  37. package/dist/gs/builtin/type.d.ts +23 -2
  38. package/dist/gs/builtin/type.js +125 -0
  39. package/dist/gs/builtin/type.js.map +1 -1
  40. package/dist/gs/builtin/varRef.d.ts +3 -0
  41. package/dist/gs/builtin/varRef.js +6 -1
  42. package/dist/gs/builtin/varRef.js.map +1 -1
  43. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  44. package/dist/gs/bytes/reader.gs.js +1 -1
  45. package/dist/gs/bytes/reader.gs.js.map +1 -1
  46. package/dist/gs/reflect/index.d.ts +2 -2
  47. package/dist/gs/reflect/index.js +1 -1
  48. package/dist/gs/reflect/index.js.map +1 -1
  49. package/dist/gs/reflect/map.d.ts +3 -2
  50. package/dist/gs/reflect/map.js +37 -3
  51. package/dist/gs/reflect/map.js.map +1 -1
  52. package/dist/gs/reflect/type.d.ts +53 -12
  53. package/dist/gs/reflect/type.js +906 -31
  54. package/dist/gs/reflect/type.js.map +1 -1
  55. package/dist/gs/reflect/types.d.ts +11 -12
  56. package/dist/gs/reflect/types.js +26 -15
  57. package/dist/gs/reflect/types.js.map +1 -1
  58. package/dist/gs/reflect/value.d.ts +4 -4
  59. package/dist/gs/reflect/value.js +8 -2
  60. package/dist/gs/reflect/value.js.map +1 -1
  61. package/dist/gs/slices/slices.d.ts +21 -0
  62. package/dist/gs/slices/slices.js +48 -0
  63. package/dist/gs/slices/slices.js.map +1 -1
  64. package/dist/gs/strconv/atoi.gs.js +20 -2
  65. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  66. package/dist/gs/sync/atomic/type.gs.d.ts +3 -3
  67. package/dist/gs/sync/atomic/type.gs.js +13 -7
  68. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  69. package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
  70. package/dist/gs/unicode/utf8/utf8.js +10 -6
  71. package/dist/gs/unicode/utf8/utf8.js.map +1 -1
  72. package/go.mod +6 -6
  73. package/go.sum +12 -8
  74. package/gs/builtin/builtin.ts +27 -2
  75. package/gs/builtin/errors.ts +12 -0
  76. package/gs/builtin/slice.ts +126 -55
  77. package/gs/builtin/type.ts +159 -2
  78. package/gs/builtin/varRef.ts +8 -2
  79. package/gs/bytes/reader.gs.ts +2 -2
  80. package/gs/math/hypot.gs.test.ts +3 -1
  81. package/gs/math/pow10.gs.test.ts +5 -4
  82. package/gs/reflect/index.ts +3 -2
  83. package/gs/reflect/map.test.ts +7 -6
  84. package/gs/reflect/map.ts +49 -7
  85. package/gs/reflect/type.ts +1150 -57
  86. package/gs/reflect/types.ts +34 -21
  87. package/gs/reflect/value.ts +12 -6
  88. package/gs/slices/slices.ts +55 -0
  89. package/gs/strconv/atoi.gs.ts +18 -2
  90. package/gs/sync/atomic/type.gs.ts +15 -10
  91. package/gs/unicode/utf8/utf8.ts +12 -8
  92. package/package.json +23 -14
@@ -12,7 +12,7 @@ import (
12
12
  )
13
13
 
14
14
  // TestAnalysisVarRefLogic verifies that the analysis correctly identifies
15
- // which variables need variable references based on actual compliance test cases
15
+ // which variables need variable references based on actual tests test cases
16
16
  func TestAnalysisVarRefLogic(t *testing.T) {
17
17
  tests := []struct {
18
18
  name string
@@ -235,8 +235,8 @@ func main() {
235
235
 
236
236
  // TestWrapperTypeDetection verifies that the analysis correctly identifies wrapper types
237
237
  func TestWrapperTypeDetection(t *testing.T) {
238
- // Use the actual compliance test case
239
- testPath := "../compliance/tests/wrapper_type_args"
238
+ // Use the actual tests test case
239
+ testPath := "../tests/tests/wrapper_type_args"
240
240
 
241
241
  // Load the package using the packages config like the main compiler does
242
242
  cfg := &packages.Config{
@@ -148,9 +148,32 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
148
148
  }
149
149
  }
150
150
 
151
+ // Check for integer division compound assignment (i /= n)
152
+ // Go's integer division truncates, but JavaScript's /= doesn't
153
+ // We need to convert `i /= n` to `i = Math.trunc(i / n)`
154
+ isIntegerDivisionAssign := false
155
+ if tok == token.QUO_ASSIGN && len(lhs) == 1 && len(rhs) == 1 {
156
+ if lhsType := c.pkg.TypesInfo.TypeOf(lhs[0]); lhsType != nil {
157
+ if basic, ok := lhsType.Underlying().(*types.Basic); ok {
158
+ if basic.Info()&types.IsInteger != 0 {
159
+ isIntegerDivisionAssign = true
160
+ }
161
+ }
162
+ }
163
+ }
164
+
151
165
  // Only write the assignment operator for regular variables, not for map assignments handled by mapSet
152
166
  if isMapIndexLHS && len(lhs) == 1 { // Only skip operator if it's a single map assignment
153
167
  // Continue, we've already written part of the mapSet() function call
168
+ } else if isIntegerDivisionAssign {
169
+ // For integer division compound assignment, convert `i /= n` to `i = Math.trunc(i / n)`
170
+ c.tsw.WriteLiterally(" = Math.trunc(")
171
+ // Write LHS again as first operand of division
172
+ if err := c.WriteValueExpr(lhs[0]); err != nil {
173
+ return err
174
+ }
175
+ c.tsw.WriteLiterally(" / ")
176
+ // RHS will be written below, then we close the parenthesis
154
177
  } else {
155
178
  c.tsw.WriteLiterally(" ")
156
179
  if err := c.writeAssignmentOperator(tok); err != nil {
@@ -219,6 +242,15 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
219
242
  if shouldApplyClone(c.pkg, r) {
220
243
  // When cloning for value assignment, mark the result as struct value
221
244
  c.tsw.WriteLiterally("$.markAsStructValue(")
245
+
246
+ // Check if RHS is an async call - if so, wrap in parentheses so .clone() binds correctly
247
+ // Example: (await asyncFunc()).clone() instead of await asyncFunc().clone()
248
+ needsParensForAsync := false
249
+ if callExpr, isCall := r.(*ast.CallExpr); isCall && c.isCallExprAsync(callExpr) {
250
+ needsParensForAsync = true
251
+ c.tsw.WriteLiterally("(")
252
+ }
253
+
222
254
  // For other expressions, we need to handle variable referenced access differently
223
255
  if _, isIdent := r.(*ast.Ident); isIdent {
224
256
  // For identifiers, WriteValueExpr already adds .value if needed
@@ -236,6 +268,9 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
236
268
  }
237
269
  }
238
270
 
271
+ if needsParensForAsync {
272
+ c.tsw.WriteLiterally(")")
273
+ }
239
274
  c.tsw.WriteLiterally(".clone())") // Always add clone for struct values
240
275
  } else {
241
276
  // Check if this is a pointer variable assignment to an interface type
@@ -270,6 +305,11 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
270
305
  }
271
306
 
272
307
  // Non-struct case: write RHS normally
308
+ // Check if this is a primitive error type being assigned to an error interface
309
+ if c.writePrimitiveErrorWrapperForAssign(lhs, r, i) {
310
+ continue
311
+ }
312
+
273
313
  if err := c.WriteValueExpr(r); err != nil { // RHS is a non-struct value
274
314
  return err
275
315
  }
@@ -281,6 +321,11 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
281
321
  c.tsw.WriteLiterally(")")
282
322
  }
283
323
 
324
+ // Close the parenthesis for integer division compound assignment
325
+ if isIntegerDivisionAssign {
326
+ c.tsw.WriteLiterally(")")
327
+ }
328
+
284
329
  // If the LHS was a single map index, close the mapSet call
285
330
  if isMapIndexLHS && len(lhs) == 1 {
286
331
  c.tsw.WriteLiterally(")")
@@ -288,6 +333,61 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
288
333
  return nil
289
334
  }
290
335
 
336
+ // writePrimitiveErrorWrapperForAssign checks if an RHS value is a primitive type
337
+ // that implements the error interface being assigned to an error-typed LHS,
338
+ // and if so, wraps it with $.wrapPrimitiveError.
339
+ // Returns true if the wrapper was written, false otherwise.
340
+ func (c *GoToTSCompiler) writePrimitiveErrorWrapperForAssign(lhs []ast.Expr, rhs ast.Expr, rhsIndex int) bool {
341
+ // Only handle single assignments for now
342
+ if len(lhs) != 1 || rhsIndex != 0 {
343
+ return false
344
+ }
345
+
346
+ // Get the LHS type
347
+ lhsType := c.pkg.TypesInfo.TypeOf(lhs[0])
348
+ if lhsType == nil {
349
+ return false
350
+ }
351
+
352
+ // Check if the LHS type is the error interface
353
+ if iface, ok := lhsType.Underlying().(*types.Interface); !ok || iface.String() != "interface{Error() string}" {
354
+ return false
355
+ }
356
+
357
+ // Get the actual type of the RHS expression
358
+ rhsType := c.pkg.TypesInfo.TypeOf(rhs)
359
+ if rhsType == nil {
360
+ return false
361
+ }
362
+
363
+ // Check if the RHS type is a wrapper type (named type with basic underlying type)
364
+ if !c.isWrapperType(rhsType) {
365
+ return false
366
+ }
367
+
368
+ // Check if the RHS type has an Error() method
369
+ if !c.typeHasMethods(rhsType, "Error") {
370
+ return false
371
+ }
372
+
373
+ // Get the qualified type name for the Error function
374
+ typeName := c.getQualifiedTypeName(rhsType)
375
+ if typeName == "" {
376
+ return false
377
+ }
378
+
379
+ // Write: $.wrapPrimitiveError(value, TypeName_Error)
380
+ c.tsw.WriteLiterally("$.wrapPrimitiveError(")
381
+ if err := c.WriteValueExpr(rhs); err != nil {
382
+ return false
383
+ }
384
+ c.tsw.WriteLiterally(", ")
385
+ c.tsw.WriteLiterally(typeName)
386
+ c.tsw.WriteLiterally("_Error)")
387
+
388
+ return true
389
+ }
390
+
291
391
  // writeBlankIdentifierAssign handles assignment to blank identifier (_)
292
392
  func (c *GoToTSCompiler) writeBlankIdentifierAssign(rhs ast.Expr) error {
293
393
  c.tsw.WriteLiterally("/* _ = */ ")
@@ -24,7 +24,7 @@ func TestEmitBuiltinOption(t *testing.T) {
24
24
  log.SetLevel(logrus.DebugLevel)
25
25
  le := logrus.NewEntry(log)
26
26
 
27
- // Case 1: DisableEmitBuiltin = true (default behavior in compliance tests)
27
+ // Case 1: DisableEmitBuiltin = true (default behavior in tests tests)
28
28
  t.Run("DisableEmitBuiltin=true", func(t *testing.T) {
29
29
  outputDir := filepath.Join(tempDir, "disabled")
30
30
  config := &Config{
@@ -13,7 +13,6 @@ import (
13
13
  "os"
14
14
  "path/filepath"
15
15
  "slices"
16
- "sort"
17
16
  "strings"
18
17
 
19
18
  gs "github.com/aperturerobotics/goscript"
@@ -487,7 +486,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
487
486
 
488
487
  // Write exports if this file has exported symbols
489
488
  if len(valueSymbols) > 0 {
490
- sort.Strings(valueSymbols)
489
+ slices.Sort(valueSymbols)
491
490
  exportLine := fmt.Sprintf("export { %s } from \"./%s.js\"\n",
492
491
  strings.Join(valueSymbols, ", "), fileName)
493
492
  if _, err := indexFile.WriteString(exportLine); err != nil {
@@ -497,7 +496,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
497
496
 
498
497
  // Write struct exports (both as types and values)
499
498
  if len(structSymbols) > 0 {
500
- sort.Strings(structSymbols)
499
+ slices.Sort(structSymbols)
501
500
  // Export classes as values (which makes them available as both types and values in TypeScript)
502
501
  exportLine := fmt.Sprintf("export { %s } from \"./%s.js\"\n",
503
502
  strings.Join(structSymbols, ", "), fileName)
@@ -507,7 +506,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
507
506
  }
508
507
 
509
508
  if len(typeSymbols) > 0 {
510
- sort.Strings(typeSymbols)
509
+ slices.Sort(typeSymbols)
511
510
  exportLine := fmt.Sprintf("export type { %s } from \"./%s.js\"\n",
512
511
  strings.Join(typeSymbols, ", "), fileName)
513
512
  if _, err := indexFile.WriteString(exportLine); err != nil {
@@ -594,7 +593,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
594
593
  c.codeWriter = NewTSCodeWriter(of)
595
594
 
596
595
  // Pass analysis to compiler
597
- goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis)
596
+ goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis, c.fullPath)
598
597
 
599
598
  // Add import for the goscript runtime using namespace import and alias
600
599
  c.codeWriter.WriteLinef("import * as $ from %q", "@goscript/builtin/index.js")
@@ -612,7 +611,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
612
611
  for sourceFile := range imports {
613
612
  sourceFiles = append(sourceFiles, sourceFile)
614
613
  }
615
- sort.Strings(sourceFiles)
614
+ slices.Sort(sourceFiles)
616
615
 
617
616
  for _, sourceFile := range sourceFiles {
618
617
  functions := imports[sourceFile]
@@ -623,7 +622,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
623
622
  sanitizedFunctions = append(sanitizedFunctions, sanitizeIdentifier(fn))
624
623
  }
625
624
  // Sort functions for consistent output
626
- sort.Strings(sanitizedFunctions)
625
+ slices.Sort(sanitizedFunctions)
627
626
  c.codeWriter.WriteLinef("import { %s } from \"./%s.gs.js\";",
628
627
  strings.Join(sanitizedFunctions, ", "), sourceFile)
629
628
  }
@@ -637,7 +636,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
637
636
  for sourceFile := range typeImports {
638
637
  sourceFiles = append(sourceFiles, sourceFile)
639
638
  }
640
- sort.Strings(sourceFiles)
639
+ slices.Sort(sourceFiles)
641
640
 
642
641
  for _, sourceFile := range sourceFiles {
643
642
  typeImports := typeImports[sourceFile]
@@ -668,7 +667,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
668
667
  sanitizedTypes = append(sanitizedTypes, sanitizeIdentifier(typeName))
669
668
  }
670
669
  // Sort types for consistent output
671
- sort.Strings(sanitizedTypes)
670
+ slices.Sort(sanitizedTypes)
672
671
  c.codeWriter.WriteLinef("import { %s } from \"./%s.gs.js\";",
673
672
  strings.Join(sanitizedTypes, ", "), sourceFile)
674
673
  }
@@ -676,6 +675,46 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
676
675
  }
677
676
  }
678
677
 
678
+ // Generate auto-imports for variables from other files in the same package
679
+ if varImports := c.PackageAnalysis.VariableCalls[currentFileName]; varImports != nil {
680
+ // Sort source files for consistent import order
681
+ var sourceFiles []string
682
+ for sourceFile := range varImports {
683
+ sourceFiles = append(sourceFiles, sourceFile)
684
+ }
685
+ slices.Sort(sourceFiles)
686
+
687
+ for _, sourceFile := range sourceFiles {
688
+ variables := varImports[sourceFile]
689
+ if len(variables) > 0 {
690
+ // Apply sanitization to variable names
691
+ var sanitizedVariables []string
692
+ for _, varName := range variables {
693
+ sanitizedVariables = append(sanitizedVariables, sanitizeIdentifier(varName))
694
+ }
695
+ // Sort variables for consistent output
696
+ slices.Sort(sanitizedVariables)
697
+ c.codeWriter.WriteLinef("import { %s } from \"./%s.gs.js\";",
698
+ strings.Join(sanitizedVariables, ", "), sourceFile)
699
+ }
700
+ }
701
+ }
702
+
703
+ // Write synthetic imports (for promoted methods from embedded structs)
704
+ // Use per-file synthetic imports to avoid adding unused imports
705
+ if syntheticImports := c.Analysis.SyntheticImportsPerFile[c.fullPath]; syntheticImports != nil {
706
+ // Sort by package name for consistent output
707
+ var syntheticPkgNames []string
708
+ for pkgName := range syntheticImports {
709
+ syntheticPkgNames = append(syntheticPkgNames, pkgName)
710
+ }
711
+ slices.Sort(syntheticPkgNames)
712
+ for _, pkgName := range syntheticPkgNames {
713
+ imp := syntheticImports[pkgName]
714
+ c.codeWriter.WriteImport(pkgName, imp.importPath+"/index.js")
715
+ }
716
+ }
717
+
679
718
  c.codeWriter.WriteLine("") // Add a newline after imports
680
719
 
681
720
  if err := goWriter.WriteDecls(f.Decls); err != nil {
@@ -695,18 +734,27 @@ type GoToTSCompiler struct {
695
734
 
696
735
  analysis *Analysis
697
736
 
737
+ // currentFilePath is the path of the file being compiled
738
+ // Used for looking up per-file synthetic imports
739
+ currentFilePath string
740
+
698
741
  // Context flags
699
742
  insideAddressOf bool // true when processing operand of & operator
743
+
744
+ // renamedVars tracks variables that have been renamed to avoid type shadowing
745
+ // Key: types.Object of the original variable, Value: new name to use
746
+ renamedVars map[types.Object]string
700
747
  }
701
748
 
702
- // It initializes the compiler with a `TSCodeWriter` for output,
703
- // Go package information (`packages.Package`), and pre-computed
704
- // analysis results (`Analysis`) to guide the translation process.
705
- func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, analysis *Analysis) *GoToTSCompiler {
749
+ // NewGoToTSCompiler creates a new GoToTSCompiler with a TSCodeWriter for output,
750
+ // Go package information, pre-computed analysis results, and the current file path.
751
+ func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, analysis *Analysis, filePath string) *GoToTSCompiler {
706
752
  return &GoToTSCompiler{
707
- tsw: tsw,
708
- pkg: pkg,
709
- analysis: analysis,
753
+ tsw: tsw,
754
+ pkg: pkg,
755
+ analysis: analysis,
756
+ currentFilePath: filePath,
757
+ renamedVars: make(map[types.Object]string),
710
758
  }
711
759
  }
712
760
 
@@ -785,6 +833,18 @@ func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessVarRefedValue bool) {
785
833
  // Use TypesInfo to find the object associated with the identifier
786
834
  obj := c.objectOfIdent(exp)
787
835
 
836
+ // Check if this variable has been renamed to avoid type shadowing
837
+ if obj != nil {
838
+ if renamedName, ok := c.renamedVars[obj]; ok {
839
+ c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedName))
840
+ // Determine if we need to access .value based on analysis data
841
+ if accessVarRefedValue && c.analysis.NeedsVarRefAccess(obj) {
842
+ c.tsw.WriteLiterally("!.value")
843
+ }
844
+ return
845
+ }
846
+ }
847
+
788
848
  // Check if this identifier refers to a constant
789
849
  if obj != nil {
790
850
  if constObj, isConst := obj.(*types.Const); isConst {
@@ -12,10 +12,10 @@ import (
12
12
  "sync/atomic"
13
13
  "testing"
14
14
 
15
- "github.com/aperturerobotics/goscript/compliance"
15
+ "github.com/aperturerobotics/goscript/tests"
16
16
  )
17
17
 
18
- // NOTE: this is here instead of compliance/compliance_test.go so coverage ends up in this package.
18
+ // NOTE: this is here instead of tests/tests_test.go so coverage ends up in this package.
19
19
 
20
20
  func TestCompliance(t *testing.T) {
21
21
  // Get workspace directory (project root)
@@ -26,7 +26,7 @@ func TestCompliance(t *testing.T) {
26
26
  workspaceDir = filepath.Join(workspaceDir, "..")
27
27
 
28
28
  // First collect all test paths
29
- testsDir := filepath.Join(workspaceDir, "compliance/tests")
29
+ testsDir := filepath.Join(workspaceDir, "tests/tests")
30
30
  dirs, err := os.ReadDir(testsDir)
31
31
  if err != nil {
32
32
  t.Fatalf("failed to read tests dir: %v", err)
@@ -50,7 +50,7 @@ func TestCompliance(t *testing.T) {
50
50
  slices.Sort(testPaths)
51
51
 
52
52
  // limit concurrency
53
- simulLimit := make(chan struct{}, runtime.GOMAXPROCS(-1)*2)
53
+ simulLimit := make(chan struct{}, runtime.GOMAXPROCS(-1))
54
54
  for range cap(simulLimit) {
55
55
  simulLimit <- struct{}{}
56
56
  }
@@ -71,7 +71,7 @@ func TestCompliance(t *testing.T) {
71
71
  defer func() {
72
72
  simulLimit <- struct{}{}
73
73
  }()
74
- compliance.RunGoScriptTestDir(t, workspaceDir, path) // Pass workspaceDir
74
+ tests.RunGoScriptTestDir(t, workspaceDir, path) // Pass workspaceDir
75
75
 
76
76
  // Remove dir if everything passed
77
77
  if !t.Failed() {
@@ -89,13 +89,13 @@ func TestCompliance(t *testing.T) {
89
89
  t.Run("typecheck", func(t *testing.T) {
90
90
  t.Helper()
91
91
  if failed {
92
- t.Log("at least one compliance test failed: skipping typecheck")
92
+ t.Log("at least one tests test failed: skipping typecheck")
93
93
  t.SkipNow()
94
94
  }
95
95
 
96
96
  // NOTE: typecheck does not yet pass, so we skip for now.
97
97
  if ranTests.Load() != 0 {
98
- t.Log("at least one compliance test ran: skipping typecheck")
98
+ t.Log("at least one tests test ran: skipping typecheck")
99
99
  t.SkipNow()
100
100
  }
101
101
 
@@ -106,7 +106,7 @@ func TestCompliance(t *testing.T) {
106
106
  }
107
107
 
108
108
  // Create global typecheck tsconfig
109
- tsconfigPath := compliance.WriteGlobalTypeCheckConfig(t, parentModPath, workspaceDir)
109
+ tsconfigPath := tests.WriteGlobalTypeCheckConfig(t, parentModPath, workspaceDir)
110
110
 
111
111
  // Run TypeScript type checking
112
112
  typecheckDir := filepath.Dir(tsconfigPath)
@@ -129,7 +129,7 @@ func TestCompliance(t *testing.T) {
129
129
  }
130
130
 
131
131
  // getParentGoModulePath is a helper function to get the parent Go module path
132
- // This is similar to the one in compliance.go but simplified for use in tests
132
+ // This is similar to the one in tests.go but simplified for use in tests
133
133
  func getParentGoModulePath() (string, error) {
134
134
  cmd := exec.Command("go", "list", "-m")
135
135
  output, err := cmd.Output()
@@ -341,7 +341,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
341
341
  case *types.Map, *types.Struct:
342
342
  // Handle struct directly with the struct literal logic
343
343
  if structType, ok := underlying.(*types.Struct); ok {
344
- return c.writeUntypedStructLiteral(exp, structType) // true = anonymous
344
+ return c.writeUntypedStructLiteral(exp, tv.Type, structType)
345
345
  }
346
346
  // Map case would be handled here
347
347
  return fmt.Errorf("untyped map composite literals not yet supported")
@@ -356,7 +356,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
356
356
  // This is an anonymous struct literal with inferred pointer type
357
357
  // Just create the struct object directly - no var-refing needed
358
358
  // Anonymous literals are not variables, so they don't get var-refed
359
- return c.writeUntypedStructLiteral(exp, elemType) // true = anonymous
359
+ return c.writeUntypedStructLiteral(exp, ptrType.Elem(), elemType)
360
360
  default:
361
361
  return fmt.Errorf("unhandled pointer composite literal element type: %T", elemType)
362
362
  }
@@ -384,7 +384,7 @@ func (c *GoToTSCompiler) writeUntypedArrayLiteral(exp *ast.CompositeLit) error {
384
384
  }
385
385
 
386
386
  // writeUntypedStructLiteral handles untyped composite literals that are structs or pointers to structs
387
- func (c *GoToTSCompiler) writeUntypedStructLiteral(exp *ast.CompositeLit, structType *types.Struct) error {
387
+ func (c *GoToTSCompiler) writeUntypedStructLiteral(exp *ast.CompositeLit, actualType types.Type, structType *types.Struct) error {
388
388
  // Create field mapping like the typed struct case
389
389
  directFields := make(map[string]ast.Expr)
390
390
 
@@ -408,8 +408,23 @@ func (c *GoToTSCompiler) writeUntypedStructLiteral(exp *ast.CompositeLit, struct
408
408
  }
409
409
  }
410
410
 
411
- // Write the object literal (always anonymous for untyped)
412
- c.tsw.WriteLiterally("{")
411
+ // Check if this is a named type
412
+ isNamed := false
413
+ if _, ok := actualType.(*types.Named); ok {
414
+ isNamed = true
415
+ }
416
+
417
+ // Write the object literal
418
+ if isNamed {
419
+ // For named structs, use constructor
420
+ c.tsw.WriteLiterally("$.markAsStructValue(new ")
421
+ // Write the type name
422
+ c.WriteGoType(actualType, GoTypeContextGeneral)
423
+ c.tsw.WriteLiterally("({")
424
+ } else {
425
+ // For truly anonymous structs, just write a simple object literal
426
+ c.tsw.WriteLiterally("{")
427
+ }
413
428
 
414
429
  firstFieldWritten := false
415
430
  // Write fields in order
@@ -434,7 +449,12 @@ func (c *GoToTSCompiler) writeUntypedStructLiteral(exp *ast.CompositeLit, struct
434
449
  firstFieldWritten = true
435
450
  }
436
451
 
437
- c.tsw.WriteLiterally("}")
452
+ // Close the object literal
453
+ if isNamed {
454
+ c.tsw.WriteLiterally("}))")
455
+ } else {
456
+ c.tsw.WriteLiterally("}")
457
+ }
438
458
  return nil
439
459
  }
440
460
 
@@ -606,9 +626,10 @@ func (c *GoToTSCompiler) categorizeStructFields(
606
626
  }
607
627
  }
608
628
 
609
- // Handle the case where an anonymous struct has values without keys
629
+ // Handle the case where a struct has values without keys (positional initialization)
610
630
  // This block processes non-key-value elements and associates them with struct fields.
611
- if isAnonymousStruct && len(exp.Elts) > 0 && len(directFields) == 0 {
631
+ // This applies to both named and anonymous structs.
632
+ if len(exp.Elts) > 0 && len(directFields) == 0 {
612
633
  // Check if any elements in the composite literal are not key-value pairs.
613
634
  hasNonKeyValueElts := false
614
635
  for _, elt := range exp.Elts {
package/compiler/decl.go CHANGED
@@ -5,7 +5,7 @@ import (
5
5
  "go/ast"
6
6
  "go/token"
7
7
  "go/types"
8
- "sort"
8
+ "slices"
9
9
  )
10
10
 
11
11
  // WriteDecls iterates through a slice of Go top-level declarations (`ast.Decl`)
@@ -210,7 +210,7 @@ func (c *GoToTSCompiler) extractTypeDependencies(typeExpr ast.Expr, typeSpecMap
210
210
  }
211
211
 
212
212
  // Sort dependencies for deterministic output
213
- sort.Strings(deps)
213
+ slices.Sort(deps)
214
214
  return deps
215
215
  }
216
216
 
@@ -347,7 +347,7 @@ func (c *GoToTSCompiler) topologicalSort(dependencies map[string][]string) ([]st
347
347
  // Sort dependencies for consistent output
348
348
  sortedDeps := make([]string, len(deps))
349
349
  copy(sortedDeps, deps)
350
- sort.Strings(sortedDeps)
350
+ slices.Sort(sortedDeps)
351
351
 
352
352
  for _, dep := range sortedDeps {
353
353
  if _, exists := inDegree[dep]; exists {
@@ -359,7 +359,7 @@ func (c *GoToTSCompiler) topologicalSort(dependencies map[string][]string) ([]st
359
359
 
360
360
  // Sort neighbors in graph for consistency
361
361
  for node := range graph {
362
- sort.Strings(graph[node])
362
+ slices.Sort(graph[node])
363
363
  }
364
364
 
365
365
  // Find nodes with no incoming edges and sort them
@@ -369,7 +369,7 @@ func (c *GoToTSCompiler) topologicalSort(dependencies map[string][]string) ([]st
369
369
  queue = append(queue, node)
370
370
  }
371
371
  }
372
- sort.Strings(queue) // Sort initial queue for deterministic output
372
+ slices.Sort(queue) // Sort initial queue for deterministic output
373
373
 
374
374
  var result []string
375
375
 
@@ -389,7 +389,7 @@ func (c *GoToTSCompiler) topologicalSort(dependencies map[string][]string) ([]st
389
389
  }
390
390
 
391
391
  // Sort new zero-degree nodes and add to queue
392
- sort.Strings(newZeroNodes)
392
+ slices.Sort(newZeroNodes)
393
393
  queue = append(queue, newZeroNodes...)
394
394
  }
395
395
 
@@ -407,7 +407,7 @@ func (c *GoToTSCompiler) topologicalSort(dependencies map[string][]string) ([]st
407
407
  remaining = append(remaining, name)
408
408
  }
409
409
  }
410
- sort.Strings(remaining)
410
+ slices.Sort(remaining)
411
411
 
412
412
  return nil, fmt.Errorf("circular dependency detected in type declarations. Remaining types: %v", remaining)
413
413
  }
@@ -622,11 +622,20 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
622
622
  } else {
623
623
  // Multiple return values -> tuple type
624
624
  c.tsw.WriteLiterally("[")
625
- for i, field := range funcType.Results.List {
626
- if i > 0 {
627
- c.tsw.WriteLiterally(", ")
625
+ first := true
626
+ for _, field := range funcType.Results.List {
627
+ // Each field may represent multiple return values (e.g., "a, b int")
628
+ count := len(field.Names)
629
+ if count == 0 {
630
+ count = 1 // Unnamed return value
631
+ }
632
+ for j := 0; j < count; j++ {
633
+ if !first {
634
+ c.tsw.WriteLiterally(", ")
635
+ }
636
+ first = false
637
+ c.WriteTypeExpr(field.Type)
628
638
  }
629
- c.WriteTypeExpr(field.Type)
630
639
  }
631
640
  c.tsw.WriteLiterally("]")
632
641
  }
@@ -12,7 +12,15 @@ func (c *GoToTSCompiler) isCallExprAsync(exp *ast.CallExpr) bool {
12
12
  case *ast.Ident:
13
13
  // Function call (e.g., func())
14
14
  if obj := c.objectOfIdent(fun); obj != nil {
15
- return c.analysis.IsAsyncFunc(obj)
15
+ // Check if this is a known async function
16
+ if c.analysis.IsAsyncFunc(obj) {
17
+ return true
18
+ }
19
+ // Check if this is a variable that returns async values
20
+ // (e.g., indirect := sync.OnceValue(asyncFunc))
21
+ if c.analysis.IsAsyncReturningVar(obj) {
22
+ return true
23
+ }
16
24
  }
17
25
  return false
18
26
 
@@ -42,6 +50,13 @@ func (c *GoToTSCompiler) isCallExprAsync(exp *ast.CallExpr) bool {
42
50
  objOk = true
43
51
  }
44
52
 
53
+ case *ast.CallExpr:
54
+ // Method call on function result: funcCall().method()
55
+ // Get the type of the function call result
56
+ if callType := c.pkg.TypesInfo.TypeOf(x); callType != nil {
57
+ objOk = true
58
+ }
59
+
45
60
  default:
46
61
  objOk = false
47
62
  }
@@ -144,6 +159,16 @@ func (c *GoToTSCompiler) addNonNullAssertion(expFun ast.Expr) {
144
159
  c.tsw.WriteLiterally("!")
145
160
  }
146
161
  }
162
+ } else if selectorExpr, isSelectorExpr := expFun.(*ast.SelectorExpr); isSelectorExpr {
163
+ // Check if this is a field access that returns a function type
164
+ // e.g., s.step where step is a function-typed field
165
+ if selection := c.pkg.TypesInfo.Selections[selectorExpr]; selection != nil {
166
+ // This is a field or method selection
167
+ if selection.Kind() == types.FieldVal {
168
+ // It's a field - function-typed fields may be nil
169
+ c.tsw.WriteLiterally("!")
170
+ }
171
+ }
147
172
  } else if _, isNamed := funType.(*types.Named); isNamed {
148
173
  c.tsw.WriteLiterally("!")
149
174
  }