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.
- package/README.md +62 -46
- package/compiler/analysis.go +621 -19
- package/compiler/analysis_test.go +3 -3
- package/compiler/assignment.go +100 -0
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +76 -16
- package/compiler/compiler_test.go +9 -9
- package/compiler/composite-lit.go +29 -8
- package/compiler/decl.go +20 -11
- package/compiler/expr-call-async.go +26 -1
- package/compiler/expr-call-builtins.go +60 -4
- package/compiler/expr-call-type-conversion.go +37 -5
- package/compiler/expr-call.go +26 -6
- package/compiler/expr-selector.go +35 -2
- package/compiler/expr-type.go +12 -2
- package/compiler/expr.go +61 -0
- package/compiler/index.test.ts +3 -1
- package/compiler/lit.go +13 -4
- package/compiler/spec-struct.go +30 -8
- package/compiler/spec-value.go +2 -2
- package/compiler/spec.go +23 -4
- package/compiler/stmt-assign.go +124 -0
- package/compiler/stmt-range.go +2 -2
- package/compiler/stmt.go +160 -14
- package/compiler/type-info.go +3 -5
- package/compiler/type-utils.go +40 -1
- package/compiler/type.go +52 -14
- package/dist/gs/builtin/builtin.d.ts +8 -1
- package/dist/gs/builtin/builtin.js +26 -1
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/errors.d.ts +1 -0
- package/dist/gs/builtin/errors.js +8 -0
- package/dist/gs/builtin/errors.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +5 -4
- package/dist/gs/builtin/slice.js +88 -51
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +23 -2
- package/dist/gs/builtin/type.js +125 -0
- package/dist/gs/builtin/type.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/bytes/reader.gs.d.ts +1 -1
- package/dist/gs/bytes/reader.gs.js +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +2 -2
- package/dist/gs/reflect/index.js +1 -1
- package/dist/gs/reflect/index.js.map +1 -1
- package/dist/gs/reflect/map.d.ts +3 -2
- package/dist/gs/reflect/map.js +37 -3
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +53 -12
- package/dist/gs/reflect/type.js +906 -31
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.d.ts +11 -12
- package/dist/gs/reflect/types.js +26 -15
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/value.d.ts +4 -4
- package/dist/gs/reflect/value.js +8 -2
- package/dist/gs/reflect/value.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +21 -0
- package/dist/gs/slices/slices.js +48 -0
- package/dist/gs/slices/slices.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 +13 -7
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
- package/dist/gs/unicode/utf8/utf8.js +10 -6
- package/dist/gs/unicode/utf8/utf8.js.map +1 -1
- package/go.mod +6 -6
- package/go.sum +12 -8
- package/gs/builtin/builtin.ts +27 -2
- package/gs/builtin/errors.ts +12 -0
- package/gs/builtin/slice.ts +126 -55
- package/gs/builtin/type.ts +159 -2
- package/gs/builtin/varRef.ts +8 -2
- package/gs/bytes/reader.gs.ts +2 -2
- package/gs/math/hypot.gs.test.ts +3 -1
- package/gs/math/pow10.gs.test.ts +5 -4
- package/gs/reflect/index.ts +3 -2
- package/gs/reflect/map.test.ts +7 -6
- package/gs/reflect/map.ts +49 -7
- package/gs/reflect/type.ts +1150 -57
- package/gs/reflect/types.ts +34 -21
- package/gs/reflect/value.ts +12 -6
- package/gs/slices/slices.ts +55 -0
- package/gs/strconv/atoi.gs.ts +18 -2
- package/gs/sync/atomic/type.gs.ts +15 -10
- package/gs/unicode/utf8/utf8.ts +12 -8
- 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
|
|
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 {
|
|
@@ -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("/* _ = */ ")
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
703
|
-
// Go package information
|
|
704
|
-
|
|
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:
|
|
708
|
-
pkg:
|
|
709
|
-
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/
|
|
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()
|
|
@@ -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)
|
|
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)
|
|
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
|
-
//
|
|
412
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
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
|
}
|