@xnoxs/flux-lang 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +915 -597
- package/bin/flux.js +1 -1395
- package/dist/flux-cli.js +9564 -0
- package/dist/flux.cjs.js +1 -1
- package/dist/flux.esm.js +1 -1
- package/dist/flux.min.js +1 -1
- package/package.json +24 -16
- package/scripts/build.js +28 -29
- package/src/bundler.js +0 -216
- package/src/checker.js +0 -322
- package/src/codegen.js +0 -832
- package/src/css-preprocessor.js +0 -399
- package/src/jsx.js +0 -480
- package/src/lexer.js +0 -518
- package/src/linter.js +0 -784
- package/src/mangler.js +0 -280
- package/src/parser.js +0 -1708
- package/src/sourcemap.js +0 -82
- package/src/test-runner.js +0 -239
- package/src/transpiler.js +0 -172
- package/src/type-checker.js +0 -1206
package/src/type-checker.js
DELETED
|
@@ -1,1206 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// ── Flux Type System v2.0 ──────────────────────────────────────────────────────
|
|
4
|
-
// Full TypeScript-level type checking engine
|
|
5
|
-
// Features: structural typing, union/intersection, nullable, generics, utility types,
|
|
6
|
-
// type narrowing, tuples, index signatures, keyof, typeof, conditional types
|
|
7
|
-
|
|
8
|
-
// ── Primitive singletons ──────────────────────────────────────────────────────
|
|
9
|
-
const T_ANY = Object.freeze({ kind: 'any' });
|
|
10
|
-
const T_UNKNOWN = Object.freeze({ kind: 'unknown' });
|
|
11
|
-
const T_NEVER = Object.freeze({ kind: 'never' });
|
|
12
|
-
const T_VOID = Object.freeze({ kind: 'void' });
|
|
13
|
-
const T_NULL = Object.freeze({ kind: 'null' });
|
|
14
|
-
const T_STRING = Object.freeze({ kind: 'primitive', name: 'String' });
|
|
15
|
-
const T_INT = Object.freeze({ kind: 'primitive', name: 'Int' });
|
|
16
|
-
const T_FLOAT = Object.freeze({ kind: 'primitive', name: 'Float' });
|
|
17
|
-
const T_NUMBER = Object.freeze({ kind: 'primitive', name: 'Number' });
|
|
18
|
-
const T_BOOL = Object.freeze({ kind: 'primitive', name: 'Bool' });
|
|
19
|
-
|
|
20
|
-
// ── Type constructors ─────────────────────────────────────────────────────────
|
|
21
|
-
function T_UNION(...members) {
|
|
22
|
-
const flat = [];
|
|
23
|
-
for (const m of members) {
|
|
24
|
-
if (m && m.kind === 'union') flat.push(...m.members);
|
|
25
|
-
else if (m) flat.push(m);
|
|
26
|
-
}
|
|
27
|
-
const seen = new Set();
|
|
28
|
-
const deduped = flat.filter(m => {
|
|
29
|
-
const k = typeStr(m);
|
|
30
|
-
if (seen.has(k)) return false;
|
|
31
|
-
seen.add(k); return true;
|
|
32
|
-
});
|
|
33
|
-
if (deduped.length === 0) return T_NEVER;
|
|
34
|
-
if (deduped.length === 1) return deduped[0];
|
|
35
|
-
return { kind: 'union', members: deduped };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function T_INTERSECTION(...members) {
|
|
39
|
-
const flat = [];
|
|
40
|
-
for (const m of members) {
|
|
41
|
-
if (m && m.kind === 'intersection') flat.push(...m.members);
|
|
42
|
-
else if (m) flat.push(m);
|
|
43
|
-
}
|
|
44
|
-
if (flat.length === 1) return flat[0];
|
|
45
|
-
return { kind: 'intersection', members: flat };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function T_ARRAY(elem) { return { kind: 'generic', name: 'Array', args: [elem || T_UNKNOWN] }; }
|
|
49
|
-
function T_TUPLE(types) { return { kind: 'tuple', types: types || [] }; }
|
|
50
|
-
function T_NULLABLE(t) { return T_UNION(t, T_NULL); }
|
|
51
|
-
function T_NAMED(name) { return { kind: 'named', name }; }
|
|
52
|
-
function T_FN(params, ret) { return { kind: 'fn', params: params || [], ret: ret || T_VOID }; }
|
|
53
|
-
function T_OBJECT(shape) { return { kind: 'object', shape: shape || new Map() }; } // shape: Map<string, Type>
|
|
54
|
-
function T_RECORD(key, val) { return { kind: 'record', key: key || T_STRING, val: val || T_UNKNOWN }; }
|
|
55
|
-
function T_LITERAL(value, kind) { return { kind: 'literal', value, prim: kind }; } // 'foo', 42, true literal types
|
|
56
|
-
|
|
57
|
-
// ── Type → display string ─────────────────────────────────────────────────────
|
|
58
|
-
function typeStr(t) {
|
|
59
|
-
if (!t) return 'unknown';
|
|
60
|
-
switch (t.kind) {
|
|
61
|
-
case 'any': return 'Any';
|
|
62
|
-
case 'unknown': return 'Unknown';
|
|
63
|
-
case 'never': return 'Never';
|
|
64
|
-
case 'void': return 'Void';
|
|
65
|
-
case 'null': return 'Null';
|
|
66
|
-
case 'primitive': return t.name;
|
|
67
|
-
case 'literal': return JSON.stringify(t.value);
|
|
68
|
-
case 'union': return t.members.map(typeStr).join(' | ');
|
|
69
|
-
case 'intersection': return t.members.map(typeStr).join(' & ');
|
|
70
|
-
case 'tuple': return '[' + t.types.map(typeStr).join(', ') + ']';
|
|
71
|
-
case 'object': return '{' + [...(t.shape || new Map()).entries()].map(([k,v]) => `${k}: ${typeStr(v)}`).join(', ') + '}';
|
|
72
|
-
case 'record': return `Record<${typeStr(t.key)}, ${typeStr(t.val)}>`;
|
|
73
|
-
case 'generic': return t.name + (t.args && t.args.length ? '<' + t.args.map(typeStr).join(', ') + '>' : '');
|
|
74
|
-
case 'named': return t.name;
|
|
75
|
-
case 'fn': return `(${(t.params || []).map(typeStr).join(', ')}) -> ${typeStr(t.ret)}`;
|
|
76
|
-
case 'keyof': return `keyof ${typeStr(t.inner)}`;
|
|
77
|
-
case 'conditional': return `${typeStr(t.check)} extends ${typeStr(t.extends)} ? ${typeStr(t.then)} : ${typeStr(t.else)}`;
|
|
78
|
-
default: return 'unknown';
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ── Split at top-level separator (respects <>, [], {}, ()) ───────────────────
|
|
83
|
-
function splitAtTopLevel(str, sep) {
|
|
84
|
-
const parts = [];
|
|
85
|
-
let depth = 0, start = 0;
|
|
86
|
-
for (let i = 0; i < str.length; i++) {
|
|
87
|
-
const c = str[i];
|
|
88
|
-
if ('<([{'.includes(c)) depth++;
|
|
89
|
-
else if ('>)]}' .includes(c)) depth--;
|
|
90
|
-
else if (depth === 0 && str.slice(i, i + sep.length) === sep) {
|
|
91
|
-
parts.push(str.slice(start, i));
|
|
92
|
-
start = i + sep.length;
|
|
93
|
-
i += sep.length - 1;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
parts.push(str.slice(start));
|
|
97
|
-
return parts.filter(p => p.trim().length > 0);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ── Primitive type map ────────────────────────────────────────────────────────
|
|
101
|
-
const PRIMITIVE_MAP = {
|
|
102
|
-
String: T_STRING, string: T_STRING,
|
|
103
|
-
Int: T_INT, int: T_INT,
|
|
104
|
-
Float: T_FLOAT, float: T_FLOAT,
|
|
105
|
-
Number: T_NUMBER, number: T_NUMBER,
|
|
106
|
-
Bool: T_BOOL, boolean: T_BOOL, Boolean: T_BOOL, bool: T_BOOL,
|
|
107
|
-
Void: T_VOID, void: T_VOID,
|
|
108
|
-
Never: T_NEVER, never: T_NEVER,
|
|
109
|
-
Any: T_ANY, any: T_ANY,
|
|
110
|
-
Unknown: T_UNKNOWN, unknown: T_UNKNOWN,
|
|
111
|
-
Null: T_NULL, null: T_NULL, undefined: T_VOID,
|
|
112
|
-
Object: T_NAMED('Object'), object: T_NAMED('Object'),
|
|
113
|
-
Symbol: T_NAMED('Symbol'), symbol: T_NAMED('Symbol'),
|
|
114
|
-
BigInt: T_NAMED('BigInt'), bigint: T_NAMED('BigInt'),
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// ── Parse annotation string → Type object ────────────────────────────────────
|
|
118
|
-
function parseAnnotation(str) {
|
|
119
|
-
if (!str) return null;
|
|
120
|
-
str = str.trim();
|
|
121
|
-
|
|
122
|
-
// Strip outer parens: (A | B) → A | B
|
|
123
|
-
if (str.startsWith('(') && str.endsWith(')')) {
|
|
124
|
-
str = str.slice(1, -1).trim();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Union: A | B | C (split at top-level ' | ')
|
|
128
|
-
const unionParts = splitAtTopLevel(str, ' | ');
|
|
129
|
-
if (unionParts.length > 1) {
|
|
130
|
-
return T_UNION(...unionParts.map(parseAnnotation));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Intersection: A & B (split at top-level ' & ')
|
|
134
|
-
const intersectionParts = splitAtTopLevel(str, ' & ');
|
|
135
|
-
if (intersectionParts.length > 1) {
|
|
136
|
-
return T_INTERSECTION(...intersectionParts.map(parseAnnotation));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Conditional type: T extends U ? A : B
|
|
140
|
-
const condMatch = str.match(/^(\w+)\s+extends\s+(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
|
|
141
|
-
if (condMatch) {
|
|
142
|
-
return {
|
|
143
|
-
kind: 'conditional',
|
|
144
|
-
check: parseAnnotation(condMatch[1]),
|
|
145
|
-
extends: parseAnnotation(condMatch[2]),
|
|
146
|
-
then: parseAnnotation(condMatch[3]),
|
|
147
|
-
else: parseAnnotation(condMatch[4]),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// keyof T
|
|
152
|
-
if (str.startsWith('keyof ')) {
|
|
153
|
-
return { kind: 'keyof', inner: parseAnnotation(str.slice(6)) };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// typeof x
|
|
157
|
-
if (str.startsWith('typeof ')) {
|
|
158
|
-
return { kind: 'typeof', name: str.slice(7) };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// readonly T → same as T (readonly only prevents assignment, not type)
|
|
162
|
-
if (str.startsWith('readonly ')) {
|
|
163
|
-
return parseAnnotation(str.slice(9));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// infer T → Any (infer is used in conditional types)
|
|
167
|
-
if (str.startsWith('infer ')) {
|
|
168
|
-
return T_ANY;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Function type: fn(T1, T2) -> RetType OR fn() -> Void
|
|
172
|
-
// Also handles arrow style: (T1, T2) -> RetType
|
|
173
|
-
const fnTypeMatch = str.match(/^fn\(([^)]*)\)\s*->\s*(.+)$/) ||
|
|
174
|
-
str.match(/^\(([^)]*)\)\s*->\s*(.+)$/);
|
|
175
|
-
if (fnTypeMatch) {
|
|
176
|
-
const paramStr = fnTypeMatch[1].trim();
|
|
177
|
-
const retStr = fnTypeMatch[2].trim();
|
|
178
|
-
const params = paramStr ? splitAtTopLevel(paramStr, ', ').map(p => {
|
|
179
|
-
// Handle named param: "name: Type" → parse Type only
|
|
180
|
-
const ci = p.indexOf(':');
|
|
181
|
-
return parseAnnotation(ci >= 0 ? p.slice(ci + 1).trim() : p.trim());
|
|
182
|
-
}) : [];
|
|
183
|
-
return T_FN(params, parseAnnotation(retStr));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Nullable shorthand: String?
|
|
187
|
-
if (str.endsWith('?')) {
|
|
188
|
-
return T_NULLABLE(parseAnnotation(str.slice(0, -1)));
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Array shorthand: String[]
|
|
192
|
-
if (str.endsWith('[]')) {
|
|
193
|
-
return T_ARRAY(parseAnnotation(str.slice(0, -2)));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Tuple type: [String, Int, Bool]
|
|
197
|
-
if (str.startsWith('[') && str.endsWith(']')) {
|
|
198
|
-
const inner = str.slice(1, -1);
|
|
199
|
-
if (!inner.trim()) return T_TUPLE([]);
|
|
200
|
-
const parts = splitAtTopLevel(inner, ', ');
|
|
201
|
-
return T_TUPLE(parts.map(p => parseAnnotation(p.replace(/^\.\.\./, '').trim())));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Inline object type: { key: Type, key2?: Type }
|
|
205
|
-
if (str.startsWith('{') && str.endsWith('}')) {
|
|
206
|
-
const inner = str.slice(1, -1).trim();
|
|
207
|
-
const shape = new Map();
|
|
208
|
-
if (inner) {
|
|
209
|
-
const pairs = splitAtTopLevel(inner, ', ');
|
|
210
|
-
for (const pair of pairs) {
|
|
211
|
-
// Index signature: [key: String]: Type
|
|
212
|
-
const idxMatch = pair.match(/^\[(\w+):\s*\w+\]:\s*(.+)$/);
|
|
213
|
-
if (idxMatch) {
|
|
214
|
-
shape.set(`[index]`, parseAnnotation(idxMatch[2]));
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
const colonIdx = pair.indexOf(':');
|
|
218
|
-
if (colonIdx >= 0) {
|
|
219
|
-
const rawKey = pair.slice(0, colonIdx).trim().replace(/^readonly\s+/, '');
|
|
220
|
-
const key = rawKey.replace(/\?$/, '');
|
|
221
|
-
const opt = rawKey.endsWith('?');
|
|
222
|
-
const valType = parseAnnotation(pair.slice(colonIdx + 1).trim());
|
|
223
|
-
shape.set(key, opt ? T_UNION(valType, T_VOID) : valType);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return T_OBJECT(shape);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Generic / Utility types: Partial<T>, Record<K,V>, Array<T> etc.
|
|
231
|
-
const genericMatch = str.match(/^(\w+)<(.+)>$/s);
|
|
232
|
-
if (genericMatch) {
|
|
233
|
-
const name = genericMatch[1];
|
|
234
|
-
const rawArgs = splitAtTopLevel(genericMatch[2], ', ');
|
|
235
|
-
const args = rawArgs.map(a => parseAnnotation(a.trim()));
|
|
236
|
-
|
|
237
|
-
// Well-known utility types
|
|
238
|
-
switch (name) {
|
|
239
|
-
case 'Array':
|
|
240
|
-
case 'List':
|
|
241
|
-
case 'ReadonlyArray': return T_ARRAY(args[0] || T_UNKNOWN);
|
|
242
|
-
case 'NonNullable': return resolveNonNullable(args[0]);
|
|
243
|
-
case 'Partial': return { kind: 'utility', util: 'Partial', inner: args[0] };
|
|
244
|
-
case 'Required': return { kind: 'utility', util: 'Required', inner: args[0] };
|
|
245
|
-
case 'Readonly': return args[0]; // readonly is erased
|
|
246
|
-
case 'Record': return T_RECORD(args[0] || T_STRING, args[1] || T_UNKNOWN);
|
|
247
|
-
case 'Pick': return { kind: 'utility', util: 'Pick', inner: args[0], keys: args[1] };
|
|
248
|
-
case 'Omit': return { kind: 'utility', util: 'Omit', inner: args[0], keys: args[1] };
|
|
249
|
-
case 'Exclude': return { kind: 'utility', util: 'Exclude', inner: args[0], keys: args[1] };
|
|
250
|
-
case 'Extract': return { kind: 'utility', util: 'Extract', inner: args[0], keys: args[1] };
|
|
251
|
-
case 'ReturnType': return { kind: 'utility', util: 'ReturnType', inner: args[0] };
|
|
252
|
-
case 'InstanceType': return { kind: 'utility', util: 'InstanceType', inner: args[0] };
|
|
253
|
-
case 'Parameters': return { kind: 'utility', util: 'Parameters', inner: args[0] };
|
|
254
|
-
case 'Awaited': return { kind: 'utility', util: 'Awaited', inner: args[0] };
|
|
255
|
-
case 'Promise': return { kind: 'generic', name: 'Promise', args };
|
|
256
|
-
case 'Map': return { kind: 'generic', name: 'Map', args };
|
|
257
|
-
case 'Set': return { kind: 'generic', name: 'Set', args };
|
|
258
|
-
case 'WeakMap': return { kind: 'generic', name: 'WeakMap', args };
|
|
259
|
-
default: return { kind: 'generic', name, args };
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Primitive or named type
|
|
264
|
-
if (PRIMITIVE_MAP[str]) return PRIMITIVE_MAP[str];
|
|
265
|
-
return T_NAMED(str);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function resolveNonNullable(t) {
|
|
269
|
-
if (!t) return T_UNKNOWN;
|
|
270
|
-
if (t.kind === 'union') return T_UNION(...t.members.filter(m => m.kind !== 'null' && m.kind !== 'void'));
|
|
271
|
-
if (t.kind === 'null' || t.kind === 'void') return T_NEVER;
|
|
272
|
-
return t;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// ── Standalone type assignability (basic, no structural) ─────────────────────
|
|
276
|
-
function isAssignable(from, to) {
|
|
277
|
-
return _basicAssignable(from, to);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function _basicAssignable(from, to) {
|
|
281
|
-
if (!from || !to) return true;
|
|
282
|
-
if (to.kind === 'any') return true;
|
|
283
|
-
if (from.kind === 'any') return true;
|
|
284
|
-
if (from.kind === 'never') return true;
|
|
285
|
-
if (to.kind === 'unknown') return true;
|
|
286
|
-
|
|
287
|
-
if (from.kind === 'void' && to.kind === 'void') return true;
|
|
288
|
-
if (from.kind === 'null' && to.kind === 'null') return true;
|
|
289
|
-
if (from.kind === 'never' && to.kind === 'never') return true;
|
|
290
|
-
|
|
291
|
-
// Null assignable to nullable
|
|
292
|
-
if (from.kind === 'null' && to.kind === 'union') {
|
|
293
|
-
return to.members.some(m => m.kind === 'null' || m.kind === 'any' || m.kind === 'void');
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// From union: all members assignable to target
|
|
297
|
-
if (from.kind === 'union') {
|
|
298
|
-
return from.members.every(m => _basicAssignable(m, to));
|
|
299
|
-
}
|
|
300
|
-
// To union: from assignable to at least one member
|
|
301
|
-
if (to.kind === 'union') {
|
|
302
|
-
return to.members.some(m => _basicAssignable(from, m));
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// From intersection: assignable if from is a subtype of to (any member)
|
|
306
|
-
if (from.kind === 'intersection') {
|
|
307
|
-
return from.members.some(m => _basicAssignable(m, to));
|
|
308
|
-
}
|
|
309
|
-
// To intersection: from must satisfy all members
|
|
310
|
-
if (to.kind === 'intersection') {
|
|
311
|
-
return to.members.every(m => _basicAssignable(from, m));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Primitives
|
|
315
|
-
if (from.kind === 'primitive' && to.kind === 'primitive') {
|
|
316
|
-
if (from.name === to.name) return true;
|
|
317
|
-
if (to.name === 'Number' && ['Int','Float','Number'].includes(from.name)) return true;
|
|
318
|
-
if (to.name === 'Float' && from.name === 'Int') return true;
|
|
319
|
-
// Aliases
|
|
320
|
-
if (from.name === 'Bool' && to.name === 'Boolean') return true;
|
|
321
|
-
if (from.name === 'Boolean' && to.name === 'Bool') return true;
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Literal to primitive
|
|
326
|
-
if (from.kind === 'literal' && to.kind === 'primitive') {
|
|
327
|
-
const map = { string: 'String', number: 'Number', boolean: 'Bool' };
|
|
328
|
-
return map[from.prim] === to.name || (from.prim === 'number' && ['Int','Float','Number'].includes(to.name));
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Named types
|
|
332
|
-
if (from.kind === 'named' && to.kind === 'named') return from.name === to.name || to.name === 'Object';
|
|
333
|
-
|
|
334
|
-
// Generic types
|
|
335
|
-
if (from.kind === 'generic' && to.kind === 'generic') {
|
|
336
|
-
const arrayNames = new Set(['Array','List','ReadonlyArray']);
|
|
337
|
-
if (from.name !== to.name) {
|
|
338
|
-
if (!(arrayNames.has(from.name) && arrayNames.has(to.name))) return false;
|
|
339
|
-
}
|
|
340
|
-
if (from.args.length !== to.args.length) return false;
|
|
341
|
-
return from.args.every((a, i) => _basicAssignable(a, to.args[i]));
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Tuple types
|
|
345
|
-
if (from.kind === 'tuple' && to.kind === 'tuple') {
|
|
346
|
-
if (from.types.length !== to.types.length) return false;
|
|
347
|
-
return from.types.every((t, i) => _basicAssignable(t, to.types[i]));
|
|
348
|
-
}
|
|
349
|
-
// Array literal assignable to tuple: [Float, Float] satisfies [Float, Float]
|
|
350
|
-
if (from.kind === 'generic' && from.name === 'Array' && to.kind === 'tuple') {
|
|
351
|
-
const elemType = from.args[0] || T_UNKNOWN;
|
|
352
|
-
if (elemType.kind === 'unknown') return true;
|
|
353
|
-
return to.types.every(t => _basicAssignable(elemType, t));
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Object types (structural): both are objects, check all keys in `to` exist in `from`
|
|
357
|
-
if (from.kind === 'object' && to.kind === 'object') {
|
|
358
|
-
for (const [key, toType] of to.shape) {
|
|
359
|
-
if (!from.shape.has(key)) return false;
|
|
360
|
-
if (!_basicAssignable(from.shape.get(key), toType)) return false;
|
|
361
|
-
}
|
|
362
|
-
return true;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Object to named (basic)
|
|
366
|
-
if (from.kind === 'object' && to.kind === 'named') return true; // structural check done in class
|
|
367
|
-
|
|
368
|
-
// Function type assignability
|
|
369
|
-
if (from.kind === 'fn' && to.kind === 'fn') {
|
|
370
|
-
// Simplified: covariant return type, skip parameter variance for now
|
|
371
|
-
if (to.ret && from.ret) return _basicAssignable(from.ret, to.ret);
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
// Lambda/fn vs fn type
|
|
375
|
-
if ((from.kind === 'fn' || to.kind === 'fn') && (from.kind === 'fn' || to.kind === 'fn')) {
|
|
376
|
-
return true; // permissive — function shapes are hard to compare without inference
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Record assignability
|
|
380
|
-
if (from.kind === 'record' && to.kind === 'record') {
|
|
381
|
-
return _basicAssignable(from.val, to.val);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Generic with object args (e.g. Array<{...}> vs Array<{...}>)
|
|
385
|
-
if (from.kind === 'generic' && to.kind === 'generic') {
|
|
386
|
-
// Already handled above for basic generics, but args may be objects
|
|
387
|
-
return true; // permissive when both are same generic name (handled above)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Utility types
|
|
391
|
-
if (to.kind === 'utility') return true; // Too complex to fully evaluate standalone
|
|
392
|
-
if (from.kind === 'utility') return true;
|
|
393
|
-
|
|
394
|
-
return false;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// ── Merge two types (for inference) ──────────────────────────────────────────
|
|
398
|
-
function mergeTypes(a, b) {
|
|
399
|
-
if (!a || a.kind === 'unknown') return b || T_UNKNOWN;
|
|
400
|
-
if (!b || b.kind === 'unknown') return a;
|
|
401
|
-
if (typeStr(a) === typeStr(b)) return a;
|
|
402
|
-
if (a.kind === 'any' || b.kind === 'any') return T_ANY;
|
|
403
|
-
return T_UNION(a, b);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// ── Type Environment ──────────────────────────────────────────────────────────
|
|
407
|
-
class TypeEnv {
|
|
408
|
-
constructor(parent = null) {
|
|
409
|
-
this.parent = parent;
|
|
410
|
-
this.vars = new Map();
|
|
411
|
-
this.retType = parent ? parent.retType : null;
|
|
412
|
-
this.isAsync = parent ? parent.isAsync : false;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
set(name, type) { this.vars.set(name, type); }
|
|
416
|
-
has(name) { let e = this; while (e) { if (e.vars.has(name)) return true; e = e.parent; } return false; }
|
|
417
|
-
get(name) {
|
|
418
|
-
let env = this;
|
|
419
|
-
while (env) {
|
|
420
|
-
if (env.vars.has(name)) return env.vars.get(name);
|
|
421
|
-
env = env.parent;
|
|
422
|
-
}
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
child() { return new TypeEnv(this); }
|
|
427
|
-
childFn(retType, isAsync = false) {
|
|
428
|
-
const e = new TypeEnv(this);
|
|
429
|
-
e.retType = retType;
|
|
430
|
-
e.isAsync = isAsync;
|
|
431
|
-
return e;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Create a narrowed child scope
|
|
435
|
-
narrow(name, type) {
|
|
436
|
-
const child = new TypeEnv(this);
|
|
437
|
-
if (name && type) child.vars.set(name, type);
|
|
438
|
-
return child;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// ── Type Check Error ──────────────────────────────────────────────────────────
|
|
443
|
-
class TypeCheckError {
|
|
444
|
-
constructor(message, loc, hint = null) {
|
|
445
|
-
this.message = message;
|
|
446
|
-
this.name = 'TypeError';
|
|
447
|
-
this.hint = hint;
|
|
448
|
-
if (loc) { this.line = loc.line; this.col = loc.col; }
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// ── Main Type Checker ─────────────────────────────────────────────────────────
|
|
453
|
-
class FluxTypeChecker {
|
|
454
|
-
constructor() {
|
|
455
|
-
this.errors = [];
|
|
456
|
-
this.warnings = [];
|
|
457
|
-
this.interfaces = new Map(); // name → InterfaceDecl
|
|
458
|
-
this.types = new Map(); // name → TypeDecl (ADTs)
|
|
459
|
-
this.classes = new Map(); // name → ClassDecl
|
|
460
|
-
this.enums = new Map(); // name → EnumDecl
|
|
461
|
-
this.typeAliases = new Map(); // name → Type (from type alias declarations)
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
check(ast) {
|
|
465
|
-
this.errors = [];
|
|
466
|
-
this.warnings = [];
|
|
467
|
-
this.interfaces.clear();
|
|
468
|
-
this.types.clear();
|
|
469
|
-
this.classes.clear();
|
|
470
|
-
this.enums.clear();
|
|
471
|
-
this.typeAliases.clear();
|
|
472
|
-
|
|
473
|
-
this._collectDeclarations(ast.body);
|
|
474
|
-
this._validateImplementations();
|
|
475
|
-
|
|
476
|
-
const env = new TypeEnv();
|
|
477
|
-
this._registerBuiltins(env);
|
|
478
|
-
this._checkStmts(ast.body, env);
|
|
479
|
-
|
|
480
|
-
return { errors: this.errors, warnings: this.warnings };
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
_err(msg, loc, hint = null) { this.errors.push(new TypeCheckError(msg, loc, hint)); }
|
|
484
|
-
_warn(msg, loc, hint = null){ this.warnings.push(new TypeCheckError(msg, loc, hint)); }
|
|
485
|
-
|
|
486
|
-
// ── Pass 1: Collect declarations ──────────────────────────────────
|
|
487
|
-
_collectDeclarations(stmts) {
|
|
488
|
-
for (const node of stmts) {
|
|
489
|
-
const n = node.type === 'ExportDecl' ? node.decl : node;
|
|
490
|
-
if (!n) continue;
|
|
491
|
-
switch (n.type) {
|
|
492
|
-
case 'InterfaceDecl': this.interfaces.set(n.name, n); break;
|
|
493
|
-
case 'TypeDecl': this.types.set(n.name, n); break;
|
|
494
|
-
case 'ClassDecl': this.classes.set(n.name, n); break;
|
|
495
|
-
case 'EnumDecl': this.enums.set(n.name, n); break;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// ── Pass 2: Validate interface implementations ────────────────────
|
|
501
|
-
_validateImplementations() {
|
|
502
|
-
for (const [, cls] of this.classes) {
|
|
503
|
-
for (const ifaceName of cls.interfaces || []) {
|
|
504
|
-
this._checkInterfaceImpl(cls, ifaceName);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
_checkInterfaceImpl(cls, ifaceName) {
|
|
510
|
-
const iface = this.interfaces.get(ifaceName);
|
|
511
|
-
if (!iface) {
|
|
512
|
-
this._warn(
|
|
513
|
-
`Class '${cls.name}' implements unknown interface '${ifaceName}'`,
|
|
514
|
-
cls.loc,
|
|
515
|
-
`Define 'interface ${ifaceName}' before use`
|
|
516
|
-
);
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
for (const member of iface.members) {
|
|
520
|
-
if (member.kind === 'method') {
|
|
521
|
-
const impl = cls.methods.find(m => m.name === member.name);
|
|
522
|
-
if (!impl) {
|
|
523
|
-
this._err(
|
|
524
|
-
`Class '${cls.name}' does not implement method '${member.name}()' required by interface '${ifaceName}'`,
|
|
525
|
-
cls.loc,
|
|
526
|
-
`Add 'fn ${member.name}(${member.params.map(p => p.name + (p.typeAnn ? ': ' + p.typeAnn : '')).join(', ')})' to the class`
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
} else if (member.kind === 'field' && !member.optional) {
|
|
530
|
-
const hasField = cls.fields.find(f => f.name === member.name);
|
|
531
|
-
const hasMethod = cls.methods.find(m => m.name === member.name);
|
|
532
|
-
if (!hasField && !hasMethod) {
|
|
533
|
-
this._err(
|
|
534
|
-
`Class '${cls.name}' is missing field '${member.name}' required by interface '${ifaceName}'`,
|
|
535
|
-
cls.loc,
|
|
536
|
-
`Add '${member.name}: ${member.typeAnn}' to the class`
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
for (const superIface of iface.superInterfaces || []) {
|
|
542
|
-
this._checkInterfaceImpl(cls, superIface);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// ── Structural typing: check if object type satisfies interface ───
|
|
547
|
-
_isStructurallyCompatible(objShape, typeName) {
|
|
548
|
-
const iface = this.interfaces.get(typeName);
|
|
549
|
-
if (!iface) {
|
|
550
|
-
// Check classes too
|
|
551
|
-
const cls = this.classes.get(typeName);
|
|
552
|
-
if (!cls) return false;
|
|
553
|
-
for (const field of cls.fields) {
|
|
554
|
-
if (!objShape.has(field.name) && !field.optional) return false;
|
|
555
|
-
if (field.typeAnn && objShape.has(field.name)) {
|
|
556
|
-
const expected = parseAnnotation(field.typeAnn);
|
|
557
|
-
const actual = objShape.get(field.name);
|
|
558
|
-
if (!this._isAssignable(actual, expected)) return false;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
for (const member of iface.members) {
|
|
564
|
-
if (member.kind === 'field' && !member.optional) {
|
|
565
|
-
if (!objShape.has(member.name)) return false;
|
|
566
|
-
if (member.typeAnn) {
|
|
567
|
-
const expected = parseAnnotation(member.typeAnn);
|
|
568
|
-
const actual = objShape.get(member.name);
|
|
569
|
-
if (!this._isAssignable(actual, expected)) return false;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return true;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// ── Instance-level assignability (has structural typing access) ───
|
|
577
|
-
_isAssignable(from, to) {
|
|
578
|
-
if (!from || !to) return true;
|
|
579
|
-
if (to.kind === 'any') return true;
|
|
580
|
-
if (from.kind === 'any') return true;
|
|
581
|
-
if (from.kind === 'never') return true;
|
|
582
|
-
if (to.kind === 'unknown') return true;
|
|
583
|
-
if (from.kind === 'unknown') return false; // unknown must be narrowed
|
|
584
|
-
|
|
585
|
-
// Structural typing: object literal satisfies interface/class
|
|
586
|
-
if (from.kind === 'object' && to.kind === 'named') {
|
|
587
|
-
return this._isStructurallyCompatible(from.shape, to.name);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Structural typing: object literal satisfies inline object type
|
|
591
|
-
if (from.kind === 'object' && to.kind === 'object') {
|
|
592
|
-
for (const [key, toType] of to.shape) {
|
|
593
|
-
if (!from.shape.has(key)) return false;
|
|
594
|
-
if (!this._isAssignable(from.shape.get(key), toType)) return false;
|
|
595
|
-
}
|
|
596
|
-
return true;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Named satisfies interface (nominal → structural check)
|
|
600
|
-
if (from.kind === 'named' && to.kind === 'named') {
|
|
601
|
-
if (from.name === to.name) return true;
|
|
602
|
-
if (to.name === 'Object') return true;
|
|
603
|
-
// Check if 'from' class implements 'to' interface
|
|
604
|
-
const cls = this.classes.get(from.name);
|
|
605
|
-
if (cls && (cls.interfaces || []).includes(to.name)) return true;
|
|
606
|
-
// Check class inheritance
|
|
607
|
-
if (cls && cls.superClass === to.name) return true;
|
|
608
|
-
// Check if 'from' interface extends 'to'
|
|
609
|
-
const iface = this.interfaces.get(from.name);
|
|
610
|
-
if (iface && (iface.superInterfaces || []).includes(to.name)) return true;
|
|
611
|
-
return false;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Handle utility types
|
|
615
|
-
if (to.kind === 'utility') {
|
|
616
|
-
switch (to.util) {
|
|
617
|
-
case 'Partial': return true; // partial accepts any object
|
|
618
|
-
case 'Required': return this._isAssignable(from, to.inner);
|
|
619
|
-
case 'NonNullable': return from.kind !== 'null' && from.kind !== 'void';
|
|
620
|
-
default: return true;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
if (from.kind === 'utility') return true;
|
|
624
|
-
|
|
625
|
-
// Everything else: delegate to basic
|
|
626
|
-
return _basicAssignable(from, to);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// ── Extract narrowed type from condition ───────────────────────────
|
|
630
|
-
// Returns: { varName: string, trueType: Type, falseType: Type }
|
|
631
|
-
_analyzeNarrowingCondition(condExpr, env) {
|
|
632
|
-
if (!condExpr) return null;
|
|
633
|
-
|
|
634
|
-
// x != null / x !== null / x !== undefined
|
|
635
|
-
if (condExpr.type === 'BinaryExpr' &&
|
|
636
|
-
(condExpr.op === '!=' || condExpr.op === '!==') &&
|
|
637
|
-
condExpr.left.type === 'Identifier' &&
|
|
638
|
-
(condExpr.right.type === 'NullLit' ||
|
|
639
|
-
(condExpr.right.type === 'Identifier' && condExpr.right.name === 'undefined'))) {
|
|
640
|
-
const name = condExpr.left.name;
|
|
641
|
-
const t = env.get(name);
|
|
642
|
-
if (t && t.kind === 'union') {
|
|
643
|
-
const narrowed = T_UNION(...t.members.filter(m => m.kind !== 'null' && m.kind !== 'void'));
|
|
644
|
-
return { varName: name, trueType: narrowed, falseType: T_UNION(T_NULL, T_VOID) };
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// x == null / x === null
|
|
649
|
-
if (condExpr.type === 'BinaryExpr' &&
|
|
650
|
-
(condExpr.op === '==' || condExpr.op === '===') &&
|
|
651
|
-
condExpr.left.type === 'Identifier' &&
|
|
652
|
-
condExpr.right.type === 'NullLit') {
|
|
653
|
-
const name = condExpr.left.name;
|
|
654
|
-
const t = env.get(name);
|
|
655
|
-
if (t && t.kind === 'union') {
|
|
656
|
-
const narrowed = T_UNION(...t.members.filter(m => m.kind !== 'null' && m.kind !== 'void'));
|
|
657
|
-
return { varName: name, trueType: T_NULL, falseType: narrowed };
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// typeof x === "string" / "number" / "boolean"
|
|
662
|
-
if (condExpr.type === 'BinaryExpr' &&
|
|
663
|
-
(condExpr.op === '===' || condExpr.op === '==') &&
|
|
664
|
-
condExpr.left.type === 'TypeofExpr' &&
|
|
665
|
-
condExpr.left.operand.type === 'Identifier' &&
|
|
666
|
-
condExpr.right.type === 'StringLit') {
|
|
667
|
-
const name = condExpr.left.operand.name;
|
|
668
|
-
const typeStr = condExpr.right.value;
|
|
669
|
-
const TYPE_MAP = {
|
|
670
|
-
'string': T_STRING, 'number': T_NUMBER, 'boolean': T_BOOL,
|
|
671
|
-
'object': T_NAMED('Object'), 'function': T_FN([], T_ANY),
|
|
672
|
-
'undefined': T_VOID, 'bigint': T_NAMED('BigInt'), 'symbol': T_NAMED('Symbol'),
|
|
673
|
-
};
|
|
674
|
-
const narrowedTrue = TYPE_MAP[typeStr] || T_UNKNOWN;
|
|
675
|
-
return { varName: name, trueType: narrowedTrue, falseType: env.get(name) || T_UNKNOWN };
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// x instanceof Class
|
|
679
|
-
if (condExpr.type === 'BinaryExpr' && condExpr.op === 'instanceof' &&
|
|
680
|
-
condExpr.left.type === 'Identifier') {
|
|
681
|
-
const name = condExpr.left.name;
|
|
682
|
-
const cls = condExpr.right.type === 'Identifier' ? condExpr.right.name : null;
|
|
683
|
-
if (cls) return { varName: name, trueType: T_NAMED(cls), falseType: env.get(name) || T_UNKNOWN };
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Boolean: x (x is truthy — remove null/undefined/false/0)
|
|
687
|
-
if (condExpr.type === 'Identifier') {
|
|
688
|
-
const name = condExpr.name;
|
|
689
|
-
const t = env.get(name);
|
|
690
|
-
if (t && t.kind === 'union') {
|
|
691
|
-
const narrowed = T_UNION(...t.members.filter(m => m.kind !== 'null' && m.kind !== 'void'));
|
|
692
|
-
return { varName: name, trueType: narrowed, falseType: T_UNION(T_NULL, T_VOID) };
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return null;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// ── Register built-in globals ──────────────────────────────────────
|
|
700
|
-
_registerBuiltins(env) {
|
|
701
|
-
env.set('console', T_NAMED('Console'));
|
|
702
|
-
env.set('process', T_NAMED('Process'));
|
|
703
|
-
env.set('Math', T_NAMED('Math'));
|
|
704
|
-
env.set('JSON', T_NAMED('JSON'));
|
|
705
|
-
env.set('Date', T_NAMED('Date'));
|
|
706
|
-
env.set('Promise', T_NAMED('Promise'));
|
|
707
|
-
env.set('Error', T_NAMED('Error'));
|
|
708
|
-
env.set('Buffer', T_NAMED('Buffer'));
|
|
709
|
-
env.set('RegExp', T_NAMED('RegExp'));
|
|
710
|
-
env.set('Map', T_NAMED('Map'));
|
|
711
|
-
env.set('Set', T_NAMED('Set'));
|
|
712
|
-
env.set('WeakMap', T_NAMED('WeakMap'));
|
|
713
|
-
env.set('WeakSet', T_NAMED('WeakSet'));
|
|
714
|
-
env.set('Symbol', T_NAMED('Symbol'));
|
|
715
|
-
env.set('Proxy', T_NAMED('Proxy'));
|
|
716
|
-
env.set('Reflect', T_NAMED('Reflect'));
|
|
717
|
-
env.set('setTimeout', T_FN([T_FN([], T_VOID), T_NUMBER], T_VOID));
|
|
718
|
-
env.set('setInterval', T_FN([T_FN([], T_VOID), T_NUMBER], T_VOID));
|
|
719
|
-
env.set('clearTimeout', T_ANY);
|
|
720
|
-
env.set('clearInterval', T_ANY);
|
|
721
|
-
env.set('parseInt', T_FN([T_STRING, T_INT], T_INT));
|
|
722
|
-
env.set('parseFloat', T_FN([T_STRING], T_FLOAT));
|
|
723
|
-
env.set('isNaN', T_FN([T_NUMBER], T_BOOL));
|
|
724
|
-
env.set('isFinite', T_FN([T_NUMBER], T_BOOL));
|
|
725
|
-
env.set('require', T_FN([T_STRING], T_ANY));
|
|
726
|
-
env.set('fetch', T_FN([T_STRING], T_ANY));
|
|
727
|
-
env.set('print', T_FN([], T_VOID));
|
|
728
|
-
env.set('undefined', T_VOID);
|
|
729
|
-
env.set('Infinity', T_NUMBER);
|
|
730
|
-
env.set('NaN', T_NUMBER);
|
|
731
|
-
env.set('globalThis', T_NAMED('Object'));
|
|
732
|
-
env.set('Object', T_NAMED('Object'));
|
|
733
|
-
env.set('Array', T_NAMED('Array'));
|
|
734
|
-
env.set('String', T_NAMED('String'));
|
|
735
|
-
env.set('Number', T_NAMED('Number'));
|
|
736
|
-
env.set('Boolean', T_NAMED('Boolean'));
|
|
737
|
-
env.set('Function', T_NAMED('Function'));
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// ── Statement checking ─────────────────────────────────────────────
|
|
741
|
-
_checkStmts(stmts, env) {
|
|
742
|
-
for (const node of stmts) this._checkStmt(node, env);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
_checkStmt(node, env) {
|
|
746
|
-
if (!node) return;
|
|
747
|
-
switch (node.type) {
|
|
748
|
-
|
|
749
|
-
case 'VarDecl': {
|
|
750
|
-
const declared = node.typeAnn ? parseAnnotation(node.typeAnn) : null;
|
|
751
|
-
let actual = null;
|
|
752
|
-
if (node.init) {
|
|
753
|
-
actual = this._inferType(node.init, env);
|
|
754
|
-
}
|
|
755
|
-
if (declared && actual && actual.kind !== 'unknown') {
|
|
756
|
-
if (!this._isAssignable(actual, declared)) {
|
|
757
|
-
this._err(
|
|
758
|
-
`Type '${typeStr(actual)}' is not assignable to '${node.name}: ${typeStr(declared)}'`,
|
|
759
|
-
node.loc,
|
|
760
|
-
`Change the value to match '${typeStr(declared)}' or update the annotation`
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
env.set(node.name, declared || actual || T_UNKNOWN);
|
|
765
|
-
break;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
case 'DestructureDecl': {
|
|
769
|
-
const srcType = node.init ? this._inferType(node.init, env) : T_UNKNOWN;
|
|
770
|
-
if (node.patternType === 'object') {
|
|
771
|
-
for (const p of node.pattern) {
|
|
772
|
-
// Try to pull member type from srcType
|
|
773
|
-
let memberType = T_UNKNOWN;
|
|
774
|
-
if (srcType.kind === 'object' && srcType.shape.has(p.key)) {
|
|
775
|
-
memberType = srcType.shape.get(p.key);
|
|
776
|
-
}
|
|
777
|
-
env.set(p.alias, memberType);
|
|
778
|
-
}
|
|
779
|
-
} else {
|
|
780
|
-
for (let i = 0; i < node.pattern.length; i++) {
|
|
781
|
-
const p = node.pattern[i];
|
|
782
|
-
if (!p) continue;
|
|
783
|
-
let elemType = T_UNKNOWN;
|
|
784
|
-
if (srcType.kind === 'tuple' && srcType.types[i]) elemType = srcType.types[i];
|
|
785
|
-
else if (srcType.kind === 'generic' && srcType.name === 'Array') elemType = srcType.args[0] || T_UNKNOWN;
|
|
786
|
-
env.set(p.name, elemType);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
break;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
case 'FnDecl': {
|
|
793
|
-
const retType = node.retType ? parseAnnotation(node.retType) : null;
|
|
794
|
-
const paramTypes = node.params.map(p => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
|
|
795
|
-
if (node.name) env.set(node.name, T_FN(paramTypes, retType || T_UNKNOWN));
|
|
796
|
-
|
|
797
|
-
const fnEnv = env.childFn(retType, node.async);
|
|
798
|
-
for (let i = 0; i < node.params.length; i++) {
|
|
799
|
-
fnEnv.set(node.params[i].name, paramTypes[i]);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
if (node.inline) {
|
|
803
|
-
const bodyType = this._inferType(node.body, fnEnv);
|
|
804
|
-
if (retType && bodyType && bodyType.kind !== 'unknown') {
|
|
805
|
-
if (!this._isAssignable(bodyType, retType)) {
|
|
806
|
-
this._err(
|
|
807
|
-
`Function '${node.name || '(anon)'}' returns '${typeStr(bodyType)}' but declared '${typeStr(retType)}'`,
|
|
808
|
-
node.loc,
|
|
809
|
-
`Fix return type or update the annotation`
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
} else {
|
|
814
|
-
this._checkStmts(node.body, fnEnv);
|
|
815
|
-
}
|
|
816
|
-
break;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
case 'ClassDecl': {
|
|
820
|
-
env.set(node.name, T_NAMED(node.name));
|
|
821
|
-
const clsEnv = env.child();
|
|
822
|
-
// Build shape for structural typing later
|
|
823
|
-
const shape = new Map();
|
|
824
|
-
for (const f of node.fields) {
|
|
825
|
-
const ft = f.typeAnn ? parseAnnotation(f.typeAnn) : T_UNKNOWN;
|
|
826
|
-
clsEnv.set(f.name, ft);
|
|
827
|
-
shape.set(f.name, ft);
|
|
828
|
-
}
|
|
829
|
-
clsEnv.set('self', T_OBJECT(shape));
|
|
830
|
-
for (const m of node.methods) {
|
|
831
|
-
this._checkStmt(m, clsEnv);
|
|
832
|
-
}
|
|
833
|
-
break;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
case 'TypeDecl':
|
|
837
|
-
for (const v of node.variants) {
|
|
838
|
-
if (v.fields.length === 0) env.set(v.name, T_NAMED(node.name));
|
|
839
|
-
else env.set(v.name, T_FN(v.fields.map(() => T_ANY), T_NAMED(node.name)));
|
|
840
|
-
}
|
|
841
|
-
break;
|
|
842
|
-
|
|
843
|
-
case 'InterfaceDecl':
|
|
844
|
-
env.set(node.name, T_NAMED(node.name));
|
|
845
|
-
break;
|
|
846
|
-
|
|
847
|
-
case 'EnumDecl':
|
|
848
|
-
env.set(node.name, T_NAMED(node.name));
|
|
849
|
-
break;
|
|
850
|
-
|
|
851
|
-
case 'IfStmt': {
|
|
852
|
-
this._inferType(node.cond, env);
|
|
853
|
-
// Type narrowing via control flow
|
|
854
|
-
const narrowing = this._analyzeNarrowingCondition(node.cond, env);
|
|
855
|
-
const thenEnv = narrowing ? env.narrow(narrowing.varName, narrowing.trueType) : env.child();
|
|
856
|
-
const elseEnv = narrowing ? env.narrow(narrowing.varName, narrowing.falseType) : env.child();
|
|
857
|
-
|
|
858
|
-
this._checkStmts(node.then, thenEnv);
|
|
859
|
-
for (const ei of node.elseifs) {
|
|
860
|
-
this._inferType(ei.cond, env);
|
|
861
|
-
this._checkStmts(ei.body, env.child());
|
|
862
|
-
}
|
|
863
|
-
if (node.else_) this._checkStmts(node.else_, elseEnv);
|
|
864
|
-
break;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
case 'ForInStmt': {
|
|
868
|
-
const iterType = this._inferType(node.iter, env);
|
|
869
|
-
const inner = env.child();
|
|
870
|
-
if (iterType.kind === 'generic' && iterType.args.length > 0) inner.set(node.var, iterType.args[0]);
|
|
871
|
-
else if (iterType.kind === 'tuple' && iterType.types.length > 0) inner.set(node.var, T_UNION(...iterType.types));
|
|
872
|
-
else inner.set(node.var, T_UNKNOWN);
|
|
873
|
-
this._checkStmts(node.body, inner);
|
|
874
|
-
break;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
case 'WhileStmt':
|
|
878
|
-
this._inferType(node.cond, env);
|
|
879
|
-
this._checkStmts(node.body, env.child());
|
|
880
|
-
break;
|
|
881
|
-
|
|
882
|
-
case 'DoWhileStmt':
|
|
883
|
-
this._checkStmts(node.body, env.child());
|
|
884
|
-
this._inferType(node.cond, env);
|
|
885
|
-
break;
|
|
886
|
-
|
|
887
|
-
case 'MatchStmt':
|
|
888
|
-
this._inferType(node.subject, env);
|
|
889
|
-
for (const arm of node.arms) {
|
|
890
|
-
const inner = env.child();
|
|
891
|
-
if (arm.pattern.type === 'VariantPat') {
|
|
892
|
-
for (const b of arm.pattern.bindings) inner.set(b, T_UNKNOWN);
|
|
893
|
-
}
|
|
894
|
-
if (arm.guard) this._inferType(arm.guard, inner);
|
|
895
|
-
this._checkStmts(arm.body, inner);
|
|
896
|
-
}
|
|
897
|
-
break;
|
|
898
|
-
|
|
899
|
-
case 'ReturnStmt': {
|
|
900
|
-
const retType = env.retType;
|
|
901
|
-
if (node.value) {
|
|
902
|
-
const actual = this._inferType(node.value, env);
|
|
903
|
-
if (retType && actual && actual.kind !== 'unknown' && retType.kind !== 'unknown') {
|
|
904
|
-
const effectiveRet = env.isAsync && retType.kind === 'generic' && retType.name === 'Promise'
|
|
905
|
-
? retType.args[0]
|
|
906
|
-
: retType;
|
|
907
|
-
if (!this._isAssignable(actual, effectiveRet)) {
|
|
908
|
-
this._err(
|
|
909
|
-
`Return type '${typeStr(actual)}' is not assignable to declared '${typeStr(effectiveRet)}'`,
|
|
910
|
-
node.loc,
|
|
911
|
-
`Fix return expression or update return type annotation`
|
|
912
|
-
);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
} else if (retType && retType.kind !== 'void' && retType.kind !== 'any' && retType.kind !== 'unknown') {
|
|
916
|
-
this._warn(
|
|
917
|
-
`Missing return value — function declares return type '${typeStr(retType)}'`,
|
|
918
|
-
node.loc
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
break;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
case 'ThrowStmt':
|
|
925
|
-
this._inferType(node.value, env);
|
|
926
|
-
break;
|
|
927
|
-
|
|
928
|
-
case 'TryCatchStmt': {
|
|
929
|
-
this._checkStmts(node.tryBody, env.child());
|
|
930
|
-
if (node.catchBody) {
|
|
931
|
-
const inner = env.child();
|
|
932
|
-
if (node.catchParam) inner.set(node.catchParam, T_UNION(T_NAMED('Error'), T_UNKNOWN));
|
|
933
|
-
this._checkStmts(node.catchBody, inner);
|
|
934
|
-
}
|
|
935
|
-
if (node.finallyBody) this._checkStmts(node.finallyBody, env.child());
|
|
936
|
-
break;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
case 'ImportDecl':
|
|
940
|
-
if (node.defaultName) env.set(node.defaultName, T_ANY);
|
|
941
|
-
if (node.namespaceName) env.set(node.namespaceName, T_ANY);
|
|
942
|
-
for (const n of node.names) env.set(typeof n === 'string' ? n : n.alias, T_ANY);
|
|
943
|
-
break;
|
|
944
|
-
|
|
945
|
-
case 'ExportDecl':
|
|
946
|
-
this._checkStmt(node.decl, env);
|
|
947
|
-
break;
|
|
948
|
-
|
|
949
|
-
case 'ExprStmt':
|
|
950
|
-
this._inferType(node.expr, env);
|
|
951
|
-
break;
|
|
952
|
-
|
|
953
|
-
case 'BreakStmt':
|
|
954
|
-
case 'ContinueStmt':
|
|
955
|
-
break;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// ── Type inference ──────────────────────────────────────────────────
|
|
960
|
-
_inferType(expr, env) {
|
|
961
|
-
if (!expr) return T_VOID;
|
|
962
|
-
switch (expr.type) {
|
|
963
|
-
|
|
964
|
-
case 'NumberLit':
|
|
965
|
-
return Number.isInteger(expr.value) ? T_INT : T_FLOAT;
|
|
966
|
-
|
|
967
|
-
case 'StringLit':
|
|
968
|
-
case 'TemplateLit':
|
|
969
|
-
return T_STRING;
|
|
970
|
-
|
|
971
|
-
case 'BoolLit': return T_BOOL;
|
|
972
|
-
case 'NullLit': return T_NULL;
|
|
973
|
-
case 'RegexLit': return T_ANY;
|
|
974
|
-
case 'SelfExpr': return env.get('self') || T_UNKNOWN;
|
|
975
|
-
|
|
976
|
-
case 'Identifier': {
|
|
977
|
-
const t = env.get(expr.name);
|
|
978
|
-
if (t) return t;
|
|
979
|
-
if (this.classes.has(expr.name)) return T_NAMED(expr.name);
|
|
980
|
-
if (this.enums.has(expr.name)) return T_NAMED(expr.name);
|
|
981
|
-
if (this.interfaces.has(expr.name)) return T_NAMED(expr.name);
|
|
982
|
-
return T_UNKNOWN;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
case 'ArrayExpr': {
|
|
986
|
-
if (!expr.items || expr.items.length === 0) return T_ARRAY(T_UNKNOWN);
|
|
987
|
-
const elemTypes = expr.items
|
|
988
|
-
.filter(i => i && i.type !== 'SpreadExpr')
|
|
989
|
-
.map(i => this._inferType(i, env));
|
|
990
|
-
return T_ARRAY(elemTypes.reduce(mergeTypes, T_UNKNOWN));
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
case 'ObjectExpr': {
|
|
994
|
-
const shape = new Map();
|
|
995
|
-
for (const pair of expr.pairs || []) {
|
|
996
|
-
if (pair.spread) continue;
|
|
997
|
-
shape.set(String(pair.key), this._inferType(pair.value, env));
|
|
998
|
-
}
|
|
999
|
-
return T_OBJECT(shape);
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
case 'NewExpr':
|
|
1003
|
-
for (const a of expr.args) this._inferType(a, env);
|
|
1004
|
-
return T_NAMED(expr.callee);
|
|
1005
|
-
|
|
1006
|
-
case 'CallExpr': {
|
|
1007
|
-
const calleeType = this._inferType(expr.callee, env);
|
|
1008
|
-
for (const a of expr.args) this._inferType(a, env);
|
|
1009
|
-
if (calleeType && calleeType.kind === 'fn') {
|
|
1010
|
-
this._checkCallArgs(expr, calleeType, env);
|
|
1011
|
-
return calleeType.ret || T_UNKNOWN;
|
|
1012
|
-
}
|
|
1013
|
-
return T_UNKNOWN;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
case 'OptCallExpr':
|
|
1017
|
-
this._inferType(expr.callee, env);
|
|
1018
|
-
for (const a of expr.args) this._inferType(a, env);
|
|
1019
|
-
return T_UNKNOWN;
|
|
1020
|
-
|
|
1021
|
-
case 'MemberExpr':
|
|
1022
|
-
case 'OptMemberExpr': {
|
|
1023
|
-
const objType = this._inferType(expr.obj, env);
|
|
1024
|
-
// Try to resolve member type from known shapes
|
|
1025
|
-
if (objType.kind === 'object' && objType.shape.has(expr.prop)) {
|
|
1026
|
-
return objType.shape.get(expr.prop);
|
|
1027
|
-
}
|
|
1028
|
-
return T_UNKNOWN;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
case 'IndexExpr':
|
|
1032
|
-
case 'OptIndexExpr': {
|
|
1033
|
-
const objType = this._inferType(expr.obj, env);
|
|
1034
|
-
this._inferType(expr.idx, env);
|
|
1035
|
-
if (objType.kind === 'generic' && objType.args.length > 0) return objType.args[0];
|
|
1036
|
-
if (objType.kind === 'tuple') return T_UNION(...objType.types);
|
|
1037
|
-
if (objType.kind === 'record') return objType.val;
|
|
1038
|
-
return T_UNKNOWN;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
case 'BinaryExpr': {
|
|
1042
|
-
const left = this._inferType(expr.left, env);
|
|
1043
|
-
const right = this._inferType(expr.right, env);
|
|
1044
|
-
const op = expr.op;
|
|
1045
|
-
|
|
1046
|
-
if (['+','-','*','/','%','**'].includes(op)) {
|
|
1047
|
-
if (left.kind === 'primitive' && left.name === 'String') return T_STRING;
|
|
1048
|
-
if (right.kind === 'primitive' && right.name === 'String') return T_STRING;
|
|
1049
|
-
if (left.kind === 'unknown' || right.kind === 'unknown') return T_UNKNOWN;
|
|
1050
|
-
if (left.kind === 'any' || right.kind === 'any') return T_NUMBER;
|
|
1051
|
-
if (left.kind === 'primitive' && right.kind === 'primitive') {
|
|
1052
|
-
const ln = left.name, rn = right.name;
|
|
1053
|
-
if (ln === 'Int' && rn === 'Int') return T_INT;
|
|
1054
|
-
if (['Float','Int'].includes(ln) && ['Float','Int'].includes(rn)) return T_FLOAT;
|
|
1055
|
-
}
|
|
1056
|
-
return T_NUMBER;
|
|
1057
|
-
}
|
|
1058
|
-
if (['===','!==','==','!=','<','<=','>','>=','&&','||','instanceof','in'].includes(op)) return T_BOOL;
|
|
1059
|
-
if (op === '??') return mergeTypes(left, right);
|
|
1060
|
-
if (['&','|','^','<<','>>'].includes(op)) return T_INT;
|
|
1061
|
-
return T_UNKNOWN;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
case 'UnaryExpr': {
|
|
1065
|
-
const operand = this._inferType(expr.operand, env);
|
|
1066
|
-
if (expr.op === '!' || expr.op === 'not') return T_BOOL;
|
|
1067
|
-
if (expr.op === '-') {
|
|
1068
|
-
if (operand.kind === 'primitive' && operand.name === 'Int') return T_INT;
|
|
1069
|
-
if (operand.kind === 'primitive' && operand.name === 'Float') return T_FLOAT;
|
|
1070
|
-
return T_NUMBER;
|
|
1071
|
-
}
|
|
1072
|
-
if (expr.op === '~') return T_INT;
|
|
1073
|
-
return operand;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
case 'UpdateExpr':
|
|
1077
|
-
this._inferType(expr.operand, env);
|
|
1078
|
-
return T_NUMBER;
|
|
1079
|
-
|
|
1080
|
-
case 'TernaryExpr': {
|
|
1081
|
-
const narrowing = this._analyzeNarrowingCondition(expr.cond, env);
|
|
1082
|
-
const thenEnv = narrowing ? env.narrow(narrowing.varName, narrowing.trueType) : env;
|
|
1083
|
-
const elseEnv = narrowing ? env.narrow(narrowing.varName, narrowing.falseType) : env;
|
|
1084
|
-
this._inferType(expr.cond, env);
|
|
1085
|
-
const then = this._inferType(expr.then, thenEnv);
|
|
1086
|
-
const else_ = this._inferType(expr.else_, elseEnv);
|
|
1087
|
-
return mergeTypes(then, else_);
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
case 'AssignExpr': {
|
|
1091
|
-
const valueType = this._inferType(expr.value, env);
|
|
1092
|
-
const targetType = this._inferType(expr.target, env);
|
|
1093
|
-
if (targetType && targetType.kind !== 'unknown' && valueType && valueType.kind !== 'unknown') {
|
|
1094
|
-
if (!this._isAssignable(valueType, targetType)) {
|
|
1095
|
-
this._err(
|
|
1096
|
-
`Cannot assign '${typeStr(valueType)}' to type '${typeStr(targetType)}'`,
|
|
1097
|
-
expr.target.loc || expr.loc,
|
|
1098
|
-
`Ensure the value matches the declared type`
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
return valueType;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
case 'AwaitExpr': {
|
|
1106
|
-
const inner = this._inferType(expr.operand, env);
|
|
1107
|
-
if (inner.kind === 'generic' && inner.name === 'Promise') return inner.args[0] || T_UNKNOWN;
|
|
1108
|
-
return T_UNKNOWN;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
case 'TypeofExpr':
|
|
1112
|
-
this._inferType(expr.operand, env);
|
|
1113
|
-
return T_STRING;
|
|
1114
|
-
|
|
1115
|
-
case 'PipeExpr':
|
|
1116
|
-
this._inferType(expr.left, env);
|
|
1117
|
-
return this._inferType(expr.right, env);
|
|
1118
|
-
|
|
1119
|
-
case 'SpreadExpr':
|
|
1120
|
-
this._inferType(expr.expr, env);
|
|
1121
|
-
return T_UNKNOWN;
|
|
1122
|
-
|
|
1123
|
-
case 'RangeExpr':
|
|
1124
|
-
this._inferType(expr.start, env);
|
|
1125
|
-
this._inferType(expr.end, env);
|
|
1126
|
-
return T_ARRAY(T_INT);
|
|
1127
|
-
|
|
1128
|
-
case 'LambdaExpr': {
|
|
1129
|
-
const inner = env.child();
|
|
1130
|
-
for (const p of expr.params) inner.set(p.name, p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
|
|
1131
|
-
const ret = this._inferType(expr.body, inner);
|
|
1132
|
-
const paramTypes = expr.params.map(p => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
|
|
1133
|
-
return T_FN(paramTypes, ret);
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
case 'FnDecl': {
|
|
1137
|
-
const retType = expr.retType ? parseAnnotation(expr.retType) : null;
|
|
1138
|
-
const inner = env.childFn(retType, expr.async);
|
|
1139
|
-
for (const p of expr.params || []) inner.set(p.name, p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
|
|
1140
|
-
const paramTypes = (expr.params || []).map(p => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
|
|
1141
|
-
if (expr.inline) {
|
|
1142
|
-
const bodyType = this._inferType(expr.body, inner);
|
|
1143
|
-
return T_FN(paramTypes, retType || bodyType);
|
|
1144
|
-
}
|
|
1145
|
-
this._checkStmts(expr.body, inner);
|
|
1146
|
-
return T_FN(paramTypes, retType || T_UNKNOWN);
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
case 'CastExpr':
|
|
1150
|
-
this._inferType(expr.expr, env);
|
|
1151
|
-
return expr.castType ? parseAnnotation(expr.castType) : T_ANY;
|
|
1152
|
-
|
|
1153
|
-
case 'SatisfiesExpr':
|
|
1154
|
-
case 'NonNullExpr':
|
|
1155
|
-
case 'AsConstExpr': {
|
|
1156
|
-
const t = this._inferType(expr.expr, env);
|
|
1157
|
-
if (expr.type === 'SatisfiesExpr' && expr.satType) {
|
|
1158
|
-
const expected = parseAnnotation(expr.satType);
|
|
1159
|
-
if (!this._isAssignable(t, expected)) {
|
|
1160
|
-
this._err(`Type '${typeStr(t)}' does not satisfy '${typeStr(expected)}'`, expr.loc);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
return t;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
case 'IsExpr':
|
|
1167
|
-
this._inferType(expr.expr, env);
|
|
1168
|
-
return T_BOOL;
|
|
1169
|
-
|
|
1170
|
-
default:
|
|
1171
|
-
return T_UNKNOWN;
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
// ── Check call arguments ──────────────────────────────────────────
|
|
1176
|
-
_checkCallArgs(callExpr, fnType, env) {
|
|
1177
|
-
const args = callExpr.args || [];
|
|
1178
|
-
const params = fnType.params || [];
|
|
1179
|
-
for (let i = 0; i < args.length; i++) {
|
|
1180
|
-
const argType = this._inferType(args[i], env);
|
|
1181
|
-
const paramType = params[i] || T_ANY;
|
|
1182
|
-
if (paramType.kind !== 'any' && argType && argType.kind !== 'unknown') {
|
|
1183
|
-
if (!this._isAssignable(argType, paramType)) {
|
|
1184
|
-
this._err(
|
|
1185
|
-
`Argument ${i+1}: '${typeStr(argType)}' is not assignable to '${typeStr(paramType)}'`,
|
|
1186
|
-
callExpr.loc,
|
|
1187
|
-
`Check function signature`
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
module.exports = {
|
|
1196
|
-
FluxTypeChecker,
|
|
1197
|
-
parseAnnotation,
|
|
1198
|
-
typeStr,
|
|
1199
|
-
isAssignable,
|
|
1200
|
-
mergeTypes,
|
|
1201
|
-
splitAtTopLevel,
|
|
1202
|
-
T_ANY, T_UNKNOWN, T_NEVER, T_VOID, T_NULL,
|
|
1203
|
-
T_STRING, T_INT, T_FLOAT, T_NUMBER, T_BOOL,
|
|
1204
|
-
T_UNION, T_INTERSECTION, T_ARRAY, T_TUPLE, T_NULLABLE,
|
|
1205
|
-
T_NAMED, T_FN, T_OBJECT, T_RECORD, T_LITERAL,
|
|
1206
|
-
};
|