Skip to content

Commit 93d3f4c

Browse files
committed
fix: named_struct_async_method
Signed-off-by: Christian Stewart <christian@aperture.us>
1 parent dd43cef commit 93d3f4c

15 files changed

Lines changed: 474 additions & 117 deletions

File tree

compiler/decl.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package compiler
22

33
import (
4+
"bytes"
45
"fmt"
56
"go/ast"
67
"go/token"
@@ -677,6 +678,14 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
677678
}
678679
}
679680

681+
if !isAsync {
682+
bodyNeedsAsync, err := c.funcBodyNeedsAsync(decl, true)
683+
if err != nil {
684+
return false, err
685+
}
686+
isAsync = bodyNeedsAsync
687+
}
688+
680689
// Methods are typically public in the TS output
681690
c.tsw.WriteLiterally("public ")
682691

@@ -744,6 +753,31 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
744753
return isAsync, nil
745754
}
746755

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+
747781
// writeMethodBodyWithReceiverBinding writes the method body with optional receiver binding
748782
// receiverTarget should be "this" for struct methods or "this._value" for named type methods
749783
func (c *GoToTSCompiler) writeMethodBodyWithReceiverBinding(decl *ast.FuncDecl, receiverTarget string) error {

compiler/spec-struct.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,179 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
512512
return nil
513513
}
514514

515+
// WriteNamedStructTypeSpec generates a TypeScript class for a named type whose
516+
// underlying type is a struct defined elsewhere.
517+
func (c *GoToTSCompiler) WriteNamedStructTypeSpec(a *ast.TypeSpec, named *types.Named) error {
518+
isInsideFunction := false
519+
if nodeInfo := c.analysis.NodeData[a]; nodeInfo != nil {
520+
isInsideFunction = nodeInfo.IsInsideFunction
521+
}
522+
if !isInsideFunction {
523+
c.tsw.WriteLiterally("export ")
524+
}
525+
c.tsw.WriteLiterally("class ")
526+
if err := c.WriteValueExpr(a.Name); err != nil {
527+
return err
528+
}
529+
530+
if a.TypeParams != nil {
531+
c.WriteTypeParameters(a.TypeParams)
532+
}
533+
534+
c.tsw.WriteLiterally(" extends ")
535+
c.WriteTypeExpr(a.Type)
536+
c.tsw.WriteLiterally(" ")
537+
c.tsw.WriteLine("{")
538+
c.tsw.Indent(1)
539+
540+
className := sanitizeIdentifier(a.Name.Name)
541+
underlyingStruct, ok := named.Underlying().(*types.Struct)
542+
if !ok {
543+
return fmt.Errorf("underlying type of %s is not a struct", a.Name.Name)
544+
}
545+
546+
c.tsw.WriteLine("constructor(init?: any) {")
547+
c.tsw.Indent(1)
548+
c.tsw.WriteLine("super(init)")
549+
c.tsw.Indent(-1)
550+
c.tsw.WriteLine("}")
551+
c.tsw.WriteLine("")
552+
553+
c.tsw.WriteLinef("public clone(): %s {", className)
554+
c.tsw.Indent(1)
555+
c.tsw.WriteLinef("const cloned = new %s()", className)
556+
c.tsw.WriteLine("cloned._fields = {")
557+
c.tsw.Indent(1)
558+
559+
firstFieldWritten := false
560+
for i := 0; i < underlyingStruct.NumFields(); i++ {
561+
field := underlyingStruct.Field(i)
562+
fieldType := field.Type()
563+
var fieldKeyName string
564+
if field.Anonymous() {
565+
fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
566+
} else {
567+
fieldKeyName = field.Name()
568+
}
569+
if fieldKeyName == "_" {
570+
continue
571+
}
572+
573+
if firstFieldWritten {
574+
c.tsw.WriteLine(",")
575+
}
576+
c.writeClonedFieldInitializer(fieldKeyName, fieldType, field.Anonymous())
577+
firstFieldWritten = true
578+
}
579+
if firstFieldWritten {
580+
c.tsw.WriteLine("")
581+
}
582+
583+
c.tsw.Indent(-1)
584+
c.tsw.WriteLine("}")
585+
c.tsw.WriteLine("return cloned")
586+
c.tsw.Indent(-1)
587+
c.tsw.WriteLine("}")
588+
589+
for _, fileSyntax := range c.pkg.Syntax {
590+
for _, decl := range fileSyntax.Decls {
591+
funcDecl, isFunc := decl.(*ast.FuncDecl)
592+
if !isFunc || funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 {
593+
continue
594+
}
595+
recvType := funcDecl.Recv.List[0].Type
596+
if starExpr, ok := recvType.(*ast.StarExpr); ok {
597+
recvType = starExpr.X
598+
}
599+
600+
var recvTypeName string
601+
if ident, ok := recvType.(*ast.Ident); ok {
602+
recvTypeName = sanitizeIdentifier(ident.Name)
603+
} else if indexExpr, ok := recvType.(*ast.IndexExpr); ok {
604+
if ident, ok := indexExpr.X.(*ast.Ident); ok {
605+
recvTypeName = sanitizeIdentifier(ident.Name)
606+
}
607+
}
608+
609+
if recvTypeName == className {
610+
c.tsw.WriteLine("")
611+
if err := c.WriteFuncDeclAsMethod(funcDecl); err != nil {
612+
return err
613+
}
614+
}
615+
}
616+
}
617+
618+
c.tsw.WriteLine("")
619+
c.tsw.WriteLine("// Register this type with the runtime type system")
620+
621+
structName := className
622+
pkgPath := c.pkg.Types.Path()
623+
pkgName := c.pkg.Types.Name()
624+
if pkgPath != "" && pkgName != "main" {
625+
structName = pkgPath + "." + className
626+
} else if pkgName == "main" {
627+
structName = "main." + className
628+
}
629+
630+
c.tsw.WriteLine("static __typeInfo = $.registerStructType(")
631+
c.tsw.WriteLinef(" %q,", structName)
632+
c.tsw.WriteLinef(" new %s(),", className)
633+
c.tsw.WriteLiterally(" [")
634+
635+
var structMethods []*types.Func
636+
for method := range named.Methods() {
637+
sig := method.Type().(*types.Signature)
638+
recv := sig.Recv().Type()
639+
if ptr, ok := recv.(*types.Pointer); ok {
640+
recv = ptr.Elem()
641+
}
642+
if namedRecv, ok := recv.(*types.Named); ok && namedRecv.Obj() == named.Obj() {
643+
structMethods = append(structMethods, method)
644+
}
645+
}
646+
c.writeMethodSignatures(structMethods)
647+
c.tsw.WriteLiterally("],")
648+
c.tsw.WriteLine("")
649+
650+
c.tsw.WriteLinef(" %s,", className)
651+
c.tsw.WriteLiterally(" {")
652+
firstField := true
653+
for i := 0; i < underlyingStruct.NumFields(); i++ {
654+
field := underlyingStruct.Field(i)
655+
var fieldKeyName string
656+
if field.Anonymous() {
657+
fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
658+
} else {
659+
fieldKeyName = field.Name()
660+
}
661+
if fieldKeyName == "_" {
662+
continue
663+
}
664+
if !firstField {
665+
c.tsw.WriteLiterally(", ")
666+
}
667+
firstField = false
668+
c.tsw.WriteLiterallyf("%q: ", fieldKeyName)
669+
670+
tag := underlyingStruct.Tag(i)
671+
if tag != "" {
672+
c.tsw.WriteLiterally("{ type: ")
673+
c.writeTypeInfoObject(field.Type())
674+
c.tsw.WriteLiterallyf(", tag: %q }", tag)
675+
} else {
676+
c.writeTypeInfoObject(field.Type())
677+
}
678+
}
679+
c.tsw.WriteLiterally("}")
680+
c.tsw.WriteLine("")
681+
c.tsw.WriteLine(");")
682+
683+
c.tsw.Indent(-1)
684+
c.tsw.WriteLine("}")
685+
return nil
686+
}
687+
515688
// generateFlattenedInitTypeString generates a TypeScript type string for the
516689
// initialization object passed to a Go struct's constructor (`_init` method in TypeScript).
517690
// The generated type is a `Partial`-like structure, `"{ Field1?: Type1, Field2?: Type2, ... }"`,

compiler/spec.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,20 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
366366

367367
if recvTypeName == className {
368368
c.tsw.WriteLiterally("export function ")
369+
var isAsync bool
370+
if obj := c.pkg.TypesInfo.Defs[funcDecl.Name]; obj != nil {
371+
isAsync = c.analysis.IsAsyncFunc(obj)
372+
}
373+
if !isAsync {
374+
bodyNeedsAsync, err := c.funcBodyNeedsAsync(funcDecl, false)
375+
if err != nil {
376+
return err
377+
}
378+
isAsync = bodyNeedsAsync
379+
}
380+
if isAsync {
381+
c.tsw.WriteLiterally("async ")
382+
}
369383
c.tsw.WriteLiterally(className)
370384
c.tsw.WriteLiterally("_")
371385
c.tsw.WriteLiterally(funcDecl.Name.Name)
@@ -403,6 +417,9 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
403417
// Add return type
404418
if funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) > 0 {
405419
c.tsw.WriteLiterally(": ")
420+
if isAsync {
421+
c.tsw.WriteLiterally("Promise<")
422+
}
406423
if len(funcDecl.Type.Results.List) == 1 {
407424
c.WriteTypeExpr(funcDecl.Type.Results.List[0].Type)
408425
} else {
@@ -424,8 +441,15 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
424441
}
425442
c.tsw.WriteLiterally("]")
426443
}
444+
if isAsync {
445+
c.tsw.WriteLiterally(">")
446+
}
427447
} else {
428-
c.tsw.WriteLiterally(": void")
448+
if isAsync {
449+
c.tsw.WriteLiterally(": Promise<void>")
450+
} else {
451+
c.tsw.WriteLiterally(": void")
452+
}
429453
}
430454

431455
c.tsw.WriteLine(" {")
@@ -476,6 +500,11 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
476500
default:
477501
// Check if this type has receiver methods
478502
if c.hasReceiverMethods(a.Name.Name) {
503+
if named, ok := c.pkg.TypesInfo.Defs[a.Name].Type().(*types.Named); ok {
504+
if _, isStruct := named.Underlying().(*types.Struct); isStruct {
505+
return c.WriteNamedStructTypeSpec(a, named)
506+
}
507+
}
479508
return c.WriteNamedTypeWithMethods(a)
480509
}
481510

tests/deps/encoding/base64/base64.gs.ts

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -128,42 +128,40 @@ export class Encoding {
128128
return
129129
}
130130
/* _ = */ enc.encode
131-
let [di, si] = [0, 0]
132-
let n = (Math.trunc($.len(src) / 3)) * 3
133-
for (; si < n; ) {
131+
for (; $.len(src) >= 3; ) {
134132
// Convert 3x 8bit source bytes into 4 bytes
135-
let val = ((((src![si + 0] as number) << 16) | ((src![si + 1] as number) << 8)) | (src![si + 2] as number))
133+
let val = ((((src![0] as number) << 16) | ((src![1] as number) << 8)) | (src![2] as number))
136134

137-
dst![di + 0] = enc.encode![((val >> 18) & 0x3F)]
138-
dst![di + 1] = enc.encode![((val >> 12) & 0x3F)]
139-
dst![di + 2] = enc.encode![((val >> 6) & 0x3F)]
140-
dst![di + 3] = enc.encode![(val & 0x3F)]
135+
/* _ = */ dst![3] // Eliminate bounds checks below.
136+
dst![0] = enc.encode![((val >> 18) & 0x3F)]
137+
dst![1] = enc.encode![((val >> 12) & 0x3F)]
138+
dst![2] = enc.encode![((val >> 6) & 0x3F)]
139+
dst![3] = enc.encode![(val & 0x3F)]
141140

142-
si += 3
143-
di += 4
141+
src = $.goSlice(src, 3, undefined)
142+
dst = $.goSlice(dst, 4, undefined)
144143
}
145-
let remain = $.len(src) - si
146-
if (remain == 0) {
147-
return
148-
}
149-
let val = ((src![si + 0] as number) << 16)
150-
if (remain == 2) {
151-
val |= ((src![si + 1] as number) << 8)
152-
}
153-
dst![di + 0] = enc.encode![((val >> 18) & 0x3F)]
154-
dst![di + 1] = enc.encode![((val >> 12) & 0x3F)]
155-
switch (remain) {
156-
case 2: {
157-
dst![di + 2] = enc.encode![((val >> 6) & 0x3F)]
144+
switch ($.len(src)) {
145+
case 0: {
146+
return
147+
}
148+
case 1: {
149+
let val = ((src![0] as number) << 16)
150+
dst![0] = enc.encode![((val >> 18) & 0x3F)]
151+
dst![1] = enc.encode![((val >> 12) & 0x3F)]
158152
if (Number(enc.padChar) != -1) {
159-
dst![di + 3] = $.byte(enc.padChar)
153+
dst![2] = $.byte(enc.padChar)
154+
dst![3] = $.byte(enc.padChar)
160155
}
161156
break
162157
}
163-
case 1: {
158+
case 2: {
159+
let val = (((src![0] as number) << 16) | ((src![1] as number) << 8))
160+
dst![0] = enc.encode![((val >> 18) & 0x3F)]
161+
dst![1] = enc.encode![((val >> 12) & 0x3F)]
162+
dst![2] = enc.encode![((val >> 6) & 0x3F)]
164163
if (Number(enc.padChar) != -1) {
165-
dst![di + 2] = $.byte(enc.padChar)
166-
dst![di + 3] = $.byte(enc.padChar)
164+
dst![3] = $.byte(enc.padChar)
167165
}
168166
break
169167
}

0 commit comments

Comments
 (0)