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 let typeName = iface.$type;
92 let concreteType = typeRegistry.get(typeName);
93 // Pointer types share methods with their base type.
94 if ((!concreteType || !concreteType.methods || !concreteType.methods.has(methodName)) && typeName.startsWith('*')) {
95 typeName = typeName.slice(1);
96 concreteType = typeRegistry.get(typeName);
97 }
98 if (!concreteType || !concreteType.methods || !concreteType.methods.has(methodName)) {
99 throw new Error(`method not found: ${iface.$type}.${methodName}`);
100 }
101
102 const method = concreteType.methods.get(methodName);
103 return method(iface.$value, ...args);
104 }
105
106 // Type switch helper.
107 export function typeSwitch(iface) {
108 if (iface === null || iface === undefined) {
109 return { type: null, value: null };
110 }
111 return { type: iface.$type, value: iface.$value };
112 }
113
114 // Zero value for a type.
115 function zeroForType(typeId) {
116 const desc = typeRegistry.get(typeId);
117 if (desc && desc.zero) return desc.zero();
118
119 // Fallback for basic types.
120 if (typeId === 'bool') return false;
121 if (typeId === 'string') return '';
122 if (typeId === 'int64' || typeId === 'uint64') return 0n;
123 if (typeId.startsWith('int') || typeId.startsWith('uint') ||
124 typeId.startsWith('float') || typeId === 'uintptr' || typeId === 'byte' || typeId === 'rune') {
125 return 0;
126 }
127 return null;
128 }
129
130 // Comparable check (for map keys).
131 export function comparable(a, b) {
132 if (a === b) return true;
133 if (a === null || b === null) return false;
134 if (typeof a !== typeof b) return false;
135 if (typeof a === 'object') {
136 // Struct comparison: compare all fields.
137 const keysA = Object.keys(a).filter(k => !k.startsWith('$'));
138 const keysB = Object.keys(b).filter(k => !k.startsWith('$'));
139 if (keysA.length !== keysB.length) return false;
140 return keysA.every(k => comparable(a[k], b[k]));
141 }
142 return false;
143 }
144