goscript 0.0.21 → 0.0.23

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.
@@ -0,0 +1,209 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+ "strings"
8
+ )
9
+
10
+ // writeTypeAssert handles the Go type assertion with comma-ok idiom in an
11
+ // assignment context: `value, ok := interfaceExpr.(AssertedType)` (or with `=`).
12
+ // It translates this to a TypeScript destructuring assignment (or declaration if `tok`
13
+ // is `token.DEFINE` for `:=`) using the `$.typeAssert` runtime helper.
14
+ //
15
+ // The generated TypeScript is:
16
+ // `[let] { value: valueName, ok: okName } = $.typeAssert<AssertedType_ts>(interfaceExpr_ts, 'AssertedTypeName');`
17
+ //
18
+ // - `AssertedType_ts` is the TypeScript translation of `AssertedType`.
19
+ // - `interfaceExpr_ts` is the TypeScript translation of `interfaceExpr`.
20
+ // - `'AssertedTypeName'` is a string representation of the asserted type name,
21
+ // obtained via `getTypeNameString`, used for runtime error messages.
22
+ // - `valueName` and `okName` are the Go variable names from the LHS.
23
+ // - Blank identifiers (`_`) on the LHS are handled by omitting the corresponding
24
+ // property in the destructuring pattern (e.g., `{ ok: okName } = ...` if `value` is blank).
25
+ // - If `tok` is not `token.DEFINE` (i.e., for regular assignment `=`), the entire
26
+ // destructuring assignment is wrapped in parentheses `(...)` to make it a valid
27
+ // expression if needed, though typically assignments are statements.
28
+ //
29
+ // The statement is terminated with a newline.
30
+ func (c *GoToTSCompiler) writeTypeAssert(lhs []ast.Expr, typeAssertExpr *ast.TypeAssertExpr, tok token.Token) error {
31
+ interfaceExpr := typeAssertExpr.X
32
+ assertedType := typeAssertExpr.Type
33
+
34
+ // Unwrap parenthesized expressions to handle cases like r.((<-chan T))
35
+ for {
36
+ if parenExpr, ok := assertedType.(*ast.ParenExpr); ok {
37
+ assertedType = parenExpr.X
38
+ } else {
39
+ break
40
+ }
41
+ }
42
+
43
+ // Ensure LHS has exactly two expressions (value and ok)
44
+ if len(lhs) != 2 {
45
+ return fmt.Errorf("type assertion assignment requires exactly 2 variables on LHS, got %d", len(lhs))
46
+ }
47
+
48
+ var okIsBlank bool
49
+ var okName string
50
+
51
+ okExpr := lhs[1]
52
+ okIdent, ok := okExpr.(*ast.Ident)
53
+ if !ok {
54
+ return fmt.Errorf("ok expression is not an identifier: %T", okExpr)
55
+ }
56
+ okIsBlank = okIdent.Name == "_"
57
+ okName = okIdent.Name
58
+
59
+ valueExpr := lhs[0]
60
+
61
+ // Determine if 'ok' variable is new in 'tok == token.DEFINE' context.
62
+ // This uses types.Info.Defs to see if the identifier is defined by this statement.
63
+ var okIsNewInDefine bool
64
+ if tok == token.DEFINE && !okIsBlank {
65
+ if c.pkg.TypesInfo.Defs[okIdent] != nil {
66
+ okIsNewInDefine = true
67
+ }
68
+ }
69
+
70
+ switch vLHS := valueExpr.(type) {
71
+ case *ast.Ident:
72
+ var valueIsBlank bool
73
+ var valueName string
74
+ valueIdent := vLHS
75
+ valueIsBlank = (valueIdent.Name == "_")
76
+ valueName = valueIdent.Name
77
+
78
+ var valueIsNewInDefine bool
79
+ if tok == token.DEFINE && !valueIsBlank {
80
+ if c.pkg.TypesInfo.Defs[valueIdent] != nil { // valueIdent is defined by this statement
81
+ valueIsNewInDefine = true
82
+ }
83
+ }
84
+
85
+ writeEndParen := false // For wrapping assignment in parens to make it an expression
86
+ letDestructure := false // True if 'let { value: v, ok: o } = ...' is appropriate
87
+
88
+ if tok == token.DEFINE {
89
+ anyNewVars := (valueIsNewInDefine && !valueIsBlank) || (okIsNewInDefine && !okIsBlank)
90
+ // allVarsNewOrBlank means suitable for a single `let {v,o} = ...` destructuring
91
+ allVarsNewOrBlank := (valueIsBlank || valueIsNewInDefine) && (okIsBlank || okIsNewInDefine)
92
+
93
+ if allVarsNewOrBlank && anyNewVars {
94
+ letDestructure = true
95
+ } else if anyNewVars { // Mixed: some new, some existing. Declare new ones separately.
96
+ if !valueIsBlank && valueIsNewInDefine {
97
+ c.tsw.WriteLiterally("let ")
98
+ c.tsw.WriteLiterally(valueName)
99
+ c.tsw.WriteLiterally(": ")
100
+ c.WriteTypeExpr(assertedType) // Use WriteTypeExpr for TS type annotation
101
+ c.tsw.WriteLine("")
102
+ }
103
+ if !okIsBlank && okIsNewInDefine {
104
+ c.tsw.WriteLiterally("let ")
105
+ c.tsw.WriteLiterally(okName)
106
+ c.tsw.WriteLiterally(": boolean")
107
+ c.tsw.WriteLine("")
108
+ }
109
+ c.tsw.WriteLiterally("(") // Parenthesize the assignment part
110
+ writeEndParen = true
111
+ } else { // All variables exist
112
+ c.tsw.WriteLiterally("(")
113
+ writeEndParen = true
114
+ }
115
+ } else { // tok == token.ASSIGN
116
+ c.tsw.WriteLiterally("(")
117
+ writeEndParen = true
118
+ }
119
+
120
+ if letDestructure {
121
+ c.tsw.WriteLiterally("let ")
122
+ }
123
+
124
+ // Write the destructuring part: { value: v, ok: o }
125
+ c.tsw.WriteLiterally("{ ")
126
+ parts := []string{}
127
+ if !valueIsBlank {
128
+ parts = append(parts, fmt.Sprintf("value: %s", valueName))
129
+ }
130
+ if !okIsBlank {
131
+ parts = append(parts, fmt.Sprintf("ok: %s", okName))
132
+ }
133
+ c.tsw.WriteLiterally(strings.Join(parts, ", "))
134
+ c.tsw.WriteLiterally(" } = $.typeAssert<")
135
+ c.WriteTypeExpr(assertedType) // Generic: <AssertedTypeTS>
136
+ c.tsw.WriteLiterally(">(")
137
+ if err := c.WriteValueExpr(interfaceExpr); err != nil { // Arg1: interfaceExpr
138
+ return fmt.Errorf("failed to write interface expression in type assertion call: %w", err)
139
+ }
140
+ c.tsw.WriteLiterally(", ")
141
+ c.writeTypeDescription(assertedType) // Arg2: type info for runtime
142
+ c.tsw.WriteLiterally(")")
143
+
144
+ if writeEndParen {
145
+ c.tsw.WriteLiterally(")")
146
+ }
147
+ c.tsw.WriteLine("")
148
+
149
+ case *ast.SelectorExpr:
150
+ // Handle s.field, ok := expr.(Type)
151
+ tempValName := "_gs_ta_val_" // Fixed name for temporary value
152
+ tempOkName := "_gs_ta_ok_" // Fixed name for temporary ok status
153
+
154
+ // Declare temporary variables:
155
+ // let _gs_ta_val_: AssertedTypeTS;
156
+ c.tsw.WriteLiterally("let ")
157
+ c.tsw.WriteLiterally(tempValName)
158
+ c.tsw.WriteLiterally(": ")
159
+ c.WriteTypeExpr(assertedType) // TypeScript type for assertedType
160
+ c.tsw.WriteLine("")
161
+
162
+ // let _gs_ta_ok_: boolean;
163
+ c.tsw.WriteLiterally("let ")
164
+ c.tsw.WriteLiterally(tempOkName)
165
+ c.tsw.WriteLiterally(": boolean")
166
+ c.tsw.WriteLine("")
167
+
168
+ // Perform type assertion into temporary variables:
169
+ // ({ value: _gs_ta_val_, ok: _gs_ta_ok_ } = $.typeAssert<AssertedTypeTS>(expr, "GoTypeStr"));
170
+ c.tsw.WriteLiterally("({ value: ")
171
+ c.tsw.WriteLiterally(tempValName)
172
+ c.tsw.WriteLiterally(", ok: ")
173
+ c.tsw.WriteLiterally(tempOkName)
174
+ c.tsw.WriteLiterally(" } = $.typeAssert<")
175
+ c.WriteTypeExpr(assertedType) // Generic: <AssertedTypeTS>
176
+ c.tsw.WriteLiterally(">(")
177
+ if err := c.WriteValueExpr(interfaceExpr); err != nil { // Arg1: interfaceExpr
178
+ return fmt.Errorf("failed to write interface expression in type assertion call: %w", err)
179
+ }
180
+ c.tsw.WriteLiterally(", ")
181
+ c.writeTypeDescription(assertedType) // Arg2: type info for runtime
182
+ c.tsw.WriteLine("))")
183
+
184
+ // Assign temporary value to the selector expression:
185
+ // s.f = _gs_ta_val_;
186
+ if err := c.WriteValueExpr(vLHS); err != nil { // Writes selector expression (e.g., "s.f")
187
+ return fmt.Errorf("failed to write LHS selector expression in type assertion: %w", err)
188
+ }
189
+ c.tsw.WriteLiterally(" = ")
190
+ c.tsw.WriteLiterally(tempValName)
191
+ c.tsw.WriteLine("")
192
+
193
+ // Assign temporary ok to the ok variable (e.g., okName = _gs_ta_ok_; or let okName = ...)
194
+ if !okIsBlank {
195
+ if okIsNewInDefine { // okIsNewInDefine was determined earlier based on tok == token.DEFINE and Defs check
196
+ c.tsw.WriteLiterally("let ")
197
+ }
198
+ c.tsw.WriteLiterally(okName)
199
+ c.tsw.WriteLiterally(" = ")
200
+ c.tsw.WriteLiterally(tempOkName)
201
+ c.tsw.WriteLine("")
202
+ }
203
+
204
+ default:
205
+ return fmt.Errorf("unhandled LHS expression type for value in type assertion: %T", valueExpr)
206
+ }
207
+
208
+ return nil
209
+ }
@@ -0,0 +1,141 @@
1
+ package compiler
2
+
3
+ import "go/types"
4
+
5
+ // writeTypeInfoObject writes a TypeScript TypeInfo object literal for a given Go type.
6
+ func (c *GoToTSCompiler) writeTypeInfoObject(typ types.Type) {
7
+ if typ == nil {
8
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Basic, name: 'any' }") // Or handle as error
9
+ return
10
+ }
11
+
12
+ // If typ is a *types.Named, handle it by reference to break recursion.
13
+ if namedType, ok := typ.(*types.Named); ok {
14
+ if namedType.Obj().Name() == "error" && namedType.Obj().Pkg() == nil { // Check for builtin error
15
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Interface, name: 'GoError', methods: [{ name: 'Error', args: [], returns: [{ type: { kind: $.TypeKind.Basic, name: 'string' } }] }] }")
16
+ } else {
17
+ // For all other named types, output their name as a string literal.
18
+ // This relies on the type being registered elsewhere (e.g., via registerStructType or registerInterfaceType)
19
+ // so the TypeScript runtime can resolve the reference.
20
+ c.tsw.WriteLiterallyf("%q", namedType.Obj().Name())
21
+ }
22
+ return // Return after handling the named type by reference.
23
+ }
24
+
25
+ // If typ is not *types.Named, process its underlying structure.
26
+ underlying := typ.Underlying()
27
+ switch t := underlying.(type) {
28
+ case *types.Basic:
29
+ tsTypeName, _ := GoBuiltinToTypescript(t.Name())
30
+ if tsTypeName == "" {
31
+ tsTypeName = t.Name() // Fallback
32
+ }
33
+ c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Basic, name: %q }", tsTypeName)
34
+ // Note: The original 'case *types.Named:' here for 'underlying' is intentionally omitted.
35
+ // If typ.Underlying() is *types.Named (e.g. type T1 MyInt; type T2 T1;),
36
+ // then writeTypeInfoObject(typ.Underlying()) would be called in some contexts,
37
+ // and that call would handle it via the top-level *types.Named check.
38
+ case *types.Pointer:
39
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Pointer, elemType: ")
40
+ c.writeTypeInfoObject(t.Elem()) // Recursive call
41
+ c.tsw.WriteLiterally(" }")
42
+ case *types.Slice:
43
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Slice, elemType: ")
44
+ c.writeTypeInfoObject(t.Elem()) // Recursive call
45
+ c.tsw.WriteLiterally(" }")
46
+ case *types.Array:
47
+ c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Array, length: %d, elemType: ", t.Len())
48
+ c.writeTypeInfoObject(t.Elem()) // Recursive call
49
+ c.tsw.WriteLiterally(" }")
50
+ case *types.Map:
51
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Map, keyType: ")
52
+ c.writeTypeInfoObject(t.Key()) // Recursive call
53
+ c.tsw.WriteLiterally(", elemType: ")
54
+ c.writeTypeInfoObject(t.Elem()) // Recursive call
55
+ c.tsw.WriteLiterally(" }")
56
+ case *types.Chan:
57
+ dir := "both"
58
+ if t.Dir() == types.SendOnly {
59
+ dir = "send"
60
+ } else if t.Dir() == types.RecvOnly {
61
+ dir = "receive"
62
+ }
63
+ c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Channel, direction: %q, elemType: ", dir)
64
+ c.writeTypeInfoObject(t.Elem()) // Recursive call
65
+ c.tsw.WriteLiterally(" }")
66
+ case *types.Interface: // Anonymous interface or underlying of a non-named type alias
67
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Interface, methods: [")
68
+ var methods []*types.Func
69
+ for i := 0; i < t.NumExplicitMethods(); i++ {
70
+ methods = append(methods, t.ExplicitMethod(i))
71
+ }
72
+ // TODO: Handle embedded methods for anonymous interfaces if needed.
73
+ c.writeMethodSignatures(methods) // Calls writeMethodSignatures -> writeTypeInfoObject
74
+ c.tsw.WriteLiterally("] }")
75
+ case *types.Signature: // Anonymous func type or underlying of a non-named type alias
76
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Function, params: [")
77
+ for i := 0; i < t.Params().Len(); i++ {
78
+ if i > 0 {
79
+ c.tsw.WriteLiterally(", ")
80
+ }
81
+ c.writeTypeInfoObject(t.Params().At(i).Type()) // Recursive call
82
+ }
83
+ c.tsw.WriteLiterally("], results: [")
84
+ for i := 0; i < t.Results().Len(); i++ {
85
+ if i > 0 {
86
+ c.tsw.WriteLiterally(", ")
87
+ }
88
+ c.writeTypeInfoObject(t.Results().At(i).Type()) // Recursive call
89
+ }
90
+ c.tsw.WriteLiterally("] }")
91
+ case *types.Struct: // Anonymous struct or underlying of a non-named type alias
92
+ c.tsw.WriteLiterally("{ kind: $.TypeKind.Struct, fields: {")
93
+ for i := 0; i < t.NumFields(); i++ {
94
+ if i > 0 {
95
+ c.tsw.WriteLiterally(", ")
96
+ }
97
+ field := t.Field(i)
98
+ c.tsw.WriteLiterallyf("%q: ", field.Name())
99
+ c.writeTypeInfoObject(field.Type()) // Recursive call
100
+ }
101
+ c.tsw.WriteLiterally("}, methods: [] }") // Anonymous structs don't have methods in this context
102
+ default:
103
+ // Fallback, e.g. for types whose underlying isn't one of the above like *types.Tuple or other complex cases.
104
+ c.tsw.WriteLiterallyf("{ kind: $.TypeKind.Basic, name: %q }", typ.String()) // Fallback using the type's string representation
105
+ }
106
+ }
107
+
108
+ // writeMethodSignatures writes an array of TypeScript MethodSignature objects.
109
+ func (c *GoToTSCompiler) writeMethodSignatures(methods []*types.Func) {
110
+ firstMethod := true
111
+ for _, method := range methods {
112
+ if !firstMethod {
113
+ c.tsw.WriteLiterally(", ")
114
+ }
115
+ firstMethod = false
116
+
117
+ sig := method.Type().(*types.Signature)
118
+ c.tsw.WriteLiterallyf("{ name: %q, args: [", method.Name())
119
+ for i := 0; i < sig.Params().Len(); i++ {
120
+ if i > 0 {
121
+ c.tsw.WriteLiterally(", ")
122
+ }
123
+ param := sig.Params().At(i)
124
+ c.tsw.WriteLiterallyf("{ name: %q, type: ", param.Name())
125
+ c.writeTypeInfoObject(param.Type())
126
+ c.tsw.WriteLiterally(" }")
127
+ }
128
+ c.tsw.WriteLiterally("], returns: [")
129
+ for i := 0; i < sig.Results().Len(); i++ {
130
+ if i > 0 {
131
+ c.tsw.WriteLiterally(", ")
132
+ }
133
+ result := sig.Results().At(i)
134
+ // Return parameters in Go often don't have names that are relevant for TS signature matching
135
+ c.tsw.WriteLiterally("{ type: ")
136
+ c.writeTypeInfoObject(result.Type())
137
+ c.tsw.WriteLiterally(" }")
138
+ }
139
+ c.tsw.WriteLiterally("] }")
140
+ }
141
+ }