goscript 0.0.78 → 0.0.79

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.
@@ -2756,23 +2756,25 @@ func (v *analysisVisitor) analyzeAllMethodsAsync() {
2756
2756
  // Topologically sort methods by their dependencies
2757
2757
  sorted, cycles := v.topologicalSortMethods(methodCalls)
2758
2758
 
2759
- // Mark methods in cycles - check if they contain async operations
2760
- // We can't rely on the call graph for methods in cycles (circular dependency),
2761
- // but we can still detect if they call async external methods
2762
- //
2763
- // We need to iterate multiple times because methods in cycles can call each other,
2764
- // and we need to propagate async status until no changes occur
2759
+ // Analyze methods in dependency order (dependencies analyzed before dependents)
2760
+ for _, methodKey := range sorted {
2761
+ v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
2762
+ }
2763
+
2764
+ // Analyze methods in cycles after the acyclic portion of the graph is known.
2765
+ // This lets recursive methods observe async callees outside the cycle while
2766
+ // still converging over recursive edges inside the cycle.
2765
2767
  maxIterations := 10
2766
2768
  for range maxIterations {
2767
2769
  changed := false
2768
2770
 
2769
2771
  for _, methodKey := range cycles {
2770
- // For methods in cycles, we need to check their body directly for async operations
2771
2772
  pkg := v.analysis.AllPackages[methodKey.PackagePath]
2772
2773
  if pkg == nil && methodKey.PackagePath == v.pkg.Types.Path() {
2773
2774
  pkg = v.pkg
2774
2775
  }
2775
2776
 
2777
+ isAsync := false
2776
2778
  if pkg != nil {
2777
2779
  var funcDecl *ast.FuncDecl
2778
2780
  if methodKey.ReceiverType == "" {
@@ -2782,37 +2784,29 @@ func (v *analysisVisitor) analyzeAllMethodsAsync() {
2782
2784
  }
2783
2785
 
2784
2786
  if funcDecl != nil && funcDecl.Body != nil {
2785
- // Check if the method contains async operations (including calls to async external methods)
2786
- isAsync := v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2787
-
2788
- // Check if status changed
2789
- if oldStatus, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists || oldStatus != isAsync {
2790
- changed = true
2787
+ isAsync = v.containsAsyncOperationsComplete(funcDecl.Body, pkg)
2788
+ if !isAsync {
2789
+ for _, callee := range methodCalls[methodKey] {
2790
+ if calleeAsync, exists := v.analysis.MethodAsyncStatus[callee]; exists && calleeAsync {
2791
+ isAsync = true
2792
+ break
2793
+ }
2794
+ }
2791
2795
  }
2792
-
2793
- v.analysis.MethodAsyncStatus[methodKey] = isAsync
2794
- continue
2795
2796
  }
2796
2797
  }
2797
2798
 
2798
- // Fallback: mark as sync if we can't analyze the body
2799
- if _, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists {
2800
- v.analysis.MethodAsyncStatus[methodKey] = false
2799
+ if oldStatus, exists := v.analysis.MethodAsyncStatus[methodKey]; !exists || oldStatus != isAsync {
2801
2800
  changed = true
2802
2801
  }
2802
+ v.analysis.MethodAsyncStatus[methodKey] = isAsync
2803
2803
  }
2804
2804
 
2805
- // If no changes in this iteration, we're done
2806
2805
  if !changed {
2807
2806
  break
2808
2807
  }
2809
2808
  }
2810
2809
 
2811
- // Analyze methods in dependency order (dependencies analyzed before dependents)
2812
- for _, methodKey := range sorted {
2813
- v.analyzeMethodAsyncTopological(methodKey, methodCalls[methodKey])
2814
- }
2815
-
2816
2810
  // Track async-returning variables BEFORE analyzing function literals
2817
2811
  // This detects variables assigned from higher-order functions with async function literal args
2818
2812
  // e.g., indirect := sync.OnceValue(asyncFunc)
@@ -3259,6 +3253,11 @@ func (v *analysisVisitor) containsAsyncOperationsComplete(node ast.Node, pkg *pa
3259
3253
  if n == nil {
3260
3254
  return false
3261
3255
  }
3256
+ if n != node {
3257
+ if _, ok := n.(*ast.FuncLit); ok {
3258
+ return false
3259
+ }
3260
+ }
3262
3261
 
3263
3262
  switch s := n.(type) {
3264
3263
  case *ast.SendStmt:
package/compiler/decl.go CHANGED
@@ -1,7 +1,6 @@
1
1
  package compiler
2
2
 
3
3
  import (
4
- "bytes"
5
4
  "fmt"
6
5
  "go/ast"
7
6
  "go/token"
@@ -678,14 +677,6 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
678
677
  }
679
678
  }
680
679
 
681
- if !isAsync {
682
- bodyNeedsAsync, err := c.funcBodyNeedsAsync(decl, true)
683
- if err != nil {
684
- return false, err
685
- }
686
- isAsync = bodyNeedsAsync
687
- }
688
-
689
680
  // Methods are typically public in the TS output
690
681
  c.tsw.WriteLiterally("public ")
691
682
 
@@ -753,31 +744,6 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
753
744
  return isAsync, nil
754
745
  }
755
746
 
756
- // funcBodyNeedsAsync checks whether emitting a function body would generate await.
757
- func (c *GoToTSCompiler) funcBodyNeedsAsync(decl *ast.FuncDecl, isMethod bool) (bool, error) {
758
- if decl.Body == nil {
759
- return false, nil
760
- }
761
-
762
- var body strings.Builder
763
- writer := NewTSCodeWriter(&body)
764
- tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis, c.currentFilePath)
765
-
766
- if isMethod {
767
- if err := tempCompiler.writeMethodBodyWithReceiverBinding(decl, "this"); err != nil {
768
- return false, err
769
- }
770
- } else {
771
- for _, stmt := range decl.Body.List {
772
- if err := tempCompiler.WriteStmt(stmt); err != nil {
773
- return false, err
774
- }
775
- }
776
- }
777
-
778
- return bytes.Contains([]byte(body.String()), []byte("await")), nil
779
- }
780
-
781
747
  // writeMethodBodyWithReceiverBinding writes the method body with optional receiver binding
782
748
  // receiverTarget should be "this" for struct methods or "this._value" for named type methods
783
749
  func (c *GoToTSCompiler) writeMethodBodyWithReceiverBinding(decl *ast.FuncDecl, receiverTarget string) error {
@@ -513,7 +513,8 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
513
513
  }
514
514
 
515
515
  // WriteNamedStructTypeSpec generates a TypeScript class for a named type whose
516
- // underlying type is a struct defined elsewhere.
516
+ // underlying type is another named struct. The emitted class copies the field
517
+ // layout without inheriting the underlying named type's prototype methods.
517
518
  func (c *GoToTSCompiler) WriteNamedStructTypeSpec(a *ast.TypeSpec, named *types.Named) error {
518
519
  isInsideFunction := false
519
520
  if nodeInfo := c.analysis.NodeData[a]; nodeInfo != nil {
@@ -531,8 +532,6 @@ func (c *GoToTSCompiler) WriteNamedStructTypeSpec(a *ast.TypeSpec, named *types.
531
532
  c.WriteTypeParameters(a.TypeParams)
532
533
  }
533
534
 
534
- c.tsw.WriteLiterally(" extends ")
535
- c.WriteTypeExpr(a.Type)
536
535
  c.tsw.WriteLiterally(" ")
537
536
  c.tsw.WriteLine("{")
538
537
  c.tsw.Indent(1)
@@ -543,16 +542,77 @@ func (c *GoToTSCompiler) WriteNamedStructTypeSpec(a *ast.TypeSpec, named *types.
543
542
  return fmt.Errorf("underlying type of %s is not a struct", a.Name.Name)
544
543
  }
545
544
 
546
- c.tsw.WriteLine("constructor(init?: any) {")
545
+ underlyingType := c.pkg.TypesInfo.TypeOf(a.Type)
546
+ underlyingInitType := "{}"
547
+ if underlyingNamed, ok := underlyingType.(*types.Named); ok {
548
+ underlyingInitType = c.generateFlattenedInitTypeString(underlyingNamed, nil)
549
+ }
550
+ underlyingTypeName := c.getASTTypeString(a.Type, underlyingType)
551
+
552
+ for i := 0; i < underlyingStruct.NumFields(); i++ {
553
+ field := underlyingStruct.Field(i)
554
+ if field.Anonymous() {
555
+ continue
556
+ }
557
+ if !field.Exported() && field.Pkg() != c.pkg.Types {
558
+ continue
559
+ }
560
+ c.writeGetterSetter(field.Name(), field.Type(), nil, nil, nil)
561
+ }
562
+
563
+ for field := range underlyingStruct.Fields() {
564
+ if field.Anonymous() {
565
+ fieldKeyName := c.getEmbeddedFieldKeyName(field.Type())
566
+ c.writeGetterSetter(fieldKeyName, field.Type(), nil, nil, nil)
567
+ }
568
+ }
569
+
570
+ c.tsw.WriteLiterally("public _fields: {")
547
571
  c.tsw.Indent(1)
548
- c.tsw.WriteLine("super(init)")
572
+ c.tsw.WriteLine("")
573
+ for i := 0; i < underlyingStruct.NumFields(); i++ {
574
+ field := underlyingStruct.Field(i)
575
+ fieldKeyName := field.Name()
576
+ if field.Anonymous() {
577
+ fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
578
+ }
579
+ if fieldKeyName == "_" {
580
+ continue
581
+ }
582
+ c.tsw.WriteLinef("%s: $.VarRef<%s>;", fieldKeyName, c.getTypeString(field.Type()))
583
+ }
549
584
  c.tsw.Indent(-1)
550
585
  c.tsw.WriteLine("}")
551
586
  c.tsw.WriteLine("")
552
587
 
553
- c.tsw.WriteLinef("public clone(): %s {", className)
588
+ c.tsw.WriteLinef("constructor(init?: Partial<%s>) {", underlyingInitType)
589
+ c.tsw.Indent(1)
590
+ c.tsw.WriteLinef("const base = new %s(init)", underlyingTypeName)
591
+ c.tsw.WriteLine("this._fields = base._fields")
592
+ c.tsw.Indent(-1)
593
+ c.tsw.WriteLine("}")
594
+ c.tsw.WriteLine("")
595
+
596
+ var cloneReturnType strings.Builder
597
+ cloneReturnType.WriteString(className)
598
+ if a.TypeParams != nil && len(a.TypeParams.List) > 0 {
599
+ cloneReturnType.WriteString("<")
600
+ first := true
601
+ for _, field := range a.TypeParams.List {
602
+ for _, name := range field.Names {
603
+ if !first {
604
+ cloneReturnType.WriteString(", ")
605
+ }
606
+ first = false
607
+ cloneReturnType.WriteString(name.Name)
608
+ }
609
+ }
610
+ cloneReturnType.WriteString(">")
611
+ }
612
+
613
+ c.tsw.WriteLinef("public clone(): %s {", cloneReturnType.String())
554
614
  c.tsw.Indent(1)
555
- c.tsw.WriteLinef("const cloned = new %s()", className)
615
+ c.tsw.WriteLinef("const cloned = new %s()", cloneReturnType.String())
556
616
  c.tsw.WriteLine("cloned._fields = {")
557
617
  c.tsw.Indent(1)
558
618
 
@@ -560,11 +620,9 @@ func (c *GoToTSCompiler) WriteNamedStructTypeSpec(a *ast.TypeSpec, named *types.
560
620
  for i := 0; i < underlyingStruct.NumFields(); i++ {
561
621
  field := underlyingStruct.Field(i)
562
622
  fieldType := field.Type()
563
- var fieldKeyName string
623
+ fieldKeyName := field.Name()
564
624
  if field.Anonymous() {
565
625
  fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
566
- } else {
567
- fieldKeyName = field.Name()
568
626
  }
569
627
  if fieldKeyName == "_" {
570
628
  continue
@@ -615,6 +673,174 @@ func (c *GoToTSCompiler) WriteNamedStructTypeSpec(a *ast.TypeSpec, named *types.
615
673
  }
616
674
  }
617
675
 
676
+ seenPromotedFields := make(map[string]bool)
677
+ directMethods := make(map[string]bool)
678
+ for method := range named.Methods() {
679
+ sig := method.Type().(*types.Signature)
680
+ if sig.Recv() != nil {
681
+ recvType := sig.Recv().Type()
682
+ if namedRecv, ok := recvType.(*types.Named); ok && namedRecv.Obj() == named.Obj() {
683
+ directMethods[method.Name()] = true
684
+ } else if ptrRecv, ok := recvType.(*types.Pointer); ok {
685
+ if namedElem, ok := ptrRecv.Elem().(*types.Named); ok && namedElem.Obj() == named.Obj() {
686
+ directMethods[method.Name()] = true
687
+ }
688
+ }
689
+ }
690
+ }
691
+
692
+ for field := range underlyingStruct.Fields() {
693
+ if !field.Anonymous() {
694
+ continue
695
+ }
696
+
697
+ embeddedFieldType := field.Type()
698
+ embeddedFieldKeyName := c.getEmbeddedFieldKeyName(field.Type())
699
+
700
+ trueEmbeddedType := embeddedFieldType
701
+ if ptr, isPtr := trueEmbeddedType.(*types.Pointer); isPtr {
702
+ trueEmbeddedType = ptr.Elem()
703
+ }
704
+ if _, isNamed := trueEmbeddedType.(*types.Named); !isNamed {
705
+ continue
706
+ }
707
+
708
+ if namedEmbedded, ok := trueEmbeddedType.(*types.Named); ok {
709
+ if underlyingEmbeddedStruct, ok := namedEmbedded.Underlying().(*types.Struct); ok {
710
+ for promotedField := range underlyingEmbeddedStruct.Fields() {
711
+ if !promotedField.Exported() && promotedField.Pkg() != c.pkg.Types {
712
+ continue
713
+ }
714
+ promotedFieldName := promotedField.Name()
715
+ if seenPromotedFields[promotedFieldName] {
716
+ continue
717
+ }
718
+ conflict := false
719
+ for field := range underlyingStruct.Fields() {
720
+ if !field.Anonymous() && field.Name() == promotedFieldName {
721
+ conflict = true
722
+ break
723
+ }
724
+ }
725
+ if conflict {
726
+ continue
727
+ }
728
+
729
+ seenPromotedFields[promotedFieldName] = true
730
+ tsPromotedFieldType := c.getTypeString(promotedField.Type())
731
+ c.tsw.WriteLine("")
732
+ c.tsw.WriteLinef("public get %s(): %s {", promotedFieldName, tsPromotedFieldType)
733
+ c.tsw.Indent(1)
734
+ embeddedFieldTypeUnderlying := embeddedFieldType
735
+ if ptr, isPtr := embeddedFieldTypeUnderlying.(*types.Pointer); isPtr {
736
+ embeddedFieldTypeUnderlying = ptr.Elem()
737
+ }
738
+ if named, isNamed := embeddedFieldTypeUnderlying.(*types.Named); isNamed {
739
+ embeddedFieldTypeUnderlying = named.Underlying()
740
+ }
741
+ if _, isInterface := embeddedFieldTypeUnderlying.(*types.Interface); isInterface {
742
+ c.tsw.WriteLinef("return this.%s!.%s", embeddedFieldKeyName, promotedFieldName)
743
+ } else {
744
+ c.tsw.WriteLinef("return this.%s.%s", embeddedFieldKeyName, promotedFieldName)
745
+ }
746
+ c.tsw.Indent(-1)
747
+ c.tsw.WriteLine("}")
748
+ c.tsw.WriteLinef("public set %s(value: %s) {", promotedFieldName, tsPromotedFieldType)
749
+ c.tsw.Indent(1)
750
+ if _, isInterface := embeddedFieldTypeUnderlying.(*types.Interface); isInterface {
751
+ c.tsw.WriteLinef("this.%s!.%s = value", embeddedFieldKeyName, promotedFieldName)
752
+ } else {
753
+ c.tsw.WriteLinef("this.%s.%s = value", embeddedFieldKeyName, promotedFieldName)
754
+ }
755
+ c.tsw.Indent(-1)
756
+ c.tsw.WriteLine("}")
757
+ }
758
+ }
759
+ }
760
+
761
+ methodSetType := embeddedFieldType
762
+ if _, isPtr := embeddedFieldType.(*types.Pointer); !isPtr {
763
+ if _, isInterface := embeddedFieldType.Underlying().(*types.Interface); !isInterface {
764
+ methodSetType = types.NewPointer(embeddedFieldType)
765
+ }
766
+ }
767
+ embeddedMethodSet := types.NewMethodSet(methodSetType)
768
+ for methodSelection := range embeddedMethodSet.Methods() {
769
+ method := methodSelection.Obj().(*types.Func)
770
+ methodName := method.Name()
771
+
772
+ if len(methodSelection.Index()) == 1 && !directMethods[methodName] && !seenPromotedFields[methodName] {
773
+ conflictWithField := false
774
+ for field := range underlyingStruct.Fields() {
775
+ if !field.Anonymous() && field.Name() == methodName {
776
+ conflictWithField = true
777
+ break
778
+ }
779
+ }
780
+ if conflictWithField {
781
+ continue
782
+ }
783
+
784
+ seenPromotedFields[methodName] = true
785
+ sig := method.Type().(*types.Signature)
786
+ c.tsw.WriteLine("")
787
+ c.tsw.WriteLiterally("public ")
788
+ c.tsw.WriteLiterally(methodName)
789
+ c.tsw.WriteLiterally("(")
790
+ params := sig.Params()
791
+ paramNames := make([]string, params.Len())
792
+ for j := 0; j < params.Len(); j++ {
793
+ param := params.At(j)
794
+ paramName := param.Name()
795
+ if paramName == "" || paramName == "_" {
796
+ paramName = fmt.Sprintf("_p%d", j)
797
+ }
798
+ paramNames[j] = paramName
799
+ if j > 0 {
800
+ c.tsw.WriteLiterally(", ")
801
+ }
802
+ c.tsw.WriteLiterally(paramName)
803
+ c.tsw.WriteLiterally(": ")
804
+ c.WriteGoType(param.Type(), GoTypeContextGeneral)
805
+ }
806
+ c.tsw.WriteLiterally(")")
807
+ results := sig.Results()
808
+ if results.Len() > 0 {
809
+ c.tsw.WriteLiterally(": ")
810
+ if results.Len() == 1 {
811
+ c.WriteGoType(results.At(0).Type(), GoTypeContextFunctionReturn)
812
+ } else {
813
+ c.tsw.WriteLiterally("[")
814
+ for j := 0; j < results.Len(); j++ {
815
+ if j > 0 {
816
+ c.tsw.WriteLiterally(", ")
817
+ }
818
+ c.WriteGoType(results.At(j).Type(), GoTypeContextFunctionReturn)
819
+ }
820
+ c.tsw.WriteLiterally("]")
821
+ }
822
+ } else {
823
+ c.tsw.WriteLiterally(": void")
824
+ }
825
+ c.tsw.WriteLine(" {")
826
+ c.tsw.Indent(1)
827
+ if results.Len() > 0 {
828
+ c.tsw.WriteLiterally("return ")
829
+ }
830
+
831
+ assertionPrefix := "this.%s"
832
+ if _, isInterface := embeddedFieldType.Underlying().(*types.Interface); isInterface {
833
+ assertionPrefix = "this.%s!"
834
+ }
835
+ c.tsw.WriteLiterallyf(assertionPrefix+".%s(%s)", embeddedFieldKeyName, methodName, strings.Join(paramNames, ", "))
836
+
837
+ c.tsw.WriteLine("")
838
+ c.tsw.Indent(-1)
839
+ c.tsw.WriteLine("}")
840
+ }
841
+ }
842
+ }
843
+
618
844
  c.tsw.WriteLine("")
619
845
  c.tsw.WriteLine("// Register this type with the runtime type system")
620
846
 
@@ -652,11 +878,9 @@ func (c *GoToTSCompiler) WriteNamedStructTypeSpec(a *ast.TypeSpec, named *types.
652
878
  firstField := true
653
879
  for i := 0; i < underlyingStruct.NumFields(); i++ {
654
880
  field := underlyingStruct.Field(i)
655
- var fieldKeyName string
881
+ fieldKeyName := field.Name()
656
882
  if field.Anonymous() {
657
883
  fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
658
- } else {
659
- fieldKeyName = field.Name()
660
884
  }
661
885
  if fieldKeyName == "_" {
662
886
  continue
package/compiler/spec.go CHANGED
@@ -370,13 +370,6 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
370
370
  if obj := c.pkg.TypesInfo.Defs[funcDecl.Name]; obj != nil {
371
371
  isAsync = c.analysis.IsAsyncFunc(obj)
372
372
  }
373
- if !isAsync {
374
- bodyNeedsAsync, err := c.funcBodyNeedsAsync(funcDecl, false)
375
- if err != nil {
376
- return err
377
- }
378
- isAsync = bodyNeedsAsync
379
- }
380
373
  if isAsync {
381
374
  c.tsw.WriteLiterally("async ")
382
375
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "goscript",
3
3
  "description": "Go to TypeScript transpiler",
4
- "version": "0.0.78",
4
+ "version": "0.0.79",
5
5
  "author": {
6
6
  "name": "Aperture Robotics LLC.",
7
7
  "email": "support@aperture.us",