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