types.go raw
1 package jsbackend
2
3 import (
4 "fmt"
5 "go/types"
6 "strings"
7 )
8
9 // TypeMapper handles Go type to JavaScript representation mapping.
10 type TypeMapper struct {
11 // Track registered types for runtime type info emission.
12 registeredTypes map[string]bool
13 // Type ID counter for anonymous types.
14 anonCounter int
15 }
16
17 // NewTypeMapper creates a new type mapper.
18 func NewTypeMapper() *TypeMapper {
19 return &TypeMapper{
20 registeredTypes: make(map[string]bool),
21 }
22 }
23
24 // TypeID returns a unique string identifier for a Go type.
25 func (tm *TypeMapper) TypeID(t types.Type) string {
26 switch t := t.(type) {
27 case *types.Named:
28 obj := t.Obj()
29 if obj.Pkg() != nil {
30 return obj.Pkg().Path() + "." + obj.Name()
31 }
32 return obj.Name()
33 case *types.Basic:
34 return t.Name()
35 case *types.Pointer:
36 return "*" + tm.TypeID(t.Elem())
37 case *types.Slice:
38 return "[]" + tm.TypeID(t.Elem())
39 case *types.Array:
40 return fmt.Sprintf("[%d]%s", t.Len(), tm.TypeID(t.Elem()))
41 case *types.Map:
42 return "map[" + tm.TypeID(t.Key()) + "]" + tm.TypeID(t.Elem())
43 case *types.Chan:
44 prefix := "chan "
45 switch t.Dir() {
46 case types.SendOnly:
47 prefix = "chan<- "
48 case types.RecvOnly:
49 prefix = "<-chan "
50 }
51 return prefix + tm.TypeID(t.Elem())
52 case *types.Interface:
53 if t.NumMethods() == 0 {
54 return "interface{}"
55 }
56 tm.anonCounter++
57 return fmt.Sprintf("$iface_%d", tm.anonCounter)
58 case *types.Struct:
59 tm.anonCounter++
60 return fmt.Sprintf("$struct_%d", tm.anonCounter)
61 case *types.Signature:
62 return "func"
63 case *types.Alias:
64 return tm.TypeID(types.Unalias(t))
65 default:
66 return t.String()
67 }
68 }
69
70 // ZeroExpr returns the JavaScript expression for the zero value of a Go type.
71 func (tm *TypeMapper) ZeroExpr(t types.Type) string {
72 t = t.Underlying()
73 switch t := t.(type) {
74 case *types.Basic:
75 info := t.Info()
76 if info&types.IsBoolean != 0 {
77 return "false"
78 }
79 if info&types.IsNumeric != 0 {
80 if info&types.IsComplex != 0 {
81 return "{ re: 0, im: 0 }"
82 }
83 if t.Kind() == types.Int64 || t.Kind() == types.Uint64 {
84 return "0n"
85 }
86 return "0"
87 }
88 if info&types.IsString != 0 {
89 return "''"
90 }
91 return "null"
92 case *types.Pointer:
93 return "null"
94 case *types.Slice:
95 return "null"
96 case *types.Map:
97 return "null"
98 case *types.Chan:
99 return "null"
100 case *types.Interface:
101 return "null"
102 case *types.Signature:
103 return "null"
104 case *types.Struct:
105 return tm.StructZero(t)
106 case *types.Array:
107 elem := tm.ZeroExpr(t.Elem())
108 return fmt.Sprintf("$rt.builtin.makeSlice(%d, %d, %s)", t.Len(), t.Len(), elem)
109 default:
110 return "null"
111 }
112 }
113
114 // StructZero returns the JS expression for a zero-value struct literal.
115 func (tm *TypeMapper) StructZero(t *types.Struct) string {
116 if t.NumFields() == 0 {
117 return "{}"
118 }
119 var fields []string
120 for i := 0; i < t.NumFields(); i++ {
121 f := t.Field(i)
122 name := JsIdentifier(f.Name())
123 zero := tm.ZeroExpr(f.Type())
124 fields = append(fields, fmt.Sprintf("%s: %s", name, zero))
125 }
126 return "{ " + strings.Join(fields, ", ") + " }"
127 }
128
129 // StructClone returns JS code that deep-clones a struct value.
130 func (tm *TypeMapper) StructClone(varName string, t *types.Struct) string {
131 if t.NumFields() == 0 {
132 return "{}"
133 }
134 var fields []string
135 for i := 0; i < t.NumFields(); i++ {
136 f := t.Field(i)
137 name := JsIdentifier(f.Name())
138 val := fmt.Sprintf("%s.%s", varName, name)
139 if isStructType(f.Type()) {
140 val = tm.StructClone(val, f.Type().Underlying().(*types.Struct))
141 }
142 fields = append(fields, fmt.Sprintf("%s: %s", name, val))
143 }
144 return "{ " + strings.Join(fields, ", ") + " }"
145 }
146
147 // IsValueType returns true if the type should be passed by value in JS.
148 // Structs are value types in Go, so we need to clone them on assignment.
149 func IsValueType(t types.Type) bool {
150 switch t.Underlying().(type) {
151 case *types.Struct:
152 return true
153 case *types.Array:
154 return true
155 }
156 return false
157 }
158
159 // isStructType checks if the underlying type is a struct.
160 func isStructType(t types.Type) bool {
161 _, ok := t.Underlying().(*types.Struct)
162 return ok
163 }
164
165 // NeedsPointerWrapper returns true if this type needs $get/$set pointer wrappers.
166 func NeedsPointerWrapper(t types.Type) bool {
167 _, isPtr := t.Underlying().(*types.Pointer)
168 return isPtr
169 }
170
171 // KeyKind returns the JS map key kind string for a Go type.
172 func (tm *TypeMapper) KeyKind(t types.Type) string {
173 t = t.Underlying()
174 switch t := t.(type) {
175 case *types.Basic:
176 info := t.Info()
177 if info&types.IsString != 0 {
178 return "string"
179 }
180 if info&types.IsInteger != 0 {
181 return "int"
182 }
183 if info&types.IsFloat != 0 {
184 return "float64"
185 }
186 if info&types.IsBoolean != 0 {
187 return "bool"
188 }
189 }
190 return "struct"
191 }
192
193 // EmitTypeRegistration generates JS code to register runtime type info.
194 func (tm *TypeMapper) EmitTypeRegistration(e *Emitter, t types.Type) {
195 id := tm.TypeID(t)
196 if tm.registeredTypes[id] {
197 return
198 }
199 tm.registeredTypes[id] = true
200
201 switch ut := t.Underlying().(type) {
202 case *types.Struct:
203 e.Line("$rt.types.registerType(%s, {", JsString(id))
204 e.Indent()
205 e.Line("id: %s,", JsString(id))
206 e.Line("kind: 'struct',")
207 e.Line("methods: new Map(),")
208 e.Line("fields: [")
209 e.Indent()
210 for i := 0; i < ut.NumFields(); i++ {
211 f := ut.Field(i)
212 embedded := "false"
213 if f.Embedded() {
214 embedded = "true"
215 }
216 tag := ""
217 if ut.Tag(i) != "" {
218 tag = ut.Tag(i)
219 }
220 e.Line("{ name: %s, type: %s, tag: %s, embedded: %s },",
221 JsString(f.Name()),
222 JsString(tm.TypeID(f.Type())),
223 JsString(tag),
224 embedded)
225 }
226 e.Dedent()
227 e.Line("],")
228 e.Line("zero: () => (%s),", tm.StructZero(ut))
229 e.Dedent()
230 e.Line("});")
231
232 case *types.Interface:
233 e.Line("$rt.types.registerType(%s, {", JsString(id))
234 e.Indent()
235 e.Line("id: %s,", JsString(id))
236 e.Line("kind: 'interface',")
237 e.Line("methods: new Map([")
238 e.Indent()
239 for i := 0; i < ut.NumMethods(); i++ {
240 m := ut.Method(i)
241 e.Line("[%s, null],", JsString(m.Name()))
242 }
243 e.Dedent()
244 e.Line("]),")
245 e.Dedent()
246 e.Line("});")
247 }
248 }
249