@xnoxs/flux-lang 3.2.1 → 3.3.0

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.
@@ -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
- };