types.mjs raw

   1  // TinyJS Runtime — Type System
   2  // Runtime type information, interface satisfaction, type assertions.
   3  
   4  // Type registry: maps type ID strings to type descriptors.
   5  const typeRegistry = new Map();
   6  
   7  // Register a type. Called by generated code at init time.
   8  export function registerType(id, descriptor) {
   9    typeRegistry.set(id, descriptor);
  10  }
  11  
  12  // Type descriptor shape:
  13  // {
  14  //   id: string,           // unique type identifier (package path + name)
  15  //   kind: string,         // 'struct', 'interface', 'basic', 'pointer', 'slice', 'map', 'chan', 'func'
  16  //   methods: Map<string, Function>,  // method name -> implementation
  17  //   fields: [{name, type, tag, embedded}],  // for structs
  18  //   elem: string,         // for pointer/slice/chan: element type ID
  19  //   key: string,          // for maps: key type ID
  20  //   size: number,         // byte size (for basic types)
  21  //   zero: any,            // zero value factory
  22  // }
  23  
  24  export function getType(id) {
  25    return typeRegistry.get(id);
  26  }
  27  
  28  // Interface value: { $type: typeId, $value: concreteValue }
  29  export function makeInterface(typeId, value) {
  30    return { $type: typeId, $value: value };
  31  }
  32  
  33  // Type assertion: iface.(T)
  34  // Returns value if type matches, panics otherwise.
  35  export function typeAssert(iface, targetTypeId) {
  36    if (iface === null || iface === undefined) {
  37      throw new Error(`interface conversion: interface is nil, not ${targetTypeId}`);
  38    }
  39    if (iface.$type === targetTypeId) {
  40      return iface.$value;
  41    }
  42    throw new Error(`interface conversion: interface is ${iface.$type}, not ${targetTypeId}`);
  43  }
  44  
  45  // Comma-ok type assertion: v, ok := iface.(T)
  46  export function typeAssertOk(iface, targetTypeId) {
  47    if (iface === null || iface === undefined) {
  48      return [zeroForType(targetTypeId), false];
  49    }
  50    if (iface.$type === targetTypeId) {
  51      return [iface.$value, true];
  52    }
  53    return [zeroForType(targetTypeId), false];
  54  }
  55  
  56  // Interface type assertion: iface.(SomeInterface)
  57  // Checks if the concrete type implements the interface methods.
  58  export function interfaceAssert(iface, interfaceTypeId) {
  59    if (iface === null || iface === undefined) {
  60      throw new Error(`interface conversion: interface is nil, not ${interfaceTypeId}`);
  61    }
  62  
  63    const ifaceType = typeRegistry.get(interfaceTypeId);
  64    if (!ifaceType || ifaceType.kind !== 'interface') {
  65      throw new Error(`not an interface type: ${interfaceTypeId}`);
  66    }
  67  
  68    const concreteType = typeRegistry.get(iface.$type);
  69    if (!concreteType) {
  70      throw new Error(`unknown type: ${iface.$type}`);
  71    }
  72  
  73    // Check all interface methods are satisfied.
  74    for (const [name] of ifaceType.methods) {
  75      if (!concreteType.methods || !concreteType.methods.has(name)) {
  76        throw new Error(
  77          `interface conversion: ${iface.$type} does not implement ${interfaceTypeId} (missing method ${name})`
  78        );
  79      }
  80    }
  81  
  82    return iface;
  83  }
  84  
  85  // Interface method call dispatch.
  86  export function methodCall(iface, methodName, args) {
  87    if (iface === null || iface === undefined) {
  88      throw new Error('runtime error: invalid memory address or nil pointer dereference');
  89    }
  90  
  91    const concreteType = typeRegistry.get(iface.$type);
  92    if (!concreteType || !concreteType.methods || !concreteType.methods.has(methodName)) {
  93      throw new Error(`method not found: ${iface.$type}.${methodName}`);
  94    }
  95  
  96    const method = concreteType.methods.get(methodName);
  97    return method(iface.$value, ...args);
  98  }
  99  
 100  // Type switch helper.
 101  export function typeSwitch(iface) {
 102    if (iface === null || iface === undefined) {
 103      return { type: null, value: null };
 104    }
 105    return { type: iface.$type, value: iface.$value };
 106  }
 107  
 108  // Zero value for a type.
 109  function zeroForType(typeId) {
 110    const desc = typeRegistry.get(typeId);
 111    if (desc && desc.zero) return desc.zero();
 112  
 113    // Fallback for basic types.
 114    if (typeId === 'bool') return false;
 115    if (typeId === 'string') return '';
 116    if (typeId === 'int64' || typeId === 'uint64') return 0n;
 117    if (typeId.startsWith('int') || typeId.startsWith('uint') ||
 118        typeId.startsWith('float') || typeId === 'uintptr' || typeId === 'byte' || typeId === 'rune') {
 119      return 0;
 120    }
 121    return null;
 122  }
 123  
 124  // Comparable check (for map keys).
 125  export function comparable(a, b) {
 126    if (a === b) return true;
 127    if (a === null || b === null) return false;
 128    if (typeof a !== typeof b) return false;
 129    if (typeof a === 'object') {
 130      // Struct comparison: compare all fields.
 131      const keysA = Object.keys(a).filter(k => !k.startsWith('$'));
 132      const keysB = Object.keys(b).filter(k => !k.startsWith('$'));
 133      if (keysA.length !== keysB.length) return false;
 134      return keysA.every(k => comparable(a[k], b[k]));
 135    }
 136    return false;
 137  }
 138