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