goscript 0.0.58 → 0.0.59
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 +40 -33
- package/compiler/analysis.go +115 -19
- package/compiler/assignment.go +163 -217
- package/compiler/compiler.go +35 -31
- package/compiler/composite-lit.go +233 -196
- package/compiler/constraint.go +88 -0
- package/compiler/decl.go +82 -24
- package/compiler/expr-call-async.go +20 -34
- package/compiler/expr-call-builtins.go +19 -0
- package/compiler/expr-call-helpers.go +0 -28
- package/compiler/expr-call-make.go +93 -343
- package/compiler/expr-call-type-conversion.go +221 -249
- package/compiler/expr-call.go +70 -69
- package/compiler/expr-selector.go +21 -24
- package/compiler/expr.go +3 -60
- package/compiler/protobuf.go +180 -36
- package/compiler/spec-value.go +132 -24
- package/compiler/spec.go +14 -55
- package/compiler/stmt-assign.go +338 -356
- package/compiler/stmt-range.go +4 -24
- package/compiler/stmt.go +92 -203
- package/compiler/type-utils.go +185 -0
- package/compiler/type.go +26 -80
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js +3 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +8 -2
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/fmt/fmt.js +113 -16
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/runtime/runtime.d.ts +1 -1
- package/dist/gs/runtime/runtime.js +1 -1
- package/dist/gs/slices/slices.d.ts +23 -0
- package/dist/gs/slices/slices.js +61 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/go.mod +8 -8
- package/go.sum +14 -14
- package/gs/builtin/slice.ts +5 -2
- package/gs/builtin/type.ts +13 -6
- package/gs/fmt/fmt.test.ts +176 -0
- package/gs/fmt/fmt.ts +109 -18
- package/gs/runtime/runtime.ts +1 -1
- package/gs/slices/slices.ts +68 -0
- package/package.json +3 -3
|
@@ -79,11 +79,7 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
|
|
|
79
79
|
// Check if it's a []byte literal
|
|
80
80
|
isByteSliceLiteral := false
|
|
81
81
|
if typInfo := c.pkg.TypesInfo.TypeOf(exp.Type); typInfo != nil {
|
|
82
|
-
|
|
83
|
-
if basicElem, ok := sliceT.Elem().(*types.Basic); ok && basicElem.Kind() == types.Uint8 {
|
|
84
|
-
isByteSliceLiteral = true
|
|
85
|
-
}
|
|
86
|
-
}
|
|
82
|
+
isByteSliceLiteral = c.isByteSliceType(typInfo)
|
|
87
83
|
}
|
|
88
84
|
|
|
89
85
|
if isByteSliceLiteral {
|
|
@@ -287,96 +283,12 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
|
|
|
287
283
|
|
|
288
284
|
if isStructLiteral && structType != nil {
|
|
289
285
|
// --- Struct Literal Handling (Nested) ---
|
|
290
|
-
|
|
291
|
-
embeddedFields :=
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
field := structType.Field(i)
|
|
297
|
-
if field.Anonymous() {
|
|
298
|
-
fieldType := field.Type()
|
|
299
|
-
if ptr, ok := fieldType.(*types.Pointer); ok {
|
|
300
|
-
fieldType = ptr.Elem()
|
|
301
|
-
}
|
|
302
|
-
if named, ok := fieldType.(*types.Named); ok {
|
|
303
|
-
// Use the type name as the property name in TS
|
|
304
|
-
embeddedPropName := named.Obj().Name()
|
|
305
|
-
embeddedFields[embeddedPropName] = make(map[string]ast.Expr)
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Group literal elements by direct vs embedded fields
|
|
311
|
-
for _, elt := range exp.Elts {
|
|
312
|
-
kv, ok := elt.(*ast.KeyValueExpr)
|
|
313
|
-
if !ok {
|
|
314
|
-
continue
|
|
315
|
-
} // Skip non-key-value
|
|
316
|
-
keyIdent, ok := kv.Key.(*ast.Ident)
|
|
317
|
-
if !ok {
|
|
318
|
-
continue
|
|
319
|
-
} // Skip non-ident keys
|
|
320
|
-
keyName := keyIdent.Name
|
|
321
|
-
|
|
322
|
-
// Check if this is an explicit embedded struct initialization
|
|
323
|
-
// e.g., Person: Person{...} or Person: personVar
|
|
324
|
-
if _, isEmbedded := embeddedFields[keyName]; isEmbedded {
|
|
325
|
-
// This is an explicit initialization of an embedded struct
|
|
326
|
-
explicitEmbedded[keyName] = kv.Value
|
|
327
|
-
continue
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
isDirectField := false
|
|
331
|
-
for i := range structType.NumFields() {
|
|
332
|
-
field := structType.Field(i)
|
|
333
|
-
if field.Name() == keyName {
|
|
334
|
-
isDirectField = true
|
|
335
|
-
directFields[keyName] = kv.Value
|
|
336
|
-
break
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// For anonymous structs, all fields are direct fields
|
|
341
|
-
if isAnonymousStruct {
|
|
342
|
-
directFields[keyName] = kv.Value
|
|
343
|
-
isDirectField = true
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// If not a direct field, return an error
|
|
347
|
-
if !isDirectField {
|
|
348
|
-
// This field was not found as a direct field in the struct
|
|
349
|
-
return fmt.Errorf("field %s not found in type %s for composite literal",
|
|
350
|
-
keyName, litType.String())
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Handle the case where an anonymous struct has values without keys
|
|
355
|
-
// This block processes non-key-value elements and associates them with struct fields.
|
|
356
|
-
if isAnonymousStruct && len(exp.Elts) > 0 && len(directFields) == 0 {
|
|
357
|
-
// Check if any elements in the composite literal are not key-value pairs.
|
|
358
|
-
hasNonKeyValueElts := false
|
|
359
|
-
for _, elt := range exp.Elts {
|
|
360
|
-
// If an element is not a key-value pair, set the flag to true.
|
|
361
|
-
if _, isKV := elt.(*ast.KeyValueExpr); !isKV {
|
|
362
|
-
hasNonKeyValueElts = true
|
|
363
|
-
break
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if hasNonKeyValueElts {
|
|
368
|
-
// Get the fields from the struct type
|
|
369
|
-
for i := 0; i < structType.NumFields(); i++ {
|
|
370
|
-
field := structType.Field(i)
|
|
371
|
-
// If we have a value for this field position
|
|
372
|
-
if i < len(exp.Elts) {
|
|
373
|
-
// Check if it's not a key-value pair
|
|
374
|
-
if _, isKV := exp.Elts[i].(*ast.KeyValueExpr); !isKV {
|
|
375
|
-
directFields[field.Name()] = exp.Elts[i]
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
286
|
+
// Categorize fields into direct, embedded, and explicit embedded
|
|
287
|
+
directFields, embeddedFields, explicitEmbedded, err := c.categorizeStructFields(
|
|
288
|
+
exp, structType, litType, isAnonymousStruct,
|
|
289
|
+
)
|
|
290
|
+
if err != nil {
|
|
291
|
+
return err
|
|
380
292
|
}
|
|
381
293
|
|
|
382
294
|
// Write the object literal
|
|
@@ -388,107 +300,9 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
|
|
|
388
300
|
c.tsw.WriteLiterally("({")
|
|
389
301
|
}
|
|
390
302
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
directKeys := make([]string, 0, len(directFields))
|
|
395
|
-
for k := range directFields {
|
|
396
|
-
// Skip embedded struct names - we'll handle those separately
|
|
397
|
-
if _, isEmbedded := embeddedFields[k]; !isEmbedded {
|
|
398
|
-
directKeys = append(directKeys, k)
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
slices.Sort(directKeys)
|
|
402
|
-
for _, keyName := range directKeys {
|
|
403
|
-
if firstFieldWritten {
|
|
404
|
-
c.tsw.WriteLiterally(", ")
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Convert field name for protobuf types
|
|
408
|
-
fieldName := c.convertProtobufFieldNameInLiteral(keyName, litType)
|
|
409
|
-
|
|
410
|
-
c.tsw.WriteLiterally(fieldName)
|
|
411
|
-
c.tsw.WriteLiterally(": ")
|
|
412
|
-
if err := c.WriteVarRefedValue(directFields[keyName]); err != nil {
|
|
413
|
-
return err
|
|
414
|
-
}
|
|
415
|
-
firstFieldWritten = true
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Write explicitly initialized embedded structs
|
|
419
|
-
explicitKeys := make([]string, 0, len(explicitEmbedded))
|
|
420
|
-
for k := range explicitEmbedded {
|
|
421
|
-
explicitKeys = append(explicitKeys, k)
|
|
422
|
-
}
|
|
423
|
-
slices.Sort(explicitKeys)
|
|
424
|
-
for _, embeddedName := range explicitKeys {
|
|
425
|
-
if firstFieldWritten {
|
|
426
|
-
c.tsw.WriteLiterally(", ")
|
|
427
|
-
}
|
|
428
|
-
c.tsw.WriteLiterally(embeddedName)
|
|
429
|
-
c.tsw.WriteLiterally(": ")
|
|
430
|
-
|
|
431
|
-
// Check if the embedded value is a composite literal for a struct
|
|
432
|
-
// If so, extract the fields and write them directly
|
|
433
|
-
if compLit, ok := explicitEmbedded[embeddedName].(*ast.CompositeLit); ok {
|
|
434
|
-
// Write initialization fields directly without the 'new Constructor'
|
|
435
|
-
c.tsw.WriteLiterally("{")
|
|
436
|
-
for i, elem := range compLit.Elts {
|
|
437
|
-
if i > 0 {
|
|
438
|
-
c.tsw.WriteLiterally(", ")
|
|
439
|
-
}
|
|
440
|
-
if err := c.WriteVarRefedValue(elem); err != nil {
|
|
441
|
-
return err
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
c.tsw.WriteLiterally("}")
|
|
445
|
-
} else {
|
|
446
|
-
// Not a composite literal, write it normally
|
|
447
|
-
if err := c.WriteVarRefedValue(explicitEmbedded[embeddedName]); err != nil {
|
|
448
|
-
return err
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
firstFieldWritten = true
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Write embedded fields for structs that weren't explicitly initialized
|
|
455
|
-
embeddedKeys := make([]string, 0, len(embeddedFields))
|
|
456
|
-
for k := range embeddedFields {
|
|
457
|
-
// Skip embedded structs that were explicitly initialized
|
|
458
|
-
if _, wasExplicit := explicitEmbedded[k]; !wasExplicit {
|
|
459
|
-
embeddedKeys = append(embeddedKeys, k)
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
slices.Sort(embeddedKeys)
|
|
463
|
-
for _, embeddedPropName := range embeddedKeys {
|
|
464
|
-
fieldsMap := embeddedFields[embeddedPropName]
|
|
465
|
-
if len(fieldsMap) == 0 {
|
|
466
|
-
continue
|
|
467
|
-
} // Skip empty embedded initializers
|
|
468
|
-
|
|
469
|
-
if firstFieldWritten {
|
|
470
|
-
c.tsw.WriteLiterally(", ")
|
|
471
|
-
}
|
|
472
|
-
c.tsw.WriteLiterally(embeddedPropName) // Use the Type name as the property key
|
|
473
|
-
c.tsw.WriteLiterally(": {")
|
|
474
|
-
|
|
475
|
-
innerKeys := make([]string, 0, len(fieldsMap))
|
|
476
|
-
for k := range fieldsMap {
|
|
477
|
-
innerKeys = append(innerKeys, k)
|
|
478
|
-
}
|
|
479
|
-
slices.Sort(innerKeys)
|
|
480
|
-
for i, keyName := range innerKeys {
|
|
481
|
-
if i > 0 {
|
|
482
|
-
c.tsw.WriteLiterally(", ")
|
|
483
|
-
}
|
|
484
|
-
c.tsw.WriteLiterally(keyName) // Field name within the embedded struct
|
|
485
|
-
c.tsw.WriteLiterally(": ")
|
|
486
|
-
if err := c.WriteVarRefedValue(fieldsMap[keyName]); err != nil {
|
|
487
|
-
return err
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
c.tsw.WriteLiterally("}")
|
|
491
|
-
firstFieldWritten = true
|
|
303
|
+
// Write all fields
|
|
304
|
+
if err := c.writeStructLiteralFields(directFields, embeddedFields, explicitEmbedded, litType); err != nil {
|
|
305
|
+
return err
|
|
492
306
|
}
|
|
493
307
|
|
|
494
308
|
// Close the object literal
|
|
@@ -714,3 +528,226 @@ func (c *GoToTSCompiler) evaluateConstantExpr(expr ast.Expr) interface{} {
|
|
|
714
528
|
}
|
|
715
529
|
return nil
|
|
716
530
|
}
|
|
531
|
+
|
|
532
|
+
// categorizeStructFields organizes the elements of a struct composite literal into
|
|
533
|
+
// three categories: direct fields, embedded fields, and explicitly initialized embedded structs.
|
|
534
|
+
// Returns maps for direct fields, embedded fields (nested map), and explicit embedded initializations.
|
|
535
|
+
func (c *GoToTSCompiler) categorizeStructFields(
|
|
536
|
+
exp *ast.CompositeLit,
|
|
537
|
+
structType *types.Struct,
|
|
538
|
+
litType types.Type,
|
|
539
|
+
isAnonymousStruct bool,
|
|
540
|
+
) (
|
|
541
|
+
directFields map[string]ast.Expr,
|
|
542
|
+
embeddedFields map[string]map[string]ast.Expr,
|
|
543
|
+
explicitEmbedded map[string]ast.Expr,
|
|
544
|
+
err error,
|
|
545
|
+
) {
|
|
546
|
+
directFields = make(map[string]ast.Expr)
|
|
547
|
+
embeddedFields = make(map[string]map[string]ast.Expr)
|
|
548
|
+
explicitEmbedded = make(map[string]ast.Expr)
|
|
549
|
+
|
|
550
|
+
// Pre-populate embeddedFields map keys using the correct property name
|
|
551
|
+
for i := 0; i < structType.NumFields(); i++ {
|
|
552
|
+
field := structType.Field(i)
|
|
553
|
+
if field.Anonymous() {
|
|
554
|
+
fieldType := field.Type()
|
|
555
|
+
if ptr, ok := fieldType.(*types.Pointer); ok {
|
|
556
|
+
fieldType = ptr.Elem()
|
|
557
|
+
}
|
|
558
|
+
if named, ok := fieldType.(*types.Named); ok {
|
|
559
|
+
// Use the type name as the property name in TS
|
|
560
|
+
embeddedPropName := named.Obj().Name()
|
|
561
|
+
embeddedFields[embeddedPropName] = make(map[string]ast.Expr)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Group literal elements by direct vs embedded fields
|
|
567
|
+
for _, elt := range exp.Elts {
|
|
568
|
+
kv, ok := elt.(*ast.KeyValueExpr)
|
|
569
|
+
if !ok {
|
|
570
|
+
continue
|
|
571
|
+
} // Skip non-key-value
|
|
572
|
+
keyIdent, ok := kv.Key.(*ast.Ident)
|
|
573
|
+
if !ok {
|
|
574
|
+
continue
|
|
575
|
+
} // Skip non-ident keys
|
|
576
|
+
keyName := keyIdent.Name
|
|
577
|
+
|
|
578
|
+
// Check if this is an explicit embedded struct initialization
|
|
579
|
+
// e.g., Person: Person{...} or Person: personVar
|
|
580
|
+
if _, isEmbedded := embeddedFields[keyName]; isEmbedded {
|
|
581
|
+
// This is an explicit initialization of an embedded struct
|
|
582
|
+
explicitEmbedded[keyName] = kv.Value
|
|
583
|
+
continue
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
isDirectField := false
|
|
587
|
+
for i := range structType.NumFields() {
|
|
588
|
+
field := structType.Field(i)
|
|
589
|
+
if field.Name() == keyName {
|
|
590
|
+
isDirectField = true
|
|
591
|
+
directFields[keyName] = kv.Value
|
|
592
|
+
break
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// For anonymous structs, all fields are direct fields
|
|
597
|
+
if isAnonymousStruct {
|
|
598
|
+
directFields[keyName] = kv.Value
|
|
599
|
+
isDirectField = true
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// If not a direct field, return an error
|
|
603
|
+
if !isDirectField {
|
|
604
|
+
return nil, nil, nil, fmt.Errorf("field %s not found in type %s for composite literal",
|
|
605
|
+
keyName, litType.String())
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Handle the case where an anonymous struct has values without keys
|
|
610
|
+
// This block processes non-key-value elements and associates them with struct fields.
|
|
611
|
+
if isAnonymousStruct && len(exp.Elts) > 0 && len(directFields) == 0 {
|
|
612
|
+
// Check if any elements in the composite literal are not key-value pairs.
|
|
613
|
+
hasNonKeyValueElts := false
|
|
614
|
+
for _, elt := range exp.Elts {
|
|
615
|
+
// If an element is not a key-value pair, set the flag to true.
|
|
616
|
+
if _, isKV := elt.(*ast.KeyValueExpr); !isKV {
|
|
617
|
+
hasNonKeyValueElts = true
|
|
618
|
+
break
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if hasNonKeyValueElts {
|
|
623
|
+
// Get the fields from the struct type
|
|
624
|
+
for i := 0; i < structType.NumFields(); i++ {
|
|
625
|
+
field := structType.Field(i)
|
|
626
|
+
// If we have a value for this field position
|
|
627
|
+
if i < len(exp.Elts) {
|
|
628
|
+
// Check if it's not a key-value pair
|
|
629
|
+
if _, isKV := exp.Elts[i].(*ast.KeyValueExpr); !isKV {
|
|
630
|
+
directFields[field.Name()] = exp.Elts[i]
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return directFields, embeddedFields, explicitEmbedded, nil
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// writeStructLiteralFields writes the field initializations for a struct composite literal.
|
|
641
|
+
// It handles direct fields, explicitly initialized embedded structs, and implicitly initialized
|
|
642
|
+
// embedded fields, writing them in sorted order.
|
|
643
|
+
func (c *GoToTSCompiler) writeStructLiteralFields(
|
|
644
|
+
directFields map[string]ast.Expr,
|
|
645
|
+
embeddedFields map[string]map[string]ast.Expr,
|
|
646
|
+
explicitEmbedded map[string]ast.Expr,
|
|
647
|
+
litType types.Type,
|
|
648
|
+
) error {
|
|
649
|
+
firstFieldWritten := false
|
|
650
|
+
|
|
651
|
+
// Write direct fields that aren't embedded struct names
|
|
652
|
+
directKeys := make([]string, 0, len(directFields))
|
|
653
|
+
for k := range directFields {
|
|
654
|
+
// Skip embedded struct names - we'll handle those separately
|
|
655
|
+
if _, isEmbedded := embeddedFields[k]; !isEmbedded {
|
|
656
|
+
directKeys = append(directKeys, k)
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
slices.Sort(directKeys)
|
|
660
|
+
for _, keyName := range directKeys {
|
|
661
|
+
if firstFieldWritten {
|
|
662
|
+
c.tsw.WriteLiterally(", ")
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Convert field name for protobuf types
|
|
666
|
+
fieldName := c.convertProtobufFieldNameInLiteral(keyName, litType)
|
|
667
|
+
|
|
668
|
+
c.tsw.WriteLiterally(fieldName)
|
|
669
|
+
c.tsw.WriteLiterally(": ")
|
|
670
|
+
if err := c.WriteVarRefedValue(directFields[keyName]); err != nil {
|
|
671
|
+
return err
|
|
672
|
+
}
|
|
673
|
+
firstFieldWritten = true
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Write explicitly initialized embedded structs
|
|
677
|
+
explicitKeys := make([]string, 0, len(explicitEmbedded))
|
|
678
|
+
for k := range explicitEmbedded {
|
|
679
|
+
explicitKeys = append(explicitKeys, k)
|
|
680
|
+
}
|
|
681
|
+
slices.Sort(explicitKeys)
|
|
682
|
+
for _, embeddedName := range explicitKeys {
|
|
683
|
+
if firstFieldWritten {
|
|
684
|
+
c.tsw.WriteLiterally(", ")
|
|
685
|
+
}
|
|
686
|
+
c.tsw.WriteLiterally(embeddedName)
|
|
687
|
+
c.tsw.WriteLiterally(": ")
|
|
688
|
+
|
|
689
|
+
// Check if the embedded value is a composite literal for a struct
|
|
690
|
+
// If so, extract the fields and write them directly
|
|
691
|
+
if compLit, ok := explicitEmbedded[embeddedName].(*ast.CompositeLit); ok {
|
|
692
|
+
// Write initialization fields directly without the 'new Constructor'
|
|
693
|
+
c.tsw.WriteLiterally("{")
|
|
694
|
+
for i, elem := range compLit.Elts {
|
|
695
|
+
if i > 0 {
|
|
696
|
+
c.tsw.WriteLiterally(", ")
|
|
697
|
+
}
|
|
698
|
+
if err := c.WriteVarRefedValue(elem); err != nil {
|
|
699
|
+
return err
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
c.tsw.WriteLiterally("}")
|
|
703
|
+
} else {
|
|
704
|
+
// Not a composite literal, write it normally
|
|
705
|
+
if err := c.WriteVarRefedValue(explicitEmbedded[embeddedName]); err != nil {
|
|
706
|
+
return err
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
firstFieldWritten = true
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Write embedded fields for structs that weren't explicitly initialized
|
|
713
|
+
embeddedKeys := make([]string, 0, len(embeddedFields))
|
|
714
|
+
for k := range embeddedFields {
|
|
715
|
+
// Skip embedded structs that were explicitly initialized
|
|
716
|
+
if _, wasExplicit := explicitEmbedded[k]; !wasExplicit {
|
|
717
|
+
embeddedKeys = append(embeddedKeys, k)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
slices.Sort(embeddedKeys)
|
|
721
|
+
for _, embeddedPropName := range embeddedKeys {
|
|
722
|
+
fieldsMap := embeddedFields[embeddedPropName]
|
|
723
|
+
if len(fieldsMap) == 0 {
|
|
724
|
+
continue
|
|
725
|
+
} // Skip empty embedded initializers
|
|
726
|
+
|
|
727
|
+
if firstFieldWritten {
|
|
728
|
+
c.tsw.WriteLiterally(", ")
|
|
729
|
+
}
|
|
730
|
+
c.tsw.WriteLiterally(embeddedPropName) // Use the Type name as the property key
|
|
731
|
+
c.tsw.WriteLiterally(": {")
|
|
732
|
+
|
|
733
|
+
innerKeys := make([]string, 0, len(fieldsMap))
|
|
734
|
+
for k := range fieldsMap {
|
|
735
|
+
innerKeys = append(innerKeys, k)
|
|
736
|
+
}
|
|
737
|
+
slices.Sort(innerKeys)
|
|
738
|
+
for i, keyName := range innerKeys {
|
|
739
|
+
if i > 0 {
|
|
740
|
+
c.tsw.WriteLiterally(", ")
|
|
741
|
+
}
|
|
742
|
+
c.tsw.WriteLiterally(keyName) // Field name within the embedded struct
|
|
743
|
+
c.tsw.WriteLiterally(": ")
|
|
744
|
+
if err := c.WriteVarRefedValue(fieldsMap[keyName]); err != nil {
|
|
745
|
+
return err
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
c.tsw.WriteLiterally("}")
|
|
749
|
+
firstFieldWritten = true
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return nil
|
|
753
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import "go/types"
|
|
4
|
+
|
|
5
|
+
// ConstraintInfo holds information about types found in an interface constraint
|
|
6
|
+
type ConstraintInfo struct {
|
|
7
|
+
HasMap bool
|
|
8
|
+
HasSlice bool
|
|
9
|
+
HasString bool
|
|
10
|
+
HasByteSlice bool
|
|
11
|
+
MapValueType types.Type
|
|
12
|
+
SliceElemType types.Type
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// analyzeConstraint analyzes an interface constraint and returns information about the types it contains
|
|
16
|
+
func analyzeConstraint(iface *types.Interface) ConstraintInfo {
|
|
17
|
+
info := ConstraintInfo{}
|
|
18
|
+
|
|
19
|
+
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
|
20
|
+
embedded := iface.EmbeddedType(i)
|
|
21
|
+
if union, ok := embedded.(*types.Union); ok {
|
|
22
|
+
for j := 0; j < union.Len(); j++ {
|
|
23
|
+
term := union.Term(j)
|
|
24
|
+
checkType(term.Type(), &info)
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
checkType(embedded, &info)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return info
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func checkType(t types.Type, info *ConstraintInfo) {
|
|
35
|
+
underlying := t.Underlying()
|
|
36
|
+
|
|
37
|
+
if mapType, isMap := underlying.(*types.Map); isMap {
|
|
38
|
+
info.HasMap = true
|
|
39
|
+
if info.MapValueType == nil {
|
|
40
|
+
info.MapValueType = mapType.Elem()
|
|
41
|
+
}
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if sliceType, isSlice := underlying.(*types.Slice); isSlice {
|
|
46
|
+
info.HasSlice = true
|
|
47
|
+
if info.SliceElemType == nil {
|
|
48
|
+
info.SliceElemType = sliceType.Elem()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if elemType, isBasic := sliceType.Elem().(*types.Basic); isBasic && elemType.Kind() == types.Uint8 {
|
|
52
|
+
info.HasByteSlice = true
|
|
53
|
+
}
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if basicType, isBasic := underlying.(*types.Basic); isBasic {
|
|
58
|
+
if (basicType.Info() & types.IsString) != 0 {
|
|
59
|
+
info.HasString = true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// hasMapConstraint checks if an interface constraint includes map types
|
|
65
|
+
func hasMapConstraint(iface *types.Interface) bool {
|
|
66
|
+
return analyzeConstraint(iface).HasMap
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// hasSliceConstraint checks if an interface constraint includes slice types
|
|
70
|
+
func hasSliceConstraint(iface *types.Interface) bool {
|
|
71
|
+
return analyzeConstraint(iface).HasSlice
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// hasMixedStringByteConstraint checks if an interface constraint includes both string and []byte types
|
|
75
|
+
func hasMixedStringByteConstraint(iface *types.Interface) bool {
|
|
76
|
+
info := analyzeConstraint(iface)
|
|
77
|
+
return info.HasString && info.HasByteSlice
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// getMapValueTypeFromConstraint extracts the value type from a map constraint
|
|
81
|
+
func getMapValueTypeFromConstraint(iface *types.Interface) types.Type {
|
|
82
|
+
return analyzeConstraint(iface).MapValueType
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// getSliceElementTypeFromConstraint extracts the element type from a slice constraint
|
|
86
|
+
func getSliceElementTypeFromConstraint(iface *types.Interface) types.Type {
|
|
87
|
+
return analyzeConstraint(iface).SliceElemType
|
|
88
|
+
}
|
package/compiler/decl.go
CHANGED
|
@@ -55,7 +55,8 @@ func (c *GoToTSCompiler) WriteDecls(decls []ast.Decl) error {
|
|
|
55
55
|
// Sort type declarations by dependencies
|
|
56
56
|
sortedTypeSpecs, err := c.sortTypeSpecsByDependencies(typeSpecs)
|
|
57
57
|
if err != nil {
|
|
58
|
-
|
|
58
|
+
// Surface the error instead of silently falling back
|
|
59
|
+
return fmt.Errorf("circular dependency detected sorting type declarations: %w", err)
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
// Sort variable declarations by type dependencies
|
|
@@ -147,12 +148,20 @@ func (c *GoToTSCompiler) sortTypeSpecsByDependencies(typeSpecs []*ast.TypeSpec)
|
|
|
147
148
|
return result, nil
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
// extractTypeDependencies extracts
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// -
|
|
155
|
-
//
|
|
151
|
+
// extractTypeDependencies extracts only the dependencies that cause TypeScript initialization order issues.
|
|
152
|
+
// These are dependencies where the constructor of one type directly instantiates another type.
|
|
153
|
+
//
|
|
154
|
+
// TRUE dependencies (cause initialization issues):
|
|
155
|
+
// - Direct struct fields (non-pointer): type A struct { b B } -> A constructor calls new B()
|
|
156
|
+
// - Embedded struct fields: type A struct { B } -> A constructor calls new B()
|
|
157
|
+
// - Direct type aliases: type A B -> A directly wraps B
|
|
158
|
+
// - Array/slice of structs: type A []B -> needs B for default values
|
|
159
|
+
//
|
|
160
|
+
// FALSE dependencies (don't cause initialization issues):
|
|
161
|
+
// - Pointer fields: type A struct { b *B } -> just stores reference, no constructor call
|
|
162
|
+
// - Interface fields: type A struct { b SomeInterface } -> stores interface, no concrete instantiation
|
|
163
|
+
// - Map types: type A map[K]V -> map initialized empty, no constructor calls
|
|
164
|
+
// - Array/slice of pointers: type A []*B -> array of pointers, no constructor calls
|
|
156
165
|
func (c *GoToTSCompiler) extractTypeDependencies(typeExpr ast.Expr, typeSpecMap map[string]*ast.TypeSpec) []string {
|
|
157
166
|
var deps []string
|
|
158
167
|
|
|
@@ -164,35 +173,32 @@ func (c *GoToTSCompiler) extractTypeDependencies(typeExpr ast.Expr, typeSpecMap
|
|
|
164
173
|
}
|
|
165
174
|
|
|
166
175
|
case *ast.StructType:
|
|
167
|
-
// Struct type - check field types
|
|
176
|
+
// Struct type - check field types, but be more selective
|
|
168
177
|
if t.Fields != nil {
|
|
169
178
|
for _, field := range t.Fields.List {
|
|
170
|
-
fieldDeps := c.
|
|
179
|
+
fieldDeps := c.extractStructFieldDependencies(field.Type, typeSpecMap)
|
|
171
180
|
deps = append(deps, fieldDeps...)
|
|
172
181
|
}
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
case *ast.ArrayType:
|
|
176
|
-
// Array type -
|
|
177
|
-
|
|
178
|
-
|
|
185
|
+
// Array/slice type - only depends on element type if element is a struct (not pointer)
|
|
186
|
+
if !c.isPointerType(t.Elt) {
|
|
187
|
+
elemDeps := c.extractTypeDependencies(t.Elt, typeSpecMap)
|
|
188
|
+
deps = append(deps, elemDeps...)
|
|
189
|
+
}
|
|
190
|
+
// Arrays of pointers don't create initialization dependencies
|
|
179
191
|
|
|
180
192
|
case *ast.StarExpr:
|
|
181
|
-
// Pointer
|
|
182
|
-
|
|
183
|
-
deps = append(deps, ptrDeps...)
|
|
193
|
+
// Pointer types don't create initialization dependencies
|
|
194
|
+
// The pointed-to type doesn't need to be initialized when creating a pointer field
|
|
184
195
|
|
|
185
196
|
case *ast.MapType:
|
|
186
|
-
// Map
|
|
187
|
-
|
|
188
|
-
valueDeps := c.extractTypeDependencies(t.Value, typeSpecMap)
|
|
189
|
-
deps = append(deps, keyDeps...)
|
|
190
|
-
deps = append(deps, valueDeps...)
|
|
197
|
+
// Map types don't create initialization dependencies
|
|
198
|
+
// Maps are initialized empty, no constructor calls needed
|
|
191
199
|
|
|
192
200
|
case *ast.InterfaceType:
|
|
193
|
-
// Interface
|
|
194
|
-
// Only embedded interfaces matter, but those are rare and complex to handle
|
|
195
|
-
// For now, interfaces are considered to have no dependencies
|
|
201
|
+
// Interface types don't create initialization dependencies
|
|
196
202
|
|
|
197
203
|
case *ast.FuncType:
|
|
198
204
|
// Function types don't create initialization dependencies
|
|
@@ -208,6 +214,44 @@ func (c *GoToTSCompiler) extractTypeDependencies(typeExpr ast.Expr, typeSpecMap
|
|
|
208
214
|
return deps
|
|
209
215
|
}
|
|
210
216
|
|
|
217
|
+
// extractStructFieldDependencies extracts dependencies from struct field types
|
|
218
|
+
func (c *GoToTSCompiler) extractStructFieldDependencies(fieldType ast.Expr, typeSpecMap map[string]*ast.TypeSpec) []string {
|
|
219
|
+
var deps []string
|
|
220
|
+
|
|
221
|
+
switch t := fieldType.(type) {
|
|
222
|
+
case *ast.Ident:
|
|
223
|
+
// Direct field type: struct { b B } - this requires B to be initialized
|
|
224
|
+
if _, isLocalType := typeSpecMap[t.Name]; isLocalType {
|
|
225
|
+
deps = append(deps, t.Name)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case *ast.StarExpr:
|
|
229
|
+
// Pointer field: struct { b *B } - this doesn't require B initialization
|
|
230
|
+
// Pointers are just references, no constructor call needed
|
|
231
|
+
|
|
232
|
+
case *ast.ArrayType:
|
|
233
|
+
// Array field: struct { b []B } or struct { b [5]B }
|
|
234
|
+
// Only create dependency if element type is not a pointer
|
|
235
|
+
if !c.isPointerType(t.Elt) {
|
|
236
|
+
elemDeps := c.extractTypeDependencies(t.Elt, typeSpecMap)
|
|
237
|
+
deps = append(deps, elemDeps...)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case *ast.MapType:
|
|
241
|
+
// Map field: struct { b map[K]V } - maps don't require initialization dependencies
|
|
242
|
+
|
|
243
|
+
case *ast.InterfaceType:
|
|
244
|
+
// Interface field: struct { b SomeInterface } - no concrete type dependency
|
|
245
|
+
|
|
246
|
+
case *ast.FuncType:
|
|
247
|
+
// Function field: struct { b func() } - no dependency
|
|
248
|
+
|
|
249
|
+
// Handle other field types as needed
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return deps
|
|
253
|
+
}
|
|
254
|
+
|
|
211
255
|
// sortVarSpecsByTypeDependencies sorts variable declarations based on their type dependencies
|
|
212
256
|
func (c *GoToTSCompiler) sortVarSpecsByTypeDependencies(varSpecs []*ast.ValueSpec, typeSpecs []*ast.TypeSpec) ([]*ast.ValueSpec, error) {
|
|
213
257
|
if len(varSpecs) <= 1 {
|
|
@@ -391,7 +435,21 @@ func (c *GoToTSCompiler) topologicalSort(dependencies map[string][]string) ([]st
|
|
|
391
435
|
|
|
392
436
|
// Check for cycles
|
|
393
437
|
if len(result) != len(dependencies) {
|
|
394
|
-
|
|
438
|
+
// Find the remaining nodes to help debug the circular dependency
|
|
439
|
+
processed := make(map[string]bool)
|
|
440
|
+
for _, name := range result {
|
|
441
|
+
processed[name] = true
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
var remaining []string
|
|
445
|
+
for name := range dependencies {
|
|
446
|
+
if !processed[name] {
|
|
447
|
+
remaining = append(remaining, name)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
sort.Strings(remaining)
|
|
451
|
+
|
|
452
|
+
return nil, fmt.Errorf("circular dependency detected in type declarations. Remaining types: %v", remaining)
|
|
395
453
|
}
|
|
396
454
|
|
|
397
455
|
return result, nil
|