goscript 0.0.62 → 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.
- package/README.md +54 -47
- package/compiler/analysis.go +182 -101
- package/compiler/analysis_test.go +3 -3
- package/compiler/assignment.go +28 -0
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +25 -18
- package/compiler/compiler_test.go +9 -9
- package/compiler/expr-call.go +10 -3
- package/compiler/expr.go +24 -0
- package/compiler/spec.go +4 -2
- package/compiler/stmt-assign.go +53 -0
- package/compiler/stmt.go +32 -14
- package/compiler/type-info.go +3 -5
- package/compiler/type.go +2 -2
- package/dist/gs/builtin/slice.js +43 -37
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/varRef.d.ts +3 -0
- package/dist/gs/builtin/varRef.js +6 -1
- package/dist/gs/builtin/varRef.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +4 -1
- package/dist/gs/reflect/type.js +91 -9
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.js +20 -2
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +3 -3
- package/dist/gs/sync/atomic/type.gs.js +5 -9
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/go.mod +2 -2
- package/go.sum +4 -0
- package/gs/builtin/slice.ts +55 -48
- package/gs/builtin/varRef.ts +8 -2
- package/gs/reflect/type.ts +103 -9
- package/gs/strconv/atoi.gs.ts +18 -2
- package/gs/sync/atomic/type.gs.ts +8 -12
- package/package.json +12 -3
|
@@ -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
|
|
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
|
|
239
|
-
testPath := "../
|
|
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{
|
package/compiler/assignment.go
CHANGED
|
@@ -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 {
|
|
@@ -298,6 +321,11 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
|
|
|
298
321
|
c.tsw.WriteLiterally(")")
|
|
299
322
|
}
|
|
300
323
|
|
|
324
|
+
// Close the parenthesis for integer division compound assignment
|
|
325
|
+
if isIntegerDivisionAssign {
|
|
326
|
+
c.tsw.WriteLiterally(")")
|
|
327
|
+
}
|
|
328
|
+
|
|
301
329
|
// If the LHS was a single map index, close the mapSet call
|
|
302
330
|
if isMapIndexLHS && len(lhs) == 1 {
|
|
303
331
|
c.tsw.WriteLiterally(")")
|
package/compiler/builtin_test.go
CHANGED
|
@@ -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
|
|
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{
|
package/compiler/compiler.go
CHANGED
|
@@ -593,7 +593,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
|
|
|
593
593
|
c.codeWriter = NewTSCodeWriter(of)
|
|
594
594
|
|
|
595
595
|
// Pass analysis to compiler
|
|
596
|
-
goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis)
|
|
596
|
+
goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis, c.fullPath)
|
|
597
597
|
|
|
598
598
|
// Add import for the goscript runtime using namespace import and alias
|
|
599
599
|
c.codeWriter.WriteLinef("import * as $ from %q", "@goscript/builtin/index.js")
|
|
@@ -701,15 +701,18 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
|
|
|
701
701
|
}
|
|
702
702
|
|
|
703
703
|
// Write synthetic imports (for promoted methods from embedded structs)
|
|
704
|
-
//
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
+
}
|
|
713
716
|
}
|
|
714
717
|
|
|
715
718
|
c.codeWriter.WriteLine("") // Add a newline after imports
|
|
@@ -731,6 +734,10 @@ type GoToTSCompiler struct {
|
|
|
731
734
|
|
|
732
735
|
analysis *Analysis
|
|
733
736
|
|
|
737
|
+
// currentFilePath is the path of the file being compiled
|
|
738
|
+
// Used for looking up per-file synthetic imports
|
|
739
|
+
currentFilePath string
|
|
740
|
+
|
|
734
741
|
// Context flags
|
|
735
742
|
insideAddressOf bool // true when processing operand of & operator
|
|
736
743
|
|
|
@@ -739,15 +746,15 @@ type GoToTSCompiler struct {
|
|
|
739
746
|
renamedVars map[types.Object]string
|
|
740
747
|
}
|
|
741
748
|
|
|
742
|
-
//
|
|
743
|
-
// Go package information
|
|
744
|
-
|
|
745
|
-
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 {
|
|
746
752
|
return &GoToTSCompiler{
|
|
747
|
-
tsw:
|
|
748
|
-
pkg:
|
|
749
|
-
analysis:
|
|
750
|
-
|
|
753
|
+
tsw: tsw,
|
|
754
|
+
pkg: pkg,
|
|
755
|
+
analysis: analysis,
|
|
756
|
+
currentFilePath: filePath,
|
|
757
|
+
renamedVars: make(map[types.Object]string),
|
|
751
758
|
}
|
|
752
759
|
}
|
|
753
760
|
|
|
@@ -12,10 +12,10 @@ import (
|
|
|
12
12
|
"sync/atomic"
|
|
13
13
|
"testing"
|
|
14
14
|
|
|
15
|
-
"github.com/aperturerobotics/goscript/
|
|
15
|
+
"github.com/aperturerobotics/goscript/tests"
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
-
// NOTE: this is here instead of
|
|
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, "
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
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 :=
|
|
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
|
|
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()
|
package/compiler/expr-call.go
CHANGED
|
@@ -223,8 +223,8 @@ func (c *GoToTSCompiler) writeArgumentWithTypeHandling(arg ast.Expr, funcSig *ty
|
|
|
223
223
|
return nil
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
// resolveImportAlias returns the import alias for a given package
|
|
227
|
-
// This is the single source of truth for import alias resolution
|
|
226
|
+
// resolveImportAlias returns the import alias for a given package.
|
|
227
|
+
// This is the single source of truth for import alias resolution.
|
|
228
228
|
func (c *GoToTSCompiler) resolveImportAlias(pkg *types.Package) (alias string, found bool) {
|
|
229
229
|
if c.analysis == nil || pkg == nil {
|
|
230
230
|
return "", false
|
|
@@ -232,11 +232,18 @@ func (c *GoToTSCompiler) resolveImportAlias(pkg *types.Package) (alias string, f
|
|
|
232
232
|
|
|
233
233
|
pkgName := pkg.Name()
|
|
234
234
|
|
|
235
|
-
// Try to match by the actual package name
|
|
235
|
+
// Try to match by the actual package name in regular imports
|
|
236
236
|
if _, exists := c.analysis.Imports[pkgName]; exists {
|
|
237
237
|
return pkgName, true
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
// Also check synthetic imports for the current file
|
|
241
|
+
if syntheticImports := c.analysis.SyntheticImportsPerFile[c.currentFilePath]; syntheticImports != nil {
|
|
242
|
+
if _, exists := syntheticImports[pkgName]; exists {
|
|
243
|
+
return pkgName, true
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
240
247
|
return "", false
|
|
241
248
|
}
|
|
242
249
|
|
package/compiler/expr.go
CHANGED
|
@@ -472,6 +472,26 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
// Check if this is integer division (Go's / operator truncates for integers)
|
|
476
|
+
isIntegerDivision := false
|
|
477
|
+
if exp.Op == token.QUO && c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
478
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
479
|
+
rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
|
|
480
|
+
if leftType != nil && rightType != nil {
|
|
481
|
+
leftBasic, leftIsBasic := leftType.Underlying().(*types.Basic)
|
|
482
|
+
rightBasic, rightIsBasic := rightType.Underlying().(*types.Basic)
|
|
483
|
+
if leftIsBasic && rightIsBasic {
|
|
484
|
+
// Check if both are integer types (not floating point)
|
|
485
|
+
isIntegerDivision = (leftBasic.Info()&types.IsInteger != 0) && (rightBasic.Info()&types.IsInteger != 0)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if isIntegerDivision {
|
|
491
|
+
// Wrap integer division in Math.trunc() to match Go's integer division behavior
|
|
492
|
+
c.tsw.WriteLiterally("Math.trunc(")
|
|
493
|
+
}
|
|
494
|
+
|
|
475
495
|
if needsNumberCast {
|
|
476
496
|
c.tsw.WriteLiterally("Number(")
|
|
477
497
|
}
|
|
@@ -495,6 +515,10 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
495
515
|
return fmt.Errorf("failed to write binary expression right operand: %w", err)
|
|
496
516
|
}
|
|
497
517
|
|
|
518
|
+
if isIntegerDivision {
|
|
519
|
+
c.tsw.WriteLiterally(")") // Close Math.trunc()
|
|
520
|
+
}
|
|
521
|
+
|
|
498
522
|
if isBitwise {
|
|
499
523
|
c.tsw.WriteLiterally(")") // Add closing parenthesis for bitwise operations
|
|
500
524
|
}
|
package/compiler/spec.go
CHANGED
|
@@ -638,8 +638,10 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
|
638
638
|
// Skip writing the import if it was already written as a synthetic import
|
|
639
639
|
// This prevents duplicate imports when a file needs an import both from
|
|
640
640
|
// its AST and for promoted methods from embedded structs
|
|
641
|
-
if
|
|
642
|
-
|
|
641
|
+
if syntheticImports := c.analysis.SyntheticImportsPerFile[c.currentFilePath]; syntheticImports != nil {
|
|
642
|
+
if _, isSynthetic := syntheticImports[impName]; isSynthetic {
|
|
643
|
+
return
|
|
644
|
+
}
|
|
643
645
|
}
|
|
644
646
|
|
|
645
647
|
c.tsw.WriteImport(impName, tsImportPath+"/index.js")
|
package/compiler/stmt-assign.go
CHANGED
|
@@ -320,6 +320,59 @@ func (c *GoToTSCompiler) writeMultiVarAssignFromCall(lhs []ast.Expr, callExpr *a
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
if allNewVars && anyNewVars {
|
|
323
|
+
// Check if any variable needs VarRef - if so, we need a different approach
|
|
324
|
+
anyNeedsVarRef := false
|
|
325
|
+
needsVarRefVars := make([]bool, len(lhs))
|
|
326
|
+
for i, lhsExpr := range lhs {
|
|
327
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
|
|
328
|
+
if obj := c.pkg.TypesInfo.Defs[ident]; obj != nil {
|
|
329
|
+
if c.analysis.NeedsVarRef(obj) {
|
|
330
|
+
needsVarRefVars[i] = true
|
|
331
|
+
anyNeedsVarRef = true
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if anyNeedsVarRef {
|
|
338
|
+
// Use temp variables for destructuring, then wrap in VarRef as needed
|
|
339
|
+
c.tsw.WriteLiterally("let [")
|
|
340
|
+
for i, lhsExpr := range lhs {
|
|
341
|
+
if i != 0 {
|
|
342
|
+
c.tsw.WriteLiterally(", ")
|
|
343
|
+
}
|
|
344
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
345
|
+
if ident.Name == "_" {
|
|
346
|
+
// Empty slot for blank identifier
|
|
347
|
+
} else if needsVarRefVars[i] {
|
|
348
|
+
c.tsw.WriteLiterally("_varref_tmp_")
|
|
349
|
+
c.tsw.WriteLiterally(ident.Name)
|
|
350
|
+
} else {
|
|
351
|
+
c.WriteIdent(ident, false)
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
c.WriteValueExpr(lhsExpr)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
c.tsw.WriteLiterally("] = ")
|
|
358
|
+
c.WriteValueExpr(callExpr)
|
|
359
|
+
c.tsw.WriteLine("")
|
|
360
|
+
|
|
361
|
+
// Now declare the VarRef-wrapped variables
|
|
362
|
+
for i, lhsExpr := range lhs {
|
|
363
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && needsVarRefVars[i] {
|
|
364
|
+
c.tsw.WriteLiterally("let ")
|
|
365
|
+
c.WriteIdent(ident, false)
|
|
366
|
+
c.tsw.WriteLiterally(" = $.varRef(_varref_tmp_")
|
|
367
|
+
c.tsw.WriteLiterally(ident.Name)
|
|
368
|
+
// Add non-null assertion to handle cases where the tuple type includes null
|
|
369
|
+
c.tsw.WriteLiterally("!)")
|
|
370
|
+
c.tsw.WriteLine("")
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return nil
|
|
374
|
+
}
|
|
375
|
+
|
|
323
376
|
c.tsw.WriteLiterally("let [")
|
|
324
377
|
|
|
325
378
|
for i, lhsExpr := range lhs {
|
package/compiler/stmt.go
CHANGED
|
@@ -167,9 +167,17 @@ func (c *GoToTSCompiler) WriteStmtIncDec(stmt *ast.IncDecStmt) error {
|
|
|
167
167
|
func (c *GoToTSCompiler) WriteStmtBranch(stmt *ast.BranchStmt) error {
|
|
168
168
|
switch stmt.Tok {
|
|
169
169
|
case token.BREAK:
|
|
170
|
-
|
|
170
|
+
if stmt.Label != nil {
|
|
171
|
+
c.tsw.WriteLinef("break %s", stmt.Label.Name)
|
|
172
|
+
} else {
|
|
173
|
+
c.tsw.WriteLine("break")
|
|
174
|
+
}
|
|
171
175
|
case token.CONTINUE:
|
|
172
|
-
|
|
176
|
+
if stmt.Label != nil {
|
|
177
|
+
c.tsw.WriteLinef("continue %s", stmt.Label.Name)
|
|
178
|
+
} else {
|
|
179
|
+
c.tsw.WriteLine("continue")
|
|
180
|
+
}
|
|
173
181
|
case token.GOTO:
|
|
174
182
|
// TypeScript doesn't support goto, but we can handle it by skipping it
|
|
175
183
|
// since the labeled statement restructuring should handle the control flow
|
|
@@ -484,17 +492,10 @@ func (c *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
|
|
|
484
492
|
}
|
|
485
493
|
c.tsw.WriteLiterally(") ")
|
|
486
494
|
|
|
487
|
-
if err := c.
|
|
495
|
+
if err := c.writeIfBody(exp); err != nil {
|
|
488
496
|
return err
|
|
489
497
|
}
|
|
490
498
|
|
|
491
|
-
if exp.Else != nil {
|
|
492
|
-
c.tsw.WriteLiterally(" else ")
|
|
493
|
-
if err := c.WriteStmt(exp.Else); err != nil {
|
|
494
|
-
return err
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
499
|
c.tsw.Indent(-1)
|
|
499
500
|
c.tsw.WriteLine("}")
|
|
500
501
|
return nil
|
|
@@ -507,14 +508,31 @@ func (c *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
|
|
|
507
508
|
}
|
|
508
509
|
c.tsw.WriteLiterally(") ")
|
|
509
510
|
|
|
510
|
-
|
|
511
|
+
return c.writeIfBody(exp)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// writeIfBody writes the if body and optional else clause, handling newline suppression.
|
|
515
|
+
func (c *GoToTSCompiler) writeIfBody(exp *ast.IfStmt) error {
|
|
516
|
+
hasElse := exp.Else != nil
|
|
517
|
+
if err := c.WriteStmtBlock(exp.Body, hasElse); err != nil {
|
|
511
518
|
return err
|
|
512
519
|
}
|
|
513
520
|
|
|
514
|
-
if
|
|
521
|
+
if hasElse {
|
|
515
522
|
c.tsw.WriteLiterally(" else ")
|
|
516
|
-
|
|
517
|
-
|
|
523
|
+
switch elseStmt := exp.Else.(type) {
|
|
524
|
+
case *ast.BlockStmt:
|
|
525
|
+
if err := c.WriteStmtBlock(elseStmt, false); err != nil {
|
|
526
|
+
return err
|
|
527
|
+
}
|
|
528
|
+
case *ast.IfStmt:
|
|
529
|
+
if err := c.WriteStmtIf(elseStmt); err != nil {
|
|
530
|
+
return err
|
|
531
|
+
}
|
|
532
|
+
default:
|
|
533
|
+
if err := c.WriteStmt(exp.Else); err != nil {
|
|
534
|
+
return err
|
|
535
|
+
}
|
|
518
536
|
}
|
|
519
537
|
}
|
|
520
538
|
|
package/compiler/type-info.go
CHANGED
|
@@ -26,11 +26,9 @@ func (c *GoToTSCompiler) writeTypeInfoObject(typ types.Type) {
|
|
|
26
26
|
underlying := typ.Underlying()
|
|
27
27
|
switch t := underlying.(type) {
|
|
28
28
|
case *types.Basic:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Basic, name: %q }", tsTypeName)
|
|
29
|
+
// Use Go type name (e.g., "int") not TypeScript type name (e.g., "number")
|
|
30
|
+
// The reflect system needs Go type names to correctly determine Kind()
|
|
31
|
+
c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Basic, name: %q }", t.Name())
|
|
34
32
|
// Note: The original 'case *types.Named:' here for 'underlying' is intentionally omitted.
|
|
35
33
|
// If typ.Underlying() is *types.Named (e.g. type T1 MyInt; type T2 T1;),
|
|
36
34
|
// then writeTypeInfoObject(typ.Underlying()) would be called in some contexts,
|
package/compiler/type.go
CHANGED
|
@@ -718,7 +718,7 @@ func (c *GoToTSCompiler) writeInterfaceStructure(iface *types.Interface, astNode
|
|
|
718
718
|
func (c *GoToTSCompiler) getTypeString(goType types.Type) string {
|
|
719
719
|
var typeStr strings.Builder
|
|
720
720
|
writer := NewTSCodeWriter(&typeStr)
|
|
721
|
-
tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis)
|
|
721
|
+
tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis, c.currentFilePath)
|
|
722
722
|
tempCompiler.WriteGoType(goType, GoTypeContextGeneral)
|
|
723
723
|
return typeStr.String()
|
|
724
724
|
}
|
|
@@ -730,7 +730,7 @@ func (c *GoToTSCompiler) getTypeString(goType types.Type) string {
|
|
|
730
730
|
func (c *GoToTSCompiler) getASTTypeString(astType ast.Expr, goType types.Type) string {
|
|
731
731
|
var typeStr strings.Builder
|
|
732
732
|
writer := NewTSCodeWriter(&typeStr)
|
|
733
|
-
tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis)
|
|
733
|
+
tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis, c.currentFilePath)
|
|
734
734
|
|
|
735
735
|
if astType != nil {
|
|
736
736
|
// Use AST-based type writing to preserve qualified names
|
package/dist/gs/builtin/slice.js
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wrapSliceProxy wraps a SliceProxy in a Proxy to intercept index access
|
|
3
|
+
* and route it through the backing array.
|
|
4
|
+
*/
|
|
5
|
+
function wrapSliceProxy(proxy) {
|
|
6
|
+
const handler = {
|
|
7
|
+
get(target, prop) {
|
|
8
|
+
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
9
|
+
const index = Number(prop);
|
|
10
|
+
if (index >= 0 && index < target.__meta__.length) {
|
|
11
|
+
return target.__meta__.backing[target.__meta__.offset + index];
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`Slice index out of range: ${index} >= ${target.__meta__.length}`);
|
|
14
|
+
}
|
|
15
|
+
if (prop === 'length') {
|
|
16
|
+
return target.__meta__.length;
|
|
17
|
+
}
|
|
18
|
+
if (prop === '__meta__') {
|
|
19
|
+
return target.__meta__;
|
|
20
|
+
}
|
|
21
|
+
return Reflect.get(target, prop);
|
|
22
|
+
},
|
|
23
|
+
set(target, prop, value) {
|
|
24
|
+
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
25
|
+
const index = Number(prop);
|
|
26
|
+
if (index >= 0 && index < target.__meta__.length) {
|
|
27
|
+
target.__meta__.backing[target.__meta__.offset + index] = value;
|
|
28
|
+
target[index] = value; // Also update the proxy target for consistency
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Slice index out of range: ${index} >= ${target.__meta__.length}`);
|
|
32
|
+
}
|
|
33
|
+
if (prop === 'length' || prop === '__meta__') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return Reflect.set(target, prop, value);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
return new Proxy(proxy, handler);
|
|
40
|
+
}
|
|
1
41
|
// asArray converts a slice to a JavaScript array.
|
|
2
42
|
export function asArray(slice) {
|
|
3
43
|
if (slice === null || slice === undefined) {
|
|
@@ -65,41 +105,7 @@ export const makeSlice = (length, capacity, typeHint) => {
|
|
|
65
105
|
length: length,
|
|
66
106
|
capacity: actualCapacity,
|
|
67
107
|
};
|
|
68
|
-
|
|
69
|
-
const handler = {
|
|
70
|
-
get(target, prop) {
|
|
71
|
-
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
72
|
-
const index = Number(prop);
|
|
73
|
-
if (index >= 0 && index < target.__meta__.length) {
|
|
74
|
-
return target.__meta__.backing[target.__meta__.offset + index];
|
|
75
|
-
}
|
|
76
|
-
throw new Error(`Slice index out of range: ${index} >= ${target.__meta__.length}`);
|
|
77
|
-
}
|
|
78
|
-
if (prop === 'length') {
|
|
79
|
-
return target.__meta__.length;
|
|
80
|
-
}
|
|
81
|
-
if (prop === '__meta__') {
|
|
82
|
-
return target.__meta__;
|
|
83
|
-
}
|
|
84
|
-
return Reflect.get(target, prop);
|
|
85
|
-
},
|
|
86
|
-
set(target, prop, value) {
|
|
87
|
-
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
88
|
-
const index = Number(prop);
|
|
89
|
-
if (index >= 0 && index < target.__meta__.length) {
|
|
90
|
-
target.__meta__.backing[target.__meta__.offset + index] = value;
|
|
91
|
-
target[index] = value; // Also update the proxy target for consistency
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
throw new Error(`Slice index out of range: ${index} >= ${target.__meta__.length}`);
|
|
95
|
-
}
|
|
96
|
-
if (prop === 'length' || prop === '__meta__') {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
return Reflect.set(target, prop, value);
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
return new Proxy(proxy, handler);
|
|
108
|
+
return wrapSliceProxy(proxy);
|
|
103
109
|
}
|
|
104
110
|
const actualCapacity = capacity === undefined ? length : capacity;
|
|
105
111
|
if (length < 0 || actualCapacity < 0 || length > actualCapacity) {
|
|
@@ -581,7 +587,7 @@ export function append(slice, ...elements) {
|
|
|
581
587
|
length: newLength,
|
|
582
588
|
capacity: oldCapacity,
|
|
583
589
|
};
|
|
584
|
-
return resultProxy;
|
|
590
|
+
return wrapSliceProxy(resultProxy);
|
|
585
591
|
}
|
|
586
592
|
// Case 2: Reallocation is needed.
|
|
587
593
|
let newCapacity = oldCapacity;
|
|
@@ -613,7 +619,7 @@ export function append(slice, ...elements) {
|
|
|
613
619
|
length: newLength,
|
|
614
620
|
capacity: newCapacity,
|
|
615
621
|
};
|
|
616
|
-
return resultProxy;
|
|
622
|
+
return wrapSliceProxy(resultProxy);
|
|
617
623
|
}
|
|
618
624
|
export function copy(dst, src) {
|
|
619
625
|
if (dst === null) {
|