@jalvin/compiler 1.0.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.
@@ -0,0 +1,2196 @@
1
+ "use strict";
2
+ // ─────────────────────────────────────────────────────────────────────────────
3
+ // Jalvin Type Checker
4
+ //
5
+ // Performs:
6
+ // • Symbol resolution (builds a scope tree)
7
+ // • Type inference for expressions and declarations
8
+ // • Null safety enforcement (T vs T?)
9
+ // • Exhaustiveness checking for `when` on sealed classes
10
+ // • suspend / async context validation
11
+ // • Override checking
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.TypeChecker = exports.Scope = exports.T_UNKNOWN = exports.T_ERROR = exports.T_NOTHING = exports.T_ANY = exports.T_UNIT = exports.T_CHAR = exports.T_STRING = exports.T_BOOL = exports.T_DOUBLE = exports.T_FLOAT = exports.T_LONG = exports.T_INT = void 0;
48
+ exports.nullable = nullable;
49
+ exports.unwrapNullable = unwrapNullable;
50
+ exports.isNullable = isNullable;
51
+ exports.classType = classType;
52
+ exports.typeCheck = typeCheck;
53
+ const AST = __importStar(require("./ast.js"));
54
+ const diagnostics_js_1 = require("./diagnostics.js");
55
+ exports.T_INT = { tag: "int" };
56
+ exports.T_LONG = { tag: "long" };
57
+ exports.T_FLOAT = { tag: "float" };
58
+ exports.T_DOUBLE = { tag: "double" };
59
+ exports.T_BOOL = { tag: "boolean" };
60
+ exports.T_STRING = { tag: "string" };
61
+ exports.T_CHAR = { tag: "char" };
62
+ exports.T_UNIT = { tag: "unit" };
63
+ exports.T_ANY = { tag: "any" };
64
+ exports.T_NOTHING = { tag: "nothing" };
65
+ exports.T_ERROR = { tag: "error" };
66
+ exports.T_UNKNOWN = { tag: "unknown" };
67
+ /** JavaScript/TypeScript built-in globals that should pass through the typechecker without error. */
68
+ const JS_GLOBALS = new Set([
69
+ "Math", "JSON", "console", "Object", "Array", "Promise", "Date", "Error",
70
+ "Number", "Boolean", "Symbol", "globalThis", "window", "document",
71
+ "parseInt", "parseFloat", "isNaN", "isFinite", "encodeURIComponent",
72
+ "decodeURIComponent", "setTimeout", "clearTimeout", "setInterval", "clearInterval",
73
+ "fetch", "URL", "URLSearchParams", "FormData", "Blob", "File",
74
+ "localStorage", "sessionStorage", "performance", "navigator", "location",
75
+ "HTMLElement", "Element", "Event", "CustomEvent",
76
+ ]);
77
+ function nullable(t) {
78
+ if (t.tag === "nullable")
79
+ return t;
80
+ if (t.tag === "nothing")
81
+ return exports.T_UNIT; // Nothing? ≈ Unit?
82
+ return { tag: "nullable", inner: t };
83
+ }
84
+ function unwrapNullable(t) {
85
+ return t.tag === "nullable" ? t.inner : t;
86
+ }
87
+ function isNullable(t) {
88
+ return t.tag === "nullable";
89
+ }
90
+ function classType(name, typeArgs = [], decl) {
91
+ return decl !== undefined
92
+ ? { tag: "class", name, typeArgs, decl }
93
+ : { tag: "class", name, typeArgs };
94
+ }
95
+ class Scope {
96
+ parent;
97
+ symbols = new Map();
98
+ usedNames = new Set();
99
+ constructor(parent = null) {
100
+ this.parent = parent;
101
+ }
102
+ define(sym) {
103
+ const existing = this.symbols.get(sym.name);
104
+ if (existing)
105
+ return existing;
106
+ this.symbols.set(sym.name, sym);
107
+ return true;
108
+ }
109
+ lookup(name) {
110
+ return this.symbols.get(name) ?? this.parent?.lookup(name) ?? null;
111
+ }
112
+ markUsed(name) {
113
+ if (this.symbols.has(name)) {
114
+ this.usedNames.add(name);
115
+ }
116
+ else {
117
+ this.parent?.markUsed(name);
118
+ }
119
+ }
120
+ /** Returns symbols defined in this scope that were never referenced. */
121
+ localUnused() {
122
+ return [...this.symbols.values()].filter((s) => !this.usedNames.has(s.name));
123
+ }
124
+ }
125
+ exports.Scope = Scope;
126
+ // ---------------------------------------------------------------------------
127
+ // Type environment — built-in types and stdlib symbols
128
+ // ---------------------------------------------------------------------------
129
+ class TypeEnv {
130
+ global = new Scope();
131
+ constructor() {
132
+ this.seedBuiltins();
133
+ }
134
+ sym(name, type) {
135
+ this.global.define({ name, type, mutable: false, span: AST.BUILTIN_SPAN });
136
+ }
137
+ seedBuiltins() {
138
+ // Global functions
139
+ this.sym("println", { tag: "func", params: [exports.T_ANY], paramNames: ["message"], ret: exports.T_UNIT, suspend: false });
140
+ this.sym("print", { tag: "func", params: [exports.T_ANY], paramNames: ["message"], ret: exports.T_UNIT, suspend: false });
141
+ this.sym("readLine", { tag: "func", params: [], ret: nullable(exports.T_STRING), suspend: false });
142
+ this.sym("TODO", { tag: "func", params: [nullable(exports.T_STRING)], paramNames: ["reason"], ret: exports.T_NOTHING, suspend: false });
143
+ this.sym("error", { tag: "func", params: [exports.T_STRING], paramNames: ["message"], ret: exports.T_NOTHING, suspend: false });
144
+ this.sym("require", { tag: "func", params: [exports.T_BOOL, nullable(exports.T_STRING)], paramNames: ["value", "lazyMessage"], ret: exports.T_UNIT, suspend: false });
145
+ this.sym("check", { tag: "func", params: [exports.T_BOOL, nullable(exports.T_STRING)], paramNames: ["value", "lazyMessage"], ret: exports.T_UNIT, suspend: false });
146
+ this.sym("assert", { tag: "func", params: [exports.T_BOOL, nullable(exports.T_STRING)], paramNames: ["value", "lazyMessage"], ret: exports.T_UNIT, suspend: false });
147
+ // Collection builders
148
+ const listType = classType("List", [exports.T_UNKNOWN]);
149
+ const mutableListType = classType("MutableList", [exports.T_UNKNOWN]);
150
+ const setType = classType("Set", [exports.T_UNKNOWN]);
151
+ const mapType = classType("Map", [exports.T_UNKNOWN, exports.T_UNKNOWN]);
152
+ this.sym("listOf", { tag: "func", params: [exports.T_UNKNOWN], ret: listType, suspend: false });
153
+ this.sym("mutableListOf", { tag: "func", params: [exports.T_UNKNOWN], ret: mutableListType, suspend: false });
154
+ this.sym("setOf", { tag: "func", params: [exports.T_UNKNOWN], ret: setType, suspend: false });
155
+ this.sym("mapOf", { tag: "func", params: [exports.T_UNKNOWN], ret: mapType, suspend: false });
156
+ this.sym("emptyList", { tag: "func", params: [], ret: listType, suspend: false });
157
+ this.sym("emptyMap", { tag: "func", params: [], ret: mapType, suspend: false });
158
+ this.sym("emptySet", { tag: "func", params: [], ret: setType, suspend: false });
159
+ this.sym("mutableSetOf", { tag: "func", params: [exports.T_UNKNOWN], ret: classType("MutableSet", [exports.T_UNKNOWN]), suspend: false });
160
+ this.sym("mutableMapOf", { tag: "func", params: [exports.T_UNKNOWN], ret: classType("MutableMap", [exports.T_UNKNOWN, exports.T_UNKNOWN]), suspend: false });
161
+ // Pair / Triple constructors
162
+ const pairType = classType("Pair", [exports.T_UNKNOWN, exports.T_UNKNOWN]);
163
+ const tripleType = classType("Triple", [exports.T_UNKNOWN, exports.T_UNKNOWN, exports.T_UNKNOWN]);
164
+ this.sym("Pair", pairType);
165
+ this.sym("Triple", tripleType);
166
+ // Coroutines
167
+ const deferredType = classType("Deferred", [exports.T_UNKNOWN]);
168
+ this.sym("withContext", { tag: "func", params: [exports.T_ANY, { tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: true }], paramNames: ["context", "block"], ret: exports.T_UNKNOWN, suspend: true });
169
+ this.sym("delay", { tag: "func", params: [exports.T_LONG], paramNames: ["timeMillis"], ret: exports.T_UNIT, suspend: true });
170
+ // Dispatchers — no-ops in JS; exist for source compat
171
+ this.sym("Dispatchers", classType("Dispatchers"));
172
+ // Bibi HTTP client
173
+ this.sym("Bibi", classType("Bibi"));
174
+ this.sym("BibiError", classType("BibiError"));
175
+ // State & MVVM
176
+ this.sym("mutableStateOf", { tag: "func", params: [exports.T_UNKNOWN], paramNames: ["value"], ret: classType("MutableState", [exports.T_UNKNOWN]), suspend: false });
177
+ this.sym("remember", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["calculation"], ret: exports.T_UNKNOWN, suspend: false });
178
+ this.sym("useViewModel", { tag: "func", params: [exports.T_STRING, { tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["key", "factory"], ret: classType("ViewModel"), suspend: false });
179
+ // Keep old `viewModel` alias for source compat
180
+ this.sym("viewModel", { tag: "func", params: [], ret: classType("ViewModel"), suspend: false });
181
+ this.sym("collectAsState", { tag: "func", params: [exports.T_UNKNOWN], paramNames: ["flow"], ret: exports.T_UNKNOWN, suspend: false });
182
+ // React effect hooks
183
+ const effectDepsType = classType("List", [exports.T_UNKNOWN]);
184
+ this.sym("LaunchedEffect", { tag: "func", params: [effectDepsType, { tag: "func", params: [], ret: exports.T_UNIT, suspend: true }], paramNames: ["deps", "block"], ret: exports.T_UNIT, suspend: false });
185
+ this.sym("DisposableEffect", { tag: "func", params: [effectDepsType, { tag: "func", params: [], ret: exports.T_UNIT, suspend: false }], paramNames: ["deps", "block"], ret: exports.T_UNIT, suspend: false });
186
+ this.sym("SideEffect", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNIT, suspend: false }], paramNames: ["block"], ret: exports.T_UNIT, suspend: false });
187
+ // Scope functions (top-level `with` and standalone `run`)
188
+ this.sym("with", { tag: "func", params: [exports.T_UNKNOWN, { tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["receiver", "block"], ret: exports.T_UNKNOWN, suspend: false });
189
+ this.sym("run", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["block"], ret: exports.T_UNKNOWN, suspend: false });
190
+ // Math library
191
+ this.sym("abs", { tag: "func", params: [exports.T_INT], paramNames: ["n"], ret: exports.T_INT, suspend: false });
192
+ this.sym("minOf", { tag: "func", params: [exports.T_INT, exports.T_INT], paramNames: ["a", "b"], ret: exports.T_INT, suspend: false });
193
+ this.sym("maxOf", { tag: "func", params: [exports.T_INT, exports.T_INT], paramNames: ["a", "b"], ret: exports.T_INT, suspend: false });
194
+ this.sym("sqrt", { tag: "func", params: [exports.T_DOUBLE], paramNames: ["x"], ret: exports.T_DOUBLE, suspend: false });
195
+ // IntRange
196
+ const intRangeType = classType("IntRange");
197
+ this.sym("downTo", { tag: "func", params: [exports.T_INT, exports.T_INT], ret: intRangeType, suspend: false });
198
+ this.sym("until", { tag: "func", params: [exports.T_INT, exports.T_INT], ret: intRangeType, suspend: false });
199
+ this.sym("step", { tag: "func", params: [intRangeType, exports.T_INT], ret: intRangeType, suspend: false });
200
+ // Result<T>
201
+ const resultType = classType("Result", [exports.T_UNKNOWN]);
202
+ this.sym("runCatching", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["block"], ret: resultType, suspend: false });
203
+ this.sym("runCatchingAsync", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: true }], paramNames: ["block"], ret: resultType, suspend: true });
204
+ // StringBuilder + builders
205
+ const sbType = classType("StringBuilder");
206
+ this.sym("buildString", { tag: "func", params: [{ tag: "func", params: [sbType], ret: exports.T_UNIT, suspend: false }], paramNames: ["builderAction"], ret: exports.T_STRING, suspend: false });
207
+ this.sym("buildList", { tag: "func", params: [{ tag: "func", params: [exports.T_ANY], ret: exports.T_UNIT, suspend: false }], paramNames: ["builderAction"], ret: listType, suspend: false });
208
+ this.sym("buildSet", { tag: "func", params: [{ tag: "func", params: [exports.T_ANY], ret: exports.T_UNIT, suspend: false }], paramNames: ["builderAction"], ret: setType, suspend: false });
209
+ this.sym("buildMap", { tag: "func", params: [{ tag: "func", params: [exports.T_ANY], ret: exports.T_UNIT, suspend: false }], paramNames: ["builderAction"], ret: mapType, suspend: false });
210
+ // Regex
211
+ const regexType = classType("Regex");
212
+ this.sym("Regex", { tag: "func", params: [exports.T_STRING, exports.T_STRING], paramNames: ["pattern", "options"], ret: regexType, suspend: false });
213
+ // Timing
214
+ this.sym("measureTimeMillis", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNIT, suspend: false }], paramNames: ["block"], ret: exports.T_LONG, suspend: false });
215
+ this.sym("measureTimedValue", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["block"], ret: classType("TimedValue", [exports.T_UNKNOWN]), suspend: false });
216
+ // Random
217
+ const randomType = classType("Random");
218
+ this.sym("Random", { tag: "func", params: [], ret: randomType, suspend: false });
219
+ this.sym("randomUUID", { tag: "func", params: [], ret: exports.T_STRING, suspend: false });
220
+ // coroutineScope / supervisorScope
221
+ this.sym("coroutineScope", { tag: "func", params: [{ tag: "func", params: [classType("CoroutineScope")], ret: exports.T_UNKNOWN, suspend: true }], paramNames: ["block"], ret: exports.T_UNKNOWN, suspend: true });
222
+ this.sym("supervisorScope", { tag: "func", params: [{ tag: "func", params: [classType("CoroutineScope")], ret: exports.T_UNKNOWN, suspend: true }], paramNames: ["block"], ret: exports.T_UNKNOWN, suspend: true });
223
+ }
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // TypeChecker
227
+ // ---------------------------------------------------------------------------
228
+ class TypeChecker {
229
+ diag;
230
+ env = new TypeEnv();
231
+ /** Stack of scopes (innermost last) */
232
+ scope;
233
+ /** Whether we're inside a suspend context */
234
+ inSuspend = false;
235
+ /** Whether we're inside a component */
236
+ inComponent = false;
237
+ /** Map from node to resolved type (for IDE use) */
238
+ typeMap = new Map();
239
+ /**
240
+ * Binary expressions that resolve to operator overloads.
241
+ * Maps AST.BinaryExpr → the method name to call (e.g. "plus", "compareTo").
242
+ * Consumed by codegen to emit `left.method(right)` instead of `left op right`.
243
+ */
244
+ operatorOverloadMap = new Map();
245
+ /**
246
+ * Sealed class name → set of direct subclass names.
247
+ * Populated after hoisting so exhaustiveness checking can use it.
248
+ */
249
+ sealedSubclasses = new Map();
250
+ /**
251
+ * Enum class name → ordered list of entry names.
252
+ * Populated at the same time as sealedSubclasses.
253
+ */
254
+ enumEntries = new Map();
255
+ constructor(diag) {
256
+ this.diag = diag;
257
+ this.scope = this.env.global;
258
+ }
259
+ checkProgram(program) {
260
+ // First pass: hoist all top-level declarations into global scope
261
+ for (const decl of program.declarations) {
262
+ this.hoistDecl(decl);
263
+ }
264
+ // Second pass: build sealed-class subclass map and enum entries for exhaustiveness checking
265
+ this.buildExhaustivenessMap(program.declarations);
266
+ // Third pass: type-check bodies
267
+ for (const decl of program.declarations) {
268
+ this.checkTopLevelDecl(decl);
269
+ }
270
+ }
271
+ /**
272
+ * Walk all class-like declarations and register any that extend a known
273
+ * sealed class into `sealedSubclasses`; also records enum entry names.
274
+ */
275
+ buildExhaustivenessMap(decls) {
276
+ const sealedNames = new Set();
277
+ const collectNames = (ds, prefix = "") => {
278
+ for (const d of ds) {
279
+ if (d.kind === "SealedClassDecl") {
280
+ const name = prefix ? `${prefix}.${d.name}` : d.name;
281
+ sealedNames.add(name);
282
+ this.sealedSubclasses.set(name, new Set());
283
+ if (d.body)
284
+ collectNames(d.body.members, name);
285
+ }
286
+ else if (d.kind === "EnumClassDecl") {
287
+ const name = prefix ? `${prefix}.${d.name}` : d.name;
288
+ this.enumEntries.set(name, d.entries.map((e) => e.name));
289
+ if (d.body)
290
+ collectNames(d.body.members, name);
291
+ }
292
+ else if (d.kind === "ClassDecl" || d.kind === "DataClassDecl" || d.kind === "InterfaceDecl") {
293
+ const name = prefix ? `${prefix}.${d.name}` : d.name;
294
+ if (d.body)
295
+ collectNames(d.body.members, name);
296
+ }
297
+ else if (d.kind === "ObjectDecl" && d.name) {
298
+ const name = prefix ? `${prefix}.${d.name}` : d.name;
299
+ if (d.body)
300
+ collectNames(d.body.members, name);
301
+ }
302
+ }
303
+ };
304
+ const registerSubclasses = (ds, prefix = "") => {
305
+ for (const d of ds) {
306
+ if (d.kind === "ClassDecl" ||
307
+ d.kind === "DataClassDecl" ||
308
+ d.kind === "ObjectDecl" ||
309
+ d.kind === "SealedClassDecl") {
310
+ const declName = prefix ? `${prefix}.${d.name}` : d.name;
311
+ for (const superEntry of d.superTypes) {
312
+ const superName = this.typeRefName(superEntry.type);
313
+ if (superName !== null && sealedNames.has(superName)) {
314
+ if (declName !== null) {
315
+ this.sealedSubclasses.get(superName).add(declName);
316
+ }
317
+ }
318
+ }
319
+ if (d.body)
320
+ registerSubclasses(d.body.members, declName ?? "");
321
+ }
322
+ }
323
+ };
324
+ collectNames(decls);
325
+ registerSubclasses(decls);
326
+ }
327
+ /** Extract the fully qualified name from a TypeRef. */
328
+ typeRefName(ref) {
329
+ if (ref.kind === "SimpleTypeRef")
330
+ return ref.name.join(".");
331
+ if (ref.kind === "GenericTypeRef")
332
+ return ref.base.name.join(".");
333
+ if (ref.kind === "NullableTypeRef")
334
+ return this.typeRefName(ref.base);
335
+ return null;
336
+ }
337
+ // ── Hoisting ───────────────────────────────────────────────────────────────
338
+ /** Returns the @Nuked reason string if the declaration is annotated, else undefined. */
339
+ nukedReason(mods) {
340
+ const ann = mods.annotations.find((a) => a.name === "Nuked");
341
+ if (!ann)
342
+ return undefined;
343
+ // Strip outer quotes if the arg is a simple string literal
344
+ if (ann.args)
345
+ return ann.args.replace(/^["']|["']$/g, "");
346
+ return "";
347
+ }
348
+ /** Returns `{ nuked: reason }` only if annotated, to satisfy exactOptionalPropertyTypes. */
349
+ nukedExtra(mods) {
350
+ const r = this.nukedReason(mods);
351
+ return r !== undefined ? { nuked: r } : {};
352
+ }
353
+ hoistDecl(decl) {
354
+ switch (decl.kind) {
355
+ case "FunDecl":
356
+ this.scope.define({
357
+ name: decl.name,
358
+ type: this.funDeclType(decl),
359
+ mutable: false,
360
+ span: decl.span,
361
+ ...this.nukedExtra(decl.modifiers),
362
+ });
363
+ break;
364
+ case "ComponentDecl":
365
+ this.scope.define({
366
+ name: decl.name,
367
+ type: { tag: "func", params: this.componentParamTypes(decl), ret: exports.T_UNIT, suspend: false },
368
+ mutable: false,
369
+ span: decl.span,
370
+ ...this.nukedExtra(decl.modifiers),
371
+ });
372
+ break;
373
+ case "ClassDecl":
374
+ case "DataClassDecl":
375
+ case "SealedClassDecl":
376
+ case "InterfaceDecl":
377
+ this.scope.define({
378
+ name: decl.name,
379
+ type: classType(decl.name, [], decl),
380
+ mutable: false,
381
+ span: decl.span,
382
+ ...this.nukedExtra(decl.modifiers),
383
+ });
384
+ break;
385
+ case "EnumClassDecl":
386
+ this.scope.define({
387
+ name: decl.name,
388
+ type: classType(decl.name, [], decl),
389
+ mutable: false,
390
+ span: decl.span,
391
+ ...this.nukedExtra(decl.modifiers),
392
+ });
393
+ break;
394
+ case "ObjectDecl":
395
+ if (decl.name) {
396
+ this.scope.define({
397
+ name: decl.name,
398
+ type: classType(decl.name),
399
+ mutable: false,
400
+ span: decl.span,
401
+ ...this.nukedExtra(decl.modifiers),
402
+ });
403
+ }
404
+ break;
405
+ case "PropertyDecl":
406
+ this.scope.define({
407
+ name: decl.name,
408
+ type: decl.type ? this.resolveTypeRef(decl.type) : exports.T_UNKNOWN,
409
+ mutable: decl.mutable,
410
+ span: decl.span,
411
+ ...this.nukedExtra(decl.modifiers),
412
+ });
413
+ break;
414
+ case "DestructuringDecl":
415
+ // Hoist each binding name into global scope with unknown type
416
+ for (const name of decl.names) {
417
+ if (name) {
418
+ this.scope.define({ name, type: exports.T_UNKNOWN, mutable: decl.mutable, span: decl.span });
419
+ }
420
+ }
421
+ break;
422
+ case "ExtensionFunDecl":
423
+ // Extension functions are module-level; no special hoisting needed beyond the name
424
+ break;
425
+ case "TypeAliasDecl":
426
+ break;
427
+ }
428
+ }
429
+ funDeclType(decl) {
430
+ const params = decl.params.map((p) => this.resolveTypeRef(p.type));
431
+ const paramNames = decl.params.map((p) => p.name);
432
+ const ret = decl.returnType ? this.resolveTypeRef(decl.returnType) : exports.T_UNIT;
433
+ const suspend = AST.isSuspend(decl.modifiers);
434
+ return { tag: "func", params, paramNames, ret, suspend };
435
+ }
436
+ componentParamTypes(decl) {
437
+ return decl.params.map((p) => this.resolveTypeRef(p.type));
438
+ }
439
+ // ── Top-level checking ─────────────────────────────────────────────────────
440
+ checkTopLevelDecl(decl) {
441
+ switch (decl.kind) {
442
+ case "FunDecl":
443
+ this.checkFunDecl(decl);
444
+ break;
445
+ case "ComponentDecl":
446
+ this.checkComponentDecl(decl);
447
+ break;
448
+ case "ClassDecl":
449
+ case "DataClassDecl":
450
+ case "SealedClassDecl":
451
+ this.checkClassLike(decl);
452
+ break;
453
+ case "EnumClassDecl":
454
+ if (decl.body)
455
+ this.checkClassLike({ ...decl, kind: "ClassDecl" });
456
+ break;
457
+ case "DestructuringDecl":
458
+ this.checkExpr(decl.initializer);
459
+ break;
460
+ case "PropertyDecl":
461
+ this.checkPropertyDecl(decl);
462
+ break;
463
+ case "ExtensionFunDecl":
464
+ this.checkExtensionFun(decl);
465
+ break;
466
+ default:
467
+ break;
468
+ }
469
+ }
470
+ // ── Function checking ──────────────────────────────────────────────────────
471
+ checkFunDecl(decl) {
472
+ const childScope = new Scope(this.scope);
473
+ for (const p of decl.params) {
474
+ childScope.define({ name: p.name, type: this.resolveTypeRef(p.type), mutable: false, span: p.span });
475
+ }
476
+ const prevSuspend = this.inSuspend;
477
+ this.inSuspend = AST.isSuspend(decl.modifiers);
478
+ this.withScope(childScope, () => {
479
+ if (decl.body) {
480
+ if (decl.body.kind === "Block") {
481
+ this.checkBlock(decl.body);
482
+ }
483
+ else {
484
+ this.checkExpr(decl.body);
485
+ }
486
+ }
487
+ });
488
+ this.inSuspend = prevSuspend;
489
+ }
490
+ checkComponentDecl(decl) {
491
+ const childScope = new Scope(this.scope);
492
+ for (const p of decl.params) {
493
+ childScope.define({ name: p.name, type: this.resolveTypeRef(p.type), mutable: false, span: p.span });
494
+ }
495
+ const prevComp = this.inComponent;
496
+ this.inComponent = true;
497
+ this.withScope(childScope, () => this.checkBlock(decl.body));
498
+ this.inComponent = prevComp;
499
+ }
500
+ checkExtensionFun(decl) {
501
+ const childScope = new Scope(this.scope);
502
+ const receiverType = this.resolveTypeRef(decl.receiver);
503
+ childScope.define({ name: "this", type: receiverType, mutable: false, span: decl.span });
504
+ for (const p of decl.params) {
505
+ childScope.define({ name: p.name, type: this.resolveTypeRef(p.type), mutable: false, span: p.span });
506
+ }
507
+ const prevSuspend = this.inSuspend;
508
+ this.inSuspend = AST.isSuspend(decl.modifiers);
509
+ this.withScope(childScope, () => {
510
+ if (decl.body.kind === "Block")
511
+ this.checkBlock(decl.body);
512
+ else
513
+ this.checkExpr(decl.body);
514
+ });
515
+ this.inSuspend = prevSuspend;
516
+ }
517
+ checkClassLike(decl) {
518
+ // Inheritance check: cannot extend a final class
519
+ for (const superEntry of decl.superTypes) {
520
+ const superType = this.resolveTypeRef(superEntry.type);
521
+ if (superType.tag === "class" && superType.decl) {
522
+ const isFinal = superType.decl.modifiers.modifiers.includes("final");
523
+ // In Jalvin, classes are final by default unless 'open', 'abstract', or 'sealed'
524
+ const isOpen = superType.decl.modifiers.modifiers.some(m => m === "open" || m === "abstract");
525
+ const isSealed = superType.decl.kind === "SealedClassDecl";
526
+ if (isFinal || (!isOpen && !isSealed)) {
527
+ this.diag.error(superEntry.span, diagnostics_js_1.E_CLASS_EXTENDS_FINAL, `Cannot inherit from final class '${superType.name}'`);
528
+ }
529
+ }
530
+ }
531
+ if (!decl.body)
532
+ return;
533
+ const seen = new Set();
534
+ // Collect all member names from parent class (for override/abstract checks)
535
+ const parentMemberNames = new Set();
536
+ const abstractMemberNames = new Set();
537
+ for (const superEntry of decl.superTypes) {
538
+ const superType = this.resolveTypeRef(superEntry.type);
539
+ if (superType.tag === "class" && superType.decl?.body) {
540
+ for (const m of superType.decl.body.members) {
541
+ const n = this.memberName(m);
542
+ if (n) {
543
+ parentMemberNames.add(n);
544
+ if ((m.kind === "FunDecl" || m.kind === "PropertyDecl") &&
545
+ m.modifiers.modifiers.includes("abstract")) {
546
+ abstractMemberNames.add(n);
547
+ }
548
+ }
549
+ }
550
+ }
551
+ }
552
+ const concreteMembers = new Set();
553
+ for (const member of decl.body.members) {
554
+ const mName = this.memberName(member);
555
+ if (mName) {
556
+ if (seen.has(mName)) {
557
+ this.diag.error(member.span, diagnostics_js_1.E_DUPLICATE_CLASS_MEMBER, `Duplicate member '${mName}'`);
558
+ }
559
+ seen.add(mName);
560
+ // Check `override` is actually overriding something
561
+ if ((member.kind === "FunDecl" || member.kind === "PropertyDecl") &&
562
+ member.modifiers.modifiers.includes("override")) {
563
+ if (!parentMemberNames.has(mName)) {
564
+ this.diag.error(member.span, diagnostics_js_1.E_OVERRIDE_NOTHING, `'${mName}' overrides nothing`);
565
+ }
566
+ }
567
+ if (!(member.kind === "FunDecl" && member.modifiers.modifiers.includes("abstract"))) {
568
+ concreteMembers.add(mName);
569
+ }
570
+ }
571
+ this.checkClassMember(member);
572
+ }
573
+ // Check all abstract parent members are implemented (only for non-abstract, non-sealed subclasses)
574
+ const isAbstract = decl.modifiers.modifiers.includes("abstract");
575
+ const isSealed = decl.kind === "SealedClassDecl";
576
+ if (!isAbstract && !isSealed) {
577
+ for (const abs of abstractMemberNames) {
578
+ if (!concreteMembers.has(abs)) {
579
+ this.diag.error(decl.span, diagnostics_js_1.E_ABSTRACT_MEMBER_NOT_IMPLEMENTED, `Class '${decl.name}' must implement abstract member '${abs}'`);
580
+ }
581
+ }
582
+ }
583
+ }
584
+ memberName(member) {
585
+ switch (member.kind) {
586
+ case "FunDecl":
587
+ case "ComponentDecl":
588
+ case "PropertyDecl":
589
+ return member.name;
590
+ default:
591
+ return null;
592
+ }
593
+ }
594
+ checkClassMember(member) {
595
+ switch (member.kind) {
596
+ case "FunDecl":
597
+ this.checkFunDecl(member);
598
+ break;
599
+ case "ComponentDecl":
600
+ this.checkComponentDecl(member);
601
+ break;
602
+ case "PropertyDecl":
603
+ this.checkPropertyDecl(member);
604
+ break;
605
+ case "ClassDecl":
606
+ case "DataClassDecl":
607
+ case "SealedClassDecl":
608
+ this.checkClassLike(member);
609
+ break;
610
+ default: break;
611
+ }
612
+ }
613
+ checkPropertyDecl(decl) {
614
+ // E0321: lateinit cannot be applied to val or primitive types
615
+ if (decl.modifiers.modifiers.includes("lateinit")) {
616
+ if (!decl.mutable) {
617
+ this.diag.error(decl.span, diagnostics_js_1.E_LATEINIT_INVALID, `'lateinit' can only be applied to 'var' properties`);
618
+ }
619
+ else if (decl.type) {
620
+ const t = this.resolveTypeRef(decl.type);
621
+ const isPrimitive = t.tag === "int" || t.tag === "long" || t.tag === "float" ||
622
+ t.tag === "double" || t.tag === "boolean" || t.tag === "char";
623
+ if (isPrimitive) {
624
+ this.diag.error(decl.span, diagnostics_js_1.E_LATEINIT_INVALID, `'lateinit' cannot be applied to primitive type '${this.typeToString(t)}'`);
625
+ }
626
+ }
627
+ }
628
+ if (decl.initializer) {
629
+ const initType = this.checkExpr(decl.initializer);
630
+ if (decl.type) {
631
+ const declaredType = this.resolveTypeRef(decl.type);
632
+ this.assertAssignable(decl.initializer.span, initType, declaredType);
633
+ }
634
+ this.typeMap.set(decl, initType);
635
+ }
636
+ if (decl.delegate) {
637
+ this.checkExpr(decl.delegate);
638
+ }
639
+ }
640
+ // ── Block & statement checking ─────────────────────────────────────────────
641
+ checkBlock(block) {
642
+ const blockScope = new Scope(this.scope);
643
+ this.withScope(blockScope, () => {
644
+ let terminated = false;
645
+ for (const stmt of block.statements) {
646
+ if (terminated) {
647
+ this.diag.warning(stmt.span, diagnostics_js_1.W_UNREACHABLE_CODE, "Unreachable code");
648
+ break;
649
+ }
650
+ this.checkStmt(stmt);
651
+ if (stmt.kind === "ReturnStmt" ||
652
+ stmt.kind === "ThrowStmt" ||
653
+ stmt.kind === "BreakStmt" ||
654
+ stmt.kind === "ContinueStmt") {
655
+ terminated = true;
656
+ }
657
+ }
658
+ // W0001: warn on unused local variables (skip `_`-prefixed)
659
+ for (const sym of blockScope.localUnused()) {
660
+ if (!sym.name.startsWith("_")) {
661
+ this.diag.warning(sym.span, diagnostics_js_1.W_UNUSED_VARIABLE, `Variable '${sym.name}' is never used`);
662
+ }
663
+ }
664
+ });
665
+ }
666
+ checkStmt(stmt) {
667
+ switch (stmt.kind) {
668
+ case "Block":
669
+ this.checkBlock(stmt);
670
+ break;
671
+ case "PropertyDecl": {
672
+ const t = stmt.initializer ? this.checkExpr(stmt.initializer) : exports.T_UNKNOWN;
673
+ const declared = stmt.type ? this.resolveTypeRef(stmt.type) : t;
674
+ // W0003: implicit Any when no type annotation and type cannot be inferred
675
+ if (!stmt.type && (declared.tag === "unknown" || declared.tag === "any")) {
676
+ this.diag.warning(stmt.span, diagnostics_js_1.W_IMPLICIT_ANY, `Variable '${stmt.name}' has implicit 'Any' type — add a type annotation`);
677
+ }
678
+ const result = this.scope.define({
679
+ name: stmt.name,
680
+ type: declared,
681
+ mutable: stmt.mutable,
682
+ span: stmt.span,
683
+ });
684
+ if (result !== true) {
685
+ // Shadowing is allowed in Jalvin
686
+ }
687
+ break;
688
+ }
689
+ case "DestructuringDecl": {
690
+ this.checkExpr(stmt.initializer);
691
+ for (const name of stmt.names) {
692
+ if (name) {
693
+ this.scope.define({ name, type: exports.T_UNKNOWN, mutable: stmt.mutable, span: stmt.span });
694
+ }
695
+ }
696
+ break;
697
+ }
698
+ case "ExprStmt":
699
+ this.checkExpr(stmt.expr);
700
+ break;
701
+ case "ReturnStmt":
702
+ if (stmt.value)
703
+ this.checkExpr(stmt.value);
704
+ break;
705
+ case "ThrowStmt":
706
+ this.checkExpr(stmt.value);
707
+ break;
708
+ case "BreakStmt":
709
+ case "ContinueStmt": break;
710
+ case "IfStmt":
711
+ this.checkIfStmt(stmt);
712
+ break;
713
+ case "WhenStmt":
714
+ this.checkWhenStmt(stmt);
715
+ break;
716
+ case "ForStmt":
717
+ this.checkForStmt(stmt);
718
+ break;
719
+ case "WhileStmt":
720
+ this.checkWhileStmt(stmt);
721
+ break;
722
+ case "DoWhileStmt": {
723
+ this.checkBlock(stmt.body);
724
+ this.checkExpr(stmt.condition);
725
+ break;
726
+ }
727
+ case "TryCatchStmt":
728
+ this.checkTryCatch(stmt);
729
+ break;
730
+ case "LabeledStmt":
731
+ this.checkStmt(stmt.body);
732
+ break;
733
+ }
734
+ }
735
+ checkIfStmt(stmt) {
736
+ const condType = this.checkExpr(stmt.condition);
737
+ this.assertAssignable(stmt.condition.span, condType, exports.T_BOOL);
738
+ // Smart cast: if (x is T) { ... } narrows x to T inside the then branch
739
+ const narrowings = this.extractSmartCasts(stmt.condition);
740
+ const thenScope = new Scope(this.scope);
741
+ for (const [name, type] of narrowings) {
742
+ const existing = this.scope.lookup(name);
743
+ if (existing) {
744
+ thenScope.define({ ...existing, type });
745
+ }
746
+ }
747
+ this.withScope(thenScope, () => this.checkBlock(stmt.then));
748
+ if (stmt.else) {
749
+ const elseNarrowings = this.extractElseSmartCasts(stmt.condition);
750
+ if (elseNarrowings.size > 0) {
751
+ const elseScope = new Scope(this.scope);
752
+ for (const [name, type] of elseNarrowings) {
753
+ const existing = this.scope.lookup(name);
754
+ if (existing)
755
+ elseScope.define({ ...existing, type });
756
+ }
757
+ this.withScope(elseScope, () => {
758
+ if (stmt.else.kind === "Block")
759
+ this.checkBlock(stmt.else);
760
+ else if (stmt.else.kind === "IfStmt")
761
+ this.checkIfStmt(stmt.else);
762
+ });
763
+ }
764
+ else {
765
+ if (stmt.else.kind === "Block")
766
+ this.checkBlock(stmt.else);
767
+ else if (stmt.else.kind === "IfStmt")
768
+ this.checkIfStmt(stmt.else);
769
+ }
770
+ }
771
+ }
772
+ /**
773
+ * Extract smart-cast bindings from an `is`-check condition.
774
+ * `x is T` → { x: T }
775
+ * `x is T && y is U` → { x: T, y: U }
776
+ */
777
+ extractSmartCasts(condition) {
778
+ const result = new Map();
779
+ if (condition.kind === "TypeCheckExpr" && !condition.negated) {
780
+ if (condition.expr.kind === "NameExpr") {
781
+ result.set(condition.expr.name, this.resolveTypeRef(condition.type));
782
+ }
783
+ }
784
+ else if (condition.kind === "BinaryExpr" && condition.op === "&&") {
785
+ for (const [k, v] of this.extractSmartCasts(condition.left))
786
+ result.set(k, v);
787
+ for (const [k, v] of this.extractSmartCasts(condition.right))
788
+ result.set(k, v);
789
+ }
790
+ return result;
791
+ }
792
+ /**
793
+ * Extract smart-cast bindings for the ELSE branch.
794
+ * `x !is T` → in else, `x` IS `T` → { x: T }
795
+ * `x !is T || y !is U` → in else (both negations must fail), { x: T, y: U }
796
+ */
797
+ extractElseSmartCasts(condition) {
798
+ const result = new Map();
799
+ if (condition.kind === "TypeCheckExpr" && condition.negated) {
800
+ // x !is T → entering else means the !is test failed, so x IS T
801
+ if (condition.expr.kind === "NameExpr") {
802
+ result.set(condition.expr.name, this.resolveTypeRef(condition.type));
803
+ }
804
+ }
805
+ else if (condition.kind === "BinaryExpr" && condition.op === "||") {
806
+ // x !is T || y !is U → both must be false to reach else, so both narrow
807
+ for (const [k, v] of this.extractElseSmartCasts(condition.left))
808
+ result.set(k, v);
809
+ for (const [k, v] of this.extractElseSmartCasts(condition.right))
810
+ result.set(k, v);
811
+ }
812
+ return result;
813
+ }
814
+ checkWhenStmt(stmt) {
815
+ const subjectType = stmt.subject ? this.checkExpr(stmt.subject.expr) : null;
816
+ // Bind `when (val x = expr)` into each branch scope
817
+ for (const branch of stmt.branches) {
818
+ const branchScope = stmt.subject?.binding
819
+ ? new Scope(this.scope)
820
+ : this.scope;
821
+ if (stmt.subject?.binding && subjectType) {
822
+ branchScope.define({
823
+ name: stmt.subject.binding,
824
+ type: subjectType,
825
+ mutable: false,
826
+ span: stmt.subject.span,
827
+ });
828
+ }
829
+ this.withScope(branchScope, () => {
830
+ // Smart cast narrowing: for single `is Type` condition, narrow subject type
831
+ if (stmt.subject?.binding &&
832
+ subjectType &&
833
+ branch.conditions.length === 1 &&
834
+ branch.conditions[0].kind === "WhenIsCondition" &&
835
+ !branch.conditions[0].negated) {
836
+ const isC = branch.conditions[0];
837
+ const narrowedType = this.resolveTypeRef(isC.type);
838
+ const narrowScope = new Scope(branchScope);
839
+ narrowScope.define({
840
+ name: stmt.subject.binding,
841
+ type: narrowedType,
842
+ mutable: false,
843
+ span: isC.span,
844
+ });
845
+ this.withScope(narrowScope, () => {
846
+ if (branch.body.kind === "Block")
847
+ this.checkBlock(branch.body);
848
+ else
849
+ this.checkExpr(branch.body);
850
+ });
851
+ }
852
+ else {
853
+ for (const cond of branch.conditions) {
854
+ if (cond.kind === "WhenExprCondition")
855
+ this.checkExpr(cond.expr);
856
+ else if (cond.kind === "WhenInCondition")
857
+ this.checkExpr(cond.expr);
858
+ }
859
+ if (branch.body.kind === "Block")
860
+ this.checkBlock(branch.body);
861
+ else
862
+ this.checkExpr(branch.body);
863
+ }
864
+ });
865
+ }
866
+ // Exhaustiveness check for sealed classes
867
+ if (subjectType) {
868
+ this.checkWhenExhaustiveness(stmt.span, stmt.branches, subjectType);
869
+ }
870
+ }
871
+ /**
872
+ * If `subjectType` is a sealed class or enum, verify all variants are
873
+ * covered by type/equality branches, or there is an `else` branch.
874
+ * Emits E_WHEN_NOT_EXHAUSTIVE for any missing variant.
875
+ */
876
+ checkWhenExhaustiveness(span, branches, subjectType) {
877
+ if (subjectType.tag !== "class")
878
+ return;
879
+ const typeName = subjectType.name;
880
+ // If there's an else branch, it's always exhaustive
881
+ if (branches.some((b) => b.isElse))
882
+ return;
883
+ // ── Sealed class exhaustiveness ────────────────────────────────────────
884
+ const subclasses = this.sealedSubclasses.get(typeName);
885
+ if (subclasses) {
886
+ const covered = new Set();
887
+ for (const branch of branches) {
888
+ for (const cond of branch.conditions) {
889
+ if (cond.kind === "WhenIsCondition" && !cond.negated) {
890
+ const name = this.typeRefName(cond.type);
891
+ // We want to match fully qualified name OR just the base name if it matches
892
+ if (name) {
893
+ covered.add(name);
894
+ // Also add the simple name part to be safe
895
+ const parts = name.split(".");
896
+ covered.add(parts[parts.length - 1]);
897
+ }
898
+ }
899
+ }
900
+ }
901
+ for (const sub of subclasses) {
902
+ const subParts = sub.split(".");
903
+ const subSimple = subParts[subParts.length - 1];
904
+ if (!covered.has(sub) && !covered.has(subSimple)) {
905
+ this.diag.error(span, diagnostics_js_1.E_WHEN_NOT_EXHAUSTIVE, `Non-exhaustive 'when' on sealed class '${typeName}': missing branch for '${sub}'`);
906
+ }
907
+ }
908
+ return;
909
+ }
910
+ // ── Enum class exhaustiveness ──────────────────────────────────────────
911
+ const entries = this.enumEntries.get(typeName);
912
+ if (entries) {
913
+ const covered = new Set();
914
+ for (const branch of branches) {
915
+ for (const cond of branch.conditions) {
916
+ if (cond.kind === "WhenExprCondition") {
917
+ const expr = cond.expr;
918
+ // Match `EnumName.ENTRY` or bare `ENTRY`
919
+ if (expr.kind === "MemberExpr") {
920
+ covered.add(expr.member);
921
+ }
922
+ else if (expr.kind === "NameExpr") {
923
+ covered.add(expr.name);
924
+ }
925
+ }
926
+ }
927
+ }
928
+ for (const entry of entries) {
929
+ if (!covered.has(entry)) {
930
+ this.diag.error(span, diagnostics_js_1.E_WHEN_NOT_EXHAUSTIVE, `Non-exhaustive 'when' on enum '${typeName}': missing branch for '${entry}'`);
931
+ }
932
+ }
933
+ }
934
+ }
935
+ checkForStmt(stmt) {
936
+ const iterType = this.checkExpr(stmt.iterable);
937
+ const elemType = this.elementTypeOf(iterType);
938
+ const childScope = new Scope(this.scope);
939
+ if (typeof stmt.binding === "string") {
940
+ childScope.define({ name: stmt.binding, type: elemType, mutable: false, span: stmt.span });
941
+ }
942
+ this.withScope(childScope, () => this.checkBlock(stmt.body));
943
+ }
944
+ checkWhileStmt(stmt) {
945
+ this.checkExpr(stmt.condition);
946
+ this.checkBlock(stmt.body);
947
+ }
948
+ checkTryCatch(stmt) {
949
+ this.checkBlock(stmt.body);
950
+ for (const c of stmt.catches) {
951
+ const childScope = new Scope(this.scope);
952
+ childScope.define({ name: c.name, type: this.resolveTypeRef(c.type), mutable: false, span: c.span });
953
+ this.withScope(childScope, () => this.checkBlock(c.body));
954
+ }
955
+ if (stmt.finally)
956
+ this.checkBlock(stmt.finally);
957
+ }
958
+ // ── Expression type inference ──────────────────────────────────────────────
959
+ checkExpr(expr) {
960
+ const type = this.inferExpr(expr);
961
+ this.typeMap.set(expr, type);
962
+ return type;
963
+ }
964
+ inferExpr(expr) {
965
+ switch (expr.kind) {
966
+ case "IntLiteralExpr": return exports.T_INT;
967
+ case "LongLiteralExpr": return exports.T_LONG;
968
+ case "FloatLiteralExpr": return exports.T_FLOAT;
969
+ case "DoubleLiteralExpr": return exports.T_DOUBLE;
970
+ case "BooleanLiteralExpr": return exports.T_BOOL;
971
+ case "NullLiteralExpr": return nullable(exports.T_NOTHING);
972
+ case "StringLiteralExpr":
973
+ case "StringTemplateExpr": {
974
+ if (expr.kind === "StringTemplateExpr") {
975
+ for (const p of expr.parts) {
976
+ if (p.kind === "ExprPart")
977
+ this.checkExpr(p.expr);
978
+ }
979
+ }
980
+ return exports.T_STRING;
981
+ }
982
+ case "NameExpr": {
983
+ const sym = this.scope.lookup(expr.name);
984
+ if (!sym) {
985
+ // Don't error on Bibi — it's a special runtime symbol
986
+ if (expr.name === "Bibi")
987
+ return { tag: "func", params: [exports.T_STRING], ret: classType("BibiClient"), suspend: false };
988
+ // Companion-like type objects (for Int.MAX_VALUE, Long.MIN_VALUE)
989
+ if (expr.name === "Int")
990
+ return classType("IntCompanion");
991
+ if (expr.name === "Long")
992
+ return classType("LongCompanion");
993
+ if (expr.name === "Double" || expr.name === "Float")
994
+ return classType("NumberCompanion");
995
+ // JS builtins — allow through without error (emits as-is to TypeScript)
996
+ if (JS_GLOBALS.has(expr.name))
997
+ return exports.T_UNKNOWN;
998
+ this.diag.error(expr.span, diagnostics_js_1.E_UNDEFINED_SYMBOL, `Unresolved reference: '${expr.name}'`);
999
+ return exports.T_ERROR;
1000
+ }
1001
+ this.scope.markUsed(expr.name);
1002
+ if (sym.nuked !== undefined) {
1003
+ const reason = sym.nuked ? `: ${sym.nuked}` : "";
1004
+ this.diag.warning(expr.span, diagnostics_js_1.W_DEPRECATED, `'${expr.name}' is @Nuked${reason}`);
1005
+ }
1006
+ return sym.type;
1007
+ }
1008
+ case "ThisExpr": {
1009
+ const sym = this.scope.lookup("this");
1010
+ return sym?.type ?? exports.T_ANY;
1011
+ }
1012
+ case "SuperExpr": return exports.T_ANY;
1013
+ case "ParenExpr": return this.checkExpr(expr.expr);
1014
+ case "UnaryExpr": return this.checkUnary(expr);
1015
+ case "BinaryExpr": return this.checkBinary(expr);
1016
+ case "AssignExpr": {
1017
+ // E0320: cannot assign to const val
1018
+ if (expr.target.kind === "NameExpr") {
1019
+ const sym = this.scope.lookup(expr.target.name);
1020
+ if (sym && !sym.mutable) {
1021
+ this.diag.error(expr.span, diagnostics_js_1.E_CONST_VAL_REASSIGNMENT, `Cannot assign to 'val' '${expr.target.name}'`);
1022
+ }
1023
+ }
1024
+ this.checkExpr(expr.value);
1025
+ this.checkExpr(expr.target);
1026
+ return exports.T_UNIT;
1027
+ }
1028
+ case "CompoundAssignExpr": {
1029
+ this.checkExpr(expr.value);
1030
+ this.checkExpr(expr.target);
1031
+ return exports.T_UNIT;
1032
+ }
1033
+ case "IncrDecrExpr": {
1034
+ this.checkExpr(expr.target);
1035
+ return exports.T_UNIT;
1036
+ }
1037
+ case "MemberExpr": {
1038
+ const targetType = this.checkExpr(expr.target);
1039
+ return this.memberType(expr.span, targetType, expr.member, false);
1040
+ }
1041
+ case "SafeMemberExpr": {
1042
+ const targetType = this.checkExpr(expr.target);
1043
+ if (!isNullable(targetType) && targetType.tag !== "any" && targetType.tag !== "unknown") {
1044
+ this.diag.warning(expr.span, diagnostics_js_1.E_NOT_NULLABLE, `Safe call (?.) on non-nullable type '${this.typeToString(targetType)}'`);
1045
+ }
1046
+ const inner = unwrapNullable(targetType);
1047
+ return nullable(this.memberType(expr.span, inner, expr.member, true));
1048
+ }
1049
+ case "IndexExpr": {
1050
+ const targetType = this.checkExpr(expr.target);
1051
+ this.checkExpr(expr.index);
1052
+ // Resolve `operator fun get(index)` on user class types
1053
+ const getRetType = this.resolveGetOperator(targetType);
1054
+ if (getRetType !== null)
1055
+ return getRetType;
1056
+ return exports.T_UNKNOWN;
1057
+ }
1058
+ case "CallExpr": return this.checkCallExpr(expr);
1059
+ case "LambdaExpr": return this.checkLambda(expr);
1060
+ case "IfExpr": return this.checkIfExpr(expr);
1061
+ case "WhenExpr": {
1062
+ const subjectType = expr.subject ? this.checkExpr(expr.subject.expr) : null;
1063
+ const branchTypes = [];
1064
+ for (const b of expr.branches) {
1065
+ for (const c of b.conditions) {
1066
+ if (c.kind === "WhenExprCondition")
1067
+ this.checkExpr(c.expr);
1068
+ else if (c.kind === "WhenInCondition")
1069
+ this.checkExpr(c.expr);
1070
+ }
1071
+ branchTypes.push(b.body.kind === "Block" ? (this.checkBlock(b.body), exports.T_UNIT) : this.checkExpr(b.body));
1072
+ }
1073
+ // Exhaustiveness: when-expr on a sealed class MUST be exhaustive
1074
+ if (subjectType) {
1075
+ this.checkWhenExhaustiveness(expr.span, expr.branches, subjectType);
1076
+ }
1077
+ return this.unify(branchTypes);
1078
+ }
1079
+ case "TryCatchExpr": {
1080
+ this.checkBlock(expr.body);
1081
+ for (const c of expr.catches)
1082
+ this.checkBlock(c.body);
1083
+ if (expr.finally)
1084
+ this.checkBlock(expr.finally);
1085
+ return exports.T_UNKNOWN;
1086
+ }
1087
+ case "TypeCheckExpr": {
1088
+ this.checkExpr(expr.expr);
1089
+ return exports.T_BOOL;
1090
+ }
1091
+ case "TypeCastExpr": {
1092
+ this.checkExpr(expr.expr);
1093
+ return this.resolveTypeRef(expr.type);
1094
+ }
1095
+ case "SafeCastExpr": {
1096
+ this.checkExpr(expr.expr);
1097
+ return nullable(this.resolveTypeRef(expr.type));
1098
+ }
1099
+ case "NotNullExpr": {
1100
+ const inner = this.checkExpr(expr.expr);
1101
+ if (!isNullable(inner) && inner.tag !== "any" && inner.tag !== "unknown") {
1102
+ this.diag.warning(expr.span, diagnostics_js_1.E_NOT_NULLABLE, `Non-null assertion (!!) on non-nullable type '${this.typeToString(inner)}'`);
1103
+ }
1104
+ return unwrapNullable(inner);
1105
+ }
1106
+ case "ElvisExpr": {
1107
+ const left = this.checkExpr(expr.left);
1108
+ const right = this.checkExpr(expr.right);
1109
+ // Result type: unwrapped left | right
1110
+ return this.unify([unwrapNullable(left), right]);
1111
+ }
1112
+ case "RangeExpr": {
1113
+ this.checkExpr(expr.from);
1114
+ this.checkExpr(expr.to);
1115
+ return classType("IntRange");
1116
+ }
1117
+ case "LaunchExpr": {
1118
+ if (!this.inSuspend && !this.inComponent) {
1119
+ // launch is valid at top-level coroutine scope
1120
+ }
1121
+ const prevSuspend = this.inSuspend;
1122
+ this.inSuspend = true;
1123
+ this.checkBlock(expr.body);
1124
+ this.inSuspend = prevSuspend;
1125
+ return classType("Job");
1126
+ }
1127
+ case "AsyncExpr": {
1128
+ const prevSuspend = this.inSuspend;
1129
+ this.inSuspend = true;
1130
+ this.checkBlock(expr.body);
1131
+ this.inSuspend = prevSuspend;
1132
+ return classType("Deferred", [exports.T_UNKNOWN]);
1133
+ }
1134
+ case "CollectionLiteralExpr": {
1135
+ const elemTypes = expr.elements.map((e) => {
1136
+ if ("kind" in e && e.kind === "MapEntry") {
1137
+ this.checkExpr(e.key);
1138
+ this.checkExpr(e.value);
1139
+ return exports.T_UNKNOWN;
1140
+ }
1141
+ return this.checkExpr(e);
1142
+ });
1143
+ if (expr.collectionKind === "list")
1144
+ return classType("List", [this.unify(elemTypes)]);
1145
+ if (expr.collectionKind === "set")
1146
+ return classType("Set", [this.unify(elemTypes)]);
1147
+ return classType("Map", [exports.T_UNKNOWN, exports.T_UNKNOWN]);
1148
+ }
1149
+ case "ObjectExpr": return classType("<anonymous>");
1150
+ case "ReturnExpr": {
1151
+ if (expr.value)
1152
+ this.checkExpr(expr.value);
1153
+ return exports.T_NOTHING;
1154
+ }
1155
+ case "BreakExpr":
1156
+ case "ContinueExpr":
1157
+ return exports.T_NOTHING;
1158
+ default:
1159
+ return exports.T_UNKNOWN;
1160
+ }
1161
+ }
1162
+ checkUnary(expr) {
1163
+ const t = this.checkExpr(expr.operand);
1164
+ if (expr.op === "!") {
1165
+ this.assertAssignable(expr.span, t, exports.T_BOOL);
1166
+ return exports.T_BOOL;
1167
+ }
1168
+ return t;
1169
+ }
1170
+ checkBinary(expr) {
1171
+ const l = this.checkExpr(expr.left);
1172
+ const r = this.checkExpr(expr.right);
1173
+ // `in` / `!in` → resolve `contains` on the **right-hand** type
1174
+ if (expr.op === "in" || expr.op === "!in") {
1175
+ const containsOverload = this.resolveOperatorOverload(r, expr.op);
1176
+ if (containsOverload) {
1177
+ // Store with a sentinel so codegen knows this is an `in` overload
1178
+ this.operatorOverloadMap.set(expr, expr.op === "in" ? "contains" : "!contains");
1179
+ }
1180
+ return exports.T_BOOL;
1181
+ }
1182
+ // Check for operator overload on the left-hand class type
1183
+ const overload = this.resolveOperatorOverload(l, expr.op);
1184
+ if (overload) {
1185
+ this.operatorOverloadMap.set(expr, overload.method);
1186
+ return overload.retType;
1187
+ }
1188
+ switch (expr.op) {
1189
+ case "==":
1190
+ case "!=":
1191
+ case "===":
1192
+ case "!==": return exports.T_BOOL;
1193
+ case "<":
1194
+ case ">":
1195
+ case "<=":
1196
+ case ">=": return exports.T_BOOL;
1197
+ case "&&":
1198
+ case "||": return exports.T_BOOL;
1199
+ case "+":
1200
+ if (l.tag === "string" || r.tag === "string")
1201
+ return exports.T_STRING;
1202
+ return this.numericType(l, r);
1203
+ case "-":
1204
+ case "*":
1205
+ case "/":
1206
+ case "%":
1207
+ return this.numericType(l, r);
1208
+ default:
1209
+ return exports.T_UNKNOWN;
1210
+ }
1211
+ }
1212
+ /**
1213
+ * If the left operand type is a class that declares an `operator fun` matching
1214
+ * the given operator, return the method name and return type.
1215
+ *
1216
+ * Operator → method name mapping:
1217
+ * + → plus - → minus * → times / → div % → rem
1218
+ * == → equals < → compareTo (returns Int, we normalise to Boolean)
1219
+ * .. → rangeTo [] → get (handled elsewhere)
1220
+ * += → plusAssign, etc.
1221
+ */
1222
+ resolveOperatorOverload(lType, op) {
1223
+ if (lType.tag !== "class")
1224
+ return null;
1225
+ const decl = lType.decl;
1226
+ if (!decl || !decl.body)
1227
+ return null;
1228
+ const opToMethod = {
1229
+ "+": "plus",
1230
+ "-": "minus",
1231
+ "*": "times",
1232
+ "/": "div",
1233
+ "%": "rem",
1234
+ "..": "rangeTo",
1235
+ "..<": "rangeUntil",
1236
+ "<": "compareTo",
1237
+ ">": "compareTo",
1238
+ "<=": "compareTo",
1239
+ ">=": "compareTo",
1240
+ "in": "contains",
1241
+ "!in": "contains",
1242
+ };
1243
+ const methodName = opToMethod[op];
1244
+ if (!methodName)
1245
+ return null;
1246
+ for (const member of decl.body.members) {
1247
+ if (member.kind === "FunDecl" &&
1248
+ member.name === methodName &&
1249
+ member.modifiers.modifiers.includes("operator")) {
1250
+ // compareTo always resolves to Boolean (we wrap the Int result)
1251
+ const rawRet = member.returnType
1252
+ ? this.resolveTypeRef(member.returnType)
1253
+ : exports.T_UNKNOWN;
1254
+ const retType = (methodName === "compareTo" || methodName === "contains") ? exports.T_BOOL : rawRet;
1255
+ return { method: methodName, retType };
1256
+ }
1257
+ }
1258
+ return null;
1259
+ }
1260
+ /**
1261
+ * Resolve `operator fun get(index)` on a class type (for index expressions).
1262
+ */
1263
+ resolveGetOperator(targetType) {
1264
+ if (targetType.tag !== "class")
1265
+ return null;
1266
+ const decl = targetType.decl;
1267
+ if (!decl || !decl.body)
1268
+ return null;
1269
+ for (const member of decl.body.members) {
1270
+ if (member.kind === "FunDecl" &&
1271
+ member.name === "get" &&
1272
+ member.modifiers.modifiers.includes("operator")) {
1273
+ return member.returnType ? this.resolveTypeRef(member.returnType) : exports.T_UNKNOWN;
1274
+ }
1275
+ }
1276
+ return null;
1277
+ }
1278
+ checkCallExpr(expr) {
1279
+ const calleeType = this.checkExpr(expr.callee);
1280
+ for (const arg of expr.args)
1281
+ this.checkExpr(arg.value);
1282
+ // If callee is a member expr, check for @Nuked on the resolved method
1283
+ if (expr.callee.kind === "MemberExpr" || expr.callee.kind === "SafeMemberExpr") {
1284
+ // This is handled inside memberType helper
1285
+ }
1286
+ // Propagate expected lambda param types for `it` implicit parameter support
1287
+ if (expr.trailingLambda) {
1288
+ let expectedLambdaParams;
1289
+ if (calleeType.tag === "func" && calleeType.params.length > 0) {
1290
+ const lastParam = calleeType.params[calleeType.params.length - 1];
1291
+ if (lastParam.tag === "func") {
1292
+ expectedLambdaParams = lastParam.params;
1293
+ }
1294
+ }
1295
+ this.checkLambda(expr.trailingLambda, expectedLambdaParams);
1296
+ }
1297
+ if (calleeType.tag === "func") {
1298
+ if (!this.inSuspend && calleeType.suspend) {
1299
+ this.diag.error(expr.span, diagnostics_js_1.E_SUSPEND_IN_NON_SUSPEND, "Suspend function called outside of coroutine or suspend context");
1300
+ }
1301
+ return calleeType.ret;
1302
+ }
1303
+ if (calleeType.tag === "class") {
1304
+ // Check for `operator fun invoke(...)` on a class instance
1305
+ if (calleeType.decl && calleeType.decl.body) {
1306
+ for (const member of calleeType.decl.body.members) {
1307
+ if (member.kind === "FunDecl" &&
1308
+ member.name === "invoke" &&
1309
+ member.modifiers.modifiers.includes("operator")) {
1310
+ return member.returnType ? this.resolveTypeRef(member.returnType) : exports.T_UNIT;
1311
+ }
1312
+ }
1313
+ }
1314
+ // Regular constructor call
1315
+ return calleeType;
1316
+ }
1317
+ if (calleeType.tag === "error" || calleeType.tag === "unknown" || calleeType.tag === "any") {
1318
+ return exports.T_UNKNOWN;
1319
+ }
1320
+ this.diag.error(expr.span, diagnostics_js_1.E_NOT_A_FUNCTION, `Type '${this.typeToString(calleeType)}' is not callable`);
1321
+ return exports.T_ERROR;
1322
+ }
1323
+ checkLambda(expr, expectedParamTypes) {
1324
+ const childScope = new Scope(this.scope);
1325
+ // If the lambda has no explicit params, synthesise an `it` binding using
1326
+ // the first expected parameter type.
1327
+ if (expr.params.length === 0 && expectedParamTypes && expectedParamTypes.length === 1) {
1328
+ childScope.define({
1329
+ name: "it",
1330
+ type: expectedParamTypes[0],
1331
+ mutable: false,
1332
+ span: expr.span,
1333
+ });
1334
+ }
1335
+ for (const p of expr.params) {
1336
+ if (p.name) {
1337
+ childScope.define({
1338
+ name: p.name,
1339
+ type: p.type ? this.resolveTypeRef(p.type) : exports.T_UNKNOWN,
1340
+ mutable: false,
1341
+ span: p.span,
1342
+ });
1343
+ }
1344
+ }
1345
+ let retType = exports.T_UNIT;
1346
+ this.withScope(childScope, () => {
1347
+ for (const stmt of expr.body) {
1348
+ if (stmt.kind === "ExprStmt")
1349
+ retType = this.checkExpr(stmt.expr);
1350
+ else
1351
+ this.checkStmt(stmt);
1352
+ }
1353
+ });
1354
+ const paramTypes = expr.params.length > 0
1355
+ ? expr.params.map((p) => (p.type ? this.resolveTypeRef(p.type) : exports.T_UNKNOWN))
1356
+ : (expectedParamTypes ?? [exports.T_UNKNOWN]);
1357
+ return { tag: "func", params: paramTypes, ret: retType, suspend: false };
1358
+ }
1359
+ checkIfExpr(expr) {
1360
+ this.checkExpr(expr.condition);
1361
+ // Apply smart casts in the `then` branch
1362
+ const narrowings = this.extractSmartCasts(expr.condition);
1363
+ const thenScope = new Scope(this.scope);
1364
+ for (const [name, type] of narrowings) {
1365
+ const existing = this.scope.lookup(name);
1366
+ if (existing)
1367
+ thenScope.define({ ...existing, type });
1368
+ }
1369
+ const thenType = this.withScope(thenScope, () => expr.then.kind === "Block"
1370
+ ? (this.checkBlock(expr.then), exports.T_UNIT)
1371
+ : this.checkExpr(expr.then));
1372
+ // Apply narrowings for the else branch (e.g. `x !is T` → else: x IS T)
1373
+ const elseNarrowings = this.extractElseSmartCasts(expr.condition);
1374
+ let elseType;
1375
+ if (elseNarrowings.size > 0) {
1376
+ const elseScope = new Scope(this.scope);
1377
+ for (const [name, type] of elseNarrowings) {
1378
+ const existing = this.scope.lookup(name);
1379
+ if (existing)
1380
+ elseScope.define({ ...existing, type });
1381
+ }
1382
+ elseType = this.withScope(elseScope, () => expr.else.kind === "Block"
1383
+ ? (this.checkBlock(expr.else), exports.T_UNIT)
1384
+ : expr.else.kind === "IfExpr"
1385
+ ? this.checkIfExpr(expr.else)
1386
+ : this.checkExpr(expr.else));
1387
+ }
1388
+ else {
1389
+ elseType = expr.else.kind === "Block"
1390
+ ? (this.checkBlock(expr.else), exports.T_UNIT)
1391
+ : expr.else.kind === "IfExpr"
1392
+ ? this.checkIfExpr(expr.else)
1393
+ : this.checkExpr(expr.else);
1394
+ }
1395
+ return this.unify([thenType, elseType]);
1396
+ }
1397
+ // ── Type resolution ────────────────────────────────────────────────────────
1398
+ lookupType(nameParts) {
1399
+ if (nameParts.length === 0)
1400
+ return null;
1401
+ const first = nameParts[0];
1402
+ const builtin = this.builtinType(first);
1403
+ if (builtin && nameParts.length === 1)
1404
+ return builtin;
1405
+ let sym = this.scope.lookup(first);
1406
+ if (!sym)
1407
+ return null;
1408
+ let currentType = sym.type;
1409
+ for (let i = 1; i < nameParts.length; i++) {
1410
+ const part = nameParts[i];
1411
+ if (currentType.tag === "class" && currentType.decl?.body) {
1412
+ // Look for nested class in body
1413
+ const nested = currentType.decl.body.members.find((m) => {
1414
+ const k = m.kind;
1415
+ return ((k === "ClassDecl" ||
1416
+ k === "DataClassDecl" ||
1417
+ k === "SealedClassDecl" ||
1418
+ k === "EnumClassDecl" ||
1419
+ k === "InterfaceDecl" ||
1420
+ k === "ObjectDecl") &&
1421
+ m.name === part);
1422
+ });
1423
+ if (nested) {
1424
+ currentType = classType(currentType.name + "." + part, [], nested);
1425
+ }
1426
+ else {
1427
+ return null;
1428
+ }
1429
+ }
1430
+ else {
1431
+ return null;
1432
+ }
1433
+ }
1434
+ return currentType;
1435
+ }
1436
+ resolveTypeRef(ref) {
1437
+ switch (ref.kind) {
1438
+ case "NullableTypeRef":
1439
+ return nullable(this.resolveTypeRef(ref.base));
1440
+ case "SimpleTypeRef": {
1441
+ return this.lookupType([...ref.name]) ?? classType(ref.name.join("."));
1442
+ }
1443
+ case "GenericTypeRef": {
1444
+ const base = this.lookupType([...ref.base.name]) ?? classType(ref.base.name.join("."));
1445
+ const args = ref.args.map((a) => a.star ? exports.T_ANY : a.type ? this.resolveTypeRef(a.type) : exports.T_UNKNOWN);
1446
+ if (base.tag === "class")
1447
+ return { ...base, typeArgs: args };
1448
+ return base;
1449
+ }
1450
+ case "FunctionTypeRef": {
1451
+ const params = ref.params.map((p) => this.resolveTypeRef(p));
1452
+ const ret = this.resolveTypeRef(ref.returnType);
1453
+ return { tag: "func", params, ret, suspend: false };
1454
+ }
1455
+ case "StarProjection":
1456
+ return exports.T_ANY;
1457
+ }
1458
+ }
1459
+ builtinType(name) {
1460
+ switch (name) {
1461
+ case "Int": return exports.T_INT;
1462
+ case "Long": return exports.T_LONG;
1463
+ case "Float": return exports.T_FLOAT;
1464
+ case "Double": return exports.T_DOUBLE;
1465
+ case "Boolean": return exports.T_BOOL;
1466
+ case "String": return exports.T_STRING;
1467
+ case "Char": return exports.T_CHAR;
1468
+ case "Unit": return exports.T_UNIT;
1469
+ case "Any": return exports.T_ANY;
1470
+ case "Nothing": return exports.T_NOTHING;
1471
+ default: return null;
1472
+ }
1473
+ }
1474
+ // ── Helpers ────────────────────────────────────────────────────────────────
1475
+ memberType(span, targetType, member, safe) {
1476
+ if (targetType.tag === "error" || targetType.tag === "unknown" || targetType.tag === "any") {
1477
+ return exports.T_UNKNOWN;
1478
+ }
1479
+ if (targetType.tag === "nullable" && !safe) {
1480
+ this.diag.error(span, diagnostics_js_1.E_UNSAFE_NULL_DEREFERENCE, `Unsafe member access on nullable type '${this.typeToString(targetType)}'. Use ?. instead.`);
1481
+ return exports.T_UNKNOWN;
1482
+ }
1483
+ // Known stdlib members
1484
+ if (targetType.tag === "string") {
1485
+ const str = exports.T_STRING;
1486
+ const bool = exports.T_BOOL;
1487
+ const int = exports.T_INT;
1488
+ const strList = classType("List", [str]);
1489
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1490
+ switch (member) {
1491
+ // properties
1492
+ case "length": return int;
1493
+ case "indices": return classType("IntRange");
1494
+ case "lastIndex": return int;
1495
+ // no-arg → String
1496
+ case "uppercase":
1497
+ case "lowercase":
1498
+ case "trim":
1499
+ case "trimStart":
1500
+ case "trimEnd":
1501
+ case "reversed":
1502
+ case "trimIndent":
1503
+ case "toUpperCase":
1504
+ case "toLowerCase": // Java aliases
1505
+ return f([], str);
1506
+ // no-arg → Boolean
1507
+ case "isEmpty":
1508
+ case "isNotEmpty":
1509
+ case "isBlank":
1510
+ case "isNotBlank":
1511
+ return f([], bool);
1512
+ // String → Boolean
1513
+ case "startsWith":
1514
+ case "endsWith":
1515
+ case "contains":
1516
+ return f([str], bool);
1517
+ // (String) → String
1518
+ case "removePrefix":
1519
+ case "removeSuffix":
1520
+ return f([str], str);
1521
+ // (String) → String
1522
+ case "substringBefore":
1523
+ case "substringAfter":
1524
+ case "substringBeforeLast":
1525
+ case "substringAfterLast":
1526
+ return f([str], str);
1527
+ // (Int, Int) → String
1528
+ case "substring": return f([int, int], str);
1529
+ // (String, String) → String
1530
+ case "replace": return f([str, str], str);
1531
+ // (String) → List<String>
1532
+ case "split": return f([str], strList);
1533
+ // → List<String>
1534
+ case "lines": return f([], strList);
1535
+ // (Int) → String
1536
+ case "repeat": return f([int], str);
1537
+ case "padStart":
1538
+ case "padEnd": return f([int, str], str);
1539
+ // conversions
1540
+ case "toInt": return f([], int);
1541
+ case "toIntOrNull": return f([], nullable(int));
1542
+ case "toDouble": return f([], exports.T_DOUBLE);
1543
+ case "toDoubleOrNull": return f([], nullable(exports.T_DOUBLE));
1544
+ case "toLong": return f([], exports.T_LONG);
1545
+ case "toFloat": return f([], exports.T_FLOAT);
1546
+ case "toBoolean": return f([], bool);
1547
+ case "toBooleanOrNull": return f([], nullable(bool));
1548
+ case "toCharArray": return f([], classType("CharArray"));
1549
+ // (Int) → Char
1550
+ case "get": return f([int], exports.T_CHAR);
1551
+ // (String) → Boolean
1552
+ case "matches": return f([classType("Regex")], bool);
1553
+ // () → String
1554
+ case "capitalize":
1555
+ case "decapitalize": return f([], str);
1556
+ // (Int) → String
1557
+ case "first":
1558
+ case "last": return f([], exports.T_CHAR);
1559
+ case "firstOrNull":
1560
+ case "lastOrNull": return f([], nullable(exports.T_CHAR));
1561
+ case "take":
1562
+ case "drop": return f([int], str);
1563
+ case "takeLast":
1564
+ case "dropLast": return f([int], str);
1565
+ // compareTo, ifEmpty, ifBlank
1566
+ case "compareTo": return f([str], int);
1567
+ case "ifEmpty":
1568
+ case "ifBlank": return f([{ tag: "func", params: [], ret: str, suspend: false }], str);
1569
+ default: return exports.T_UNKNOWN;
1570
+ }
1571
+ }
1572
+ // Numeric member types (Int, Long, Float, Double, Char)
1573
+ const isNumeric = targetType.tag === "int" || targetType.tag === "long" ||
1574
+ targetType.tag === "float" || targetType.tag === "double";
1575
+ if (isNumeric) {
1576
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1577
+ switch (member) {
1578
+ case "coerceAtLeast":
1579
+ case "coerceAtMost": return f([targetType], targetType);
1580
+ case "coerceIn": return f([targetType, targetType], targetType);
1581
+ case "toInt": return f([], exports.T_INT);
1582
+ case "toLong": return f([], exports.T_LONG);
1583
+ case "toFloat": return f([], exports.T_FLOAT);
1584
+ case "toDouble": return f([], exports.T_DOUBLE);
1585
+ case "toString": return f([], exports.T_STRING);
1586
+ case "compareTo": return f([targetType], exports.T_INT);
1587
+ case "plus":
1588
+ case "minus":
1589
+ case "times":
1590
+ case "div":
1591
+ case "rem":
1592
+ return f([targetType], targetType);
1593
+ case "unaryMinus":
1594
+ case "unaryPlus": return f([], targetType);
1595
+ case "inc":
1596
+ case "dec": return f([], targetType);
1597
+ case "downTo": return f([targetType], classType("IntRange"));
1598
+ case "until": return f([targetType], classType("IntRange"));
1599
+ case "step": return f([exports.T_INT], classType("IntRange"));
1600
+ default: return exports.T_UNKNOWN;
1601
+ }
1602
+ }
1603
+ // Collection/List member types
1604
+ if (targetType.tag === "class" &&
1605
+ (targetType.name === "List" || targetType.name === "MutableList" ||
1606
+ targetType.name === "Set" || targetType.name === "MutableSet")) {
1607
+ const elemType = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
1608
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1609
+ const predFn = { tag: "func", params: [elemType], ret: exports.T_BOOL, suspend: false };
1610
+ switch (member) {
1611
+ case "size": return exports.T_INT;
1612
+ case "isEmpty":
1613
+ case "isNotEmpty": return f([], exports.T_BOOL);
1614
+ case "contains": return f([elemType], exports.T_BOOL);
1615
+ case "containsAll": return f([classType("List", [elemType])], exports.T_BOOL);
1616
+ case "get": return f([exports.T_INT], elemType);
1617
+ case "first": return f([predFn], elemType);
1618
+ case "last": return f([predFn], elemType);
1619
+ case "firstOrNull": return f([predFn], nullable(elemType));
1620
+ case "lastOrNull": return f([predFn], nullable(elemType));
1621
+ case "find":
1622
+ case "findLast": return f([predFn], nullable(elemType));
1623
+ case "indexOf":
1624
+ case "lastIndexOf": return f([elemType], exports.T_INT);
1625
+ case "indexOfFirst":
1626
+ case "indexOfLast": return f([predFn], exports.T_INT);
1627
+ case "filter":
1628
+ case "filterNot": return f([predFn], classType("List", [elemType]));
1629
+ case "filterNotNull": return f([], classType("List", [exports.T_UNKNOWN]));
1630
+ case "filterIsInstance": return f([], classType("List", [exports.T_UNKNOWN]));
1631
+ case "map": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("List", [exports.T_UNKNOWN]), suspend: false };
1632
+ case "mapNotNull": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: nullable(exports.T_UNKNOWN), suspend: false }], ret: classType("List", [exports.T_UNKNOWN]), suspend: false };
1633
+ case "mapIndexed": return { tag: "func", params: [{ tag: "func", params: [exports.T_INT, elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("List", [exports.T_UNKNOWN]), suspend: false };
1634
+ case "forEach": return f([{ tag: "func", params: [elemType], ret: exports.T_UNIT, suspend: false }], exports.T_UNIT);
1635
+ case "forEachIndexed": return f([{ tag: "func", params: [exports.T_INT, elemType], ret: exports.T_UNIT, suspend: false }], exports.T_UNIT);
1636
+ case "any":
1637
+ case "all":
1638
+ case "none": return f([predFn], exports.T_BOOL);
1639
+ case "count": return f([predFn], exports.T_INT);
1640
+ case "sumOf": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_DOUBLE, suspend: false }], ret: exports.T_DOUBLE, suspend: false };
1641
+ case "minOf": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_DOUBLE, suspend: false }], ret: exports.T_DOUBLE, suspend: false };
1642
+ case "maxOf": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_DOUBLE, suspend: false }], ret: exports.T_DOUBLE, suspend: false };
1643
+ case "minOrNull": return f([], nullable(elemType));
1644
+ case "maxOrNull": return f([], nullable(elemType));
1645
+ case "minByOrNull":
1646
+ case "maxByOrNull": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: nullable(elemType), suspend: false };
1647
+ case "joinToString": return f([exports.T_STRING, exports.T_STRING, exports.T_STRING], exports.T_STRING);
1648
+ case "sortedBy":
1649
+ case "sortedByDescending": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("List", [elemType]), suspend: false };
1650
+ case "sortedWith": return f([exports.T_UNKNOWN], classType("List", [elemType]));
1651
+ case "reversed": return f([], classType("List", [elemType]));
1652
+ case "distinct": return f([], classType("List", [elemType]));
1653
+ case "distinctBy": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("List", [elemType]), suspend: false };
1654
+ case "flatten": return f([], classType("List", [exports.T_UNKNOWN]));
1655
+ case "flatMap": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: classType("List", [exports.T_UNKNOWN]), suspend: false }], ret: classType("List", [exports.T_UNKNOWN]), suspend: false };
1656
+ case "fold": return { tag: "func", params: [exports.T_UNKNOWN, { tag: "func", params: [exports.T_UNKNOWN, elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: exports.T_UNKNOWN, suspend: false };
1657
+ case "reduce": return { tag: "func", params: [{ tag: "func", params: [elemType, elemType], ret: elemType, suspend: false }], ret: elemType, suspend: false };
1658
+ case "groupBy": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("Map", [exports.T_UNKNOWN, classType("List", [elemType])]), suspend: false };
1659
+ case "associate": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: classType("Pair", [exports.T_UNKNOWN, exports.T_UNKNOWN]), suspend: false }], ret: classType("Map", [exports.T_UNKNOWN, exports.T_UNKNOWN]), suspend: false };
1660
+ case "associateBy": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("Map", [exports.T_UNKNOWN, elemType]), suspend: false };
1661
+ case "associateWith": return { tag: "func", params: [{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("Map", [elemType, exports.T_UNKNOWN]), suspend: false };
1662
+ case "partition": return f([predFn], classType("Pair", [classType("List", [elemType]), classType("List", [elemType])]));
1663
+ case "zip": return { tag: "func", params: [classType("List", [exports.T_UNKNOWN])], ret: classType("List", [classType("Pair", [elemType, exports.T_UNKNOWN])]), suspend: false };
1664
+ case "zipWithNext": return f([], classType("List", [classType("Pair", [elemType, elemType])]));
1665
+ case "take": return f([exports.T_INT], classType("List", [elemType]));
1666
+ case "takeLast": return f([exports.T_INT], classType("List", [elemType]));
1667
+ case "takeWhile": return f([predFn], classType("List", [elemType]));
1668
+ case "drop": return f([exports.T_INT], classType("List", [elemType]));
1669
+ case "dropLast": return f([exports.T_INT], classType("List", [elemType]));
1670
+ case "dropWhile": return f([predFn], classType("List", [elemType]));
1671
+ case "chunked": return f([exports.T_INT], classType("List", [classType("List", [elemType])]));
1672
+ case "windowed": return f([exports.T_INT], classType("List", [classType("List", [elemType])]));
1673
+ case "toList": return f([], classType("List", [elemType]));
1674
+ case "toSet": return f([], classType("Set", [elemType]));
1675
+ case "toMutableList": return f([], classType("MutableList", [elemType]));
1676
+ case "toMutableSet": return f([], classType("MutableSet", [elemType]));
1677
+ case "withIndex": return f([], classType("List", [classType("IndexedValue", [elemType])]));
1678
+ case "onEach": return f([{ tag: "func", params: [elemType], ret: exports.T_UNIT, suspend: false }], classType("List", [elemType]));
1679
+ case "plus": return f([classType("List", [elemType])], classType("List", [elemType]));
1680
+ case "minus": return f([elemType], classType("List", [elemType]));
1681
+ case "intersect": return f([classType("Set", [elemType])], classType("Set", [elemType]));
1682
+ case "union": return f([classType("Set", [elemType])], classType("Set", [elemType]));
1683
+ case "subtract": return f([classType("Set", [elemType])], classType("Set", [elemType]));
1684
+ // MutableList/MutableSet only
1685
+ case "add": return f([elemType], exports.T_BOOL);
1686
+ case "remove": return f([elemType], exports.T_BOOL);
1687
+ case "removeIf": return f([predFn], exports.T_BOOL);
1688
+ case "addAll": return f([classType("List", [elemType])], exports.T_BOOL);
1689
+ case "removeAll": return f([classType("List", [elemType])], exports.T_BOOL);
1690
+ case "set": return f([exports.T_INT, elemType], elemType);
1691
+ case "clear":
1692
+ case "shuffle":
1693
+ case "sort": return f([], exports.T_UNIT);
1694
+ case "sortBy": return f([{ tag: "func", params: [elemType], ret: exports.T_UNKNOWN, suspend: false }], exports.T_UNIT);
1695
+ default: return exports.T_UNKNOWN;
1696
+ }
1697
+ }
1698
+ // Map member types
1699
+ if (targetType.tag === "class" &&
1700
+ (targetType.name === "Map" || targetType.name === "MutableMap")) {
1701
+ const keyType = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
1702
+ const valueType = targetType.typeArgs[1] ?? exports.T_UNKNOWN;
1703
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1704
+ switch (member) {
1705
+ case "size": return exports.T_INT;
1706
+ case "isEmpty":
1707
+ case "isNotEmpty": return f([], exports.T_BOOL);
1708
+ case "keys": return classType("Set", [keyType]);
1709
+ case "values": return classType("Collection", [valueType]);
1710
+ case "entries": return classType("Set", [classType("MapEntry", [keyType, valueType])]);
1711
+ case "get": return f([keyType], nullable(valueType));
1712
+ case "containsKey": return f([keyType], exports.T_BOOL);
1713
+ case "containsValue": return f([valueType], exports.T_BOOL);
1714
+ case "getOrDefault": return f([keyType, valueType], valueType);
1715
+ // MutableMap only
1716
+ case "put": return f([keyType, valueType], nullable(valueType));
1717
+ case "remove": return f([keyType], nullable(valueType));
1718
+ case "putAll": return f([classType("Map", [keyType, valueType])], exports.T_UNIT);
1719
+ case "clear": return f([], exports.T_UNIT);
1720
+ default: return exports.T_UNKNOWN;
1721
+ }
1722
+ }
1723
+ // IntRange members
1724
+ if (targetType.tag === "class" && targetType.name === "IntRange") {
1725
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1726
+ switch (member) {
1727
+ case "start":
1728
+ case "endInclusive":
1729
+ case "stepSize": return exports.T_INT;
1730
+ case "first":
1731
+ case "last":
1732
+ case "count": return f([], exports.T_INT);
1733
+ case "isEmpty": return f([], exports.T_BOOL);
1734
+ case "contains": return f([exports.T_INT], exports.T_BOOL);
1735
+ case "step": return f([exports.T_INT], classType("IntRange"));
1736
+ case "toList": return f([], classType("List", [exports.T_INT]));
1737
+ case "toString": return f([], exports.T_STRING);
1738
+ default: return exports.T_UNKNOWN;
1739
+ }
1740
+ }
1741
+ // Result<T> members
1742
+ if (targetType.tag === "class" && targetType.name === "Result") {
1743
+ const inner = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
1744
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1745
+ switch (member) {
1746
+ case "isSuccess":
1747
+ case "isFailure": return exports.T_BOOL;
1748
+ case "getOrNull": return f([], nullable(inner));
1749
+ case "getOrUndefined": return f([], nullable(inner));
1750
+ case "getOrThrow": return f([], inner);
1751
+ case "getOrDefault": return f([inner], inner);
1752
+ case "getOrElse": return f([{ tag: "func", params: [exports.T_ANY], ret: inner, suspend: false }], inner);
1753
+ case "exceptionOrNull": return f([], nullable(exports.T_ANY));
1754
+ case "map": return { tag: "func", params: [{ tag: "func", params: [inner], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("Result", [exports.T_UNKNOWN]), suspend: false };
1755
+ case "mapCatching": return { tag: "func", params: [{ tag: "func", params: [inner], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("Result", [exports.T_UNKNOWN]), suspend: false };
1756
+ case "recover": return f([{ tag: "func", params: [exports.T_ANY], ret: inner, suspend: false }], classType("Result", [inner]));
1757
+ case "onSuccess":
1758
+ case "onFailure": return f([{ tag: "func", params: [exports.T_ANY], ret: exports.T_UNIT, suspend: false }], classType("Result", [inner]));
1759
+ case "fold": return { tag: "func", params: [{ tag: "func", params: [inner], ret: exports.T_UNKNOWN, suspend: false }, { tag: "func", params: [exports.T_ANY], ret: exports.T_UNKNOWN, suspend: false }], ret: exports.T_UNKNOWN, suspend: false };
1760
+ default: return exports.T_UNKNOWN;
1761
+ }
1762
+ }
1763
+ // Regex members
1764
+ if (targetType.tag === "class" && targetType.name === "Regex") {
1765
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1766
+ const regexResult = classType("RegexResult");
1767
+ switch (member) {
1768
+ case "matches":
1769
+ case "containsMatchIn": return f([exports.T_STRING], exports.T_BOOL);
1770
+ case "find": return f([exports.T_STRING], nullable(regexResult));
1771
+ case "findAll": return f([exports.T_STRING], classType("List", [regexResult]));
1772
+ case "replace": return f([exports.T_STRING, exports.T_STRING], exports.T_STRING);
1773
+ case "replaceFirst": return f([exports.T_STRING, exports.T_STRING], exports.T_STRING);
1774
+ case "split": return f([exports.T_STRING], classType("List", [exports.T_STRING]));
1775
+ case "toPattern": return f([], exports.T_STRING);
1776
+ default: return exports.T_UNKNOWN;
1777
+ }
1778
+ }
1779
+ // StringBuilder members
1780
+ if (targetType.tag === "class" && targetType.name === "StringBuilder") {
1781
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1782
+ const self = targetType;
1783
+ switch (member) {
1784
+ case "length": return exports.T_INT;
1785
+ case "append":
1786
+ case "appendLine":
1787
+ case "prepend": return f([exports.T_ANY], self);
1788
+ case "clear": return f([], self);
1789
+ case "isEmpty":
1790
+ case "isNotEmpty": return f([], exports.T_BOOL);
1791
+ case "toString": return f([], exports.T_STRING);
1792
+ default: return exports.T_UNKNOWN;
1793
+ }
1794
+ }
1795
+ // Int companion — Int.MAX_VALUE, Int.MIN_VALUE
1796
+ if (targetType.tag === "class" && targetType.name === "IntCompanion") {
1797
+ switch (member) {
1798
+ case "MAX_VALUE": return exports.T_INT;
1799
+ case "MIN_VALUE": return exports.T_INT;
1800
+ case "SIZE_BITS":
1801
+ case "SIZE_BYTES": return exports.T_INT;
1802
+ default: return exports.T_UNKNOWN;
1803
+ }
1804
+ }
1805
+ // Long companion — Long.MAX_VALUE, Long.MIN_VALUE
1806
+ if (targetType.tag === "class" && targetType.name === "LongCompanion") {
1807
+ switch (member) {
1808
+ case "MAX_VALUE": return exports.T_LONG;
1809
+ case "MIN_VALUE": return exports.T_LONG;
1810
+ case "SIZE_BITS":
1811
+ case "SIZE_BYTES": return exports.T_INT;
1812
+ default: return exports.T_UNKNOWN;
1813
+ }
1814
+ }
1815
+ // Double / Float companion
1816
+ if (targetType.tag === "class" && targetType.name === "NumberCompanion") {
1817
+ switch (member) {
1818
+ case "MAX_VALUE":
1819
+ case "MIN_VALUE":
1820
+ case "POSITIVE_INFINITY":
1821
+ case "NEGATIVE_INFINITY":
1822
+ case "NaN": return exports.T_DOUBLE;
1823
+ default: return exports.T_UNKNOWN;
1824
+ }
1825
+ }
1826
+ // MutableStateFlow<T> / StateFlow<T> members
1827
+ if (targetType.tag === "class" &&
1828
+ (targetType.name === "MutableStateFlow" || targetType.name === "StateFlow")) {
1829
+ const inner = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
1830
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1831
+ switch (member) {
1832
+ case "value": return inner;
1833
+ case "update": return f([{ tag: "func", params: [inner], ret: inner, suspend: false }], exports.T_UNIT);
1834
+ case "collect": return f([{ tag: "func", params: [inner], ret: exports.T_UNIT, suspend: false }], exports.T_UNIT);
1835
+ case "emit": return f([inner], exports.T_UNIT);
1836
+ case "tryEmit": return f([inner], exports.T_BOOL);
1837
+ case "asStateFlow": return f([], classType("StateFlow", [inner]));
1838
+ case "compareAndSet": return f([inner, inner], exports.T_BOOL);
1839
+ case "map": return { tag: "func", params: [{ tag: "func", params: [inner], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("Flow", [exports.T_UNKNOWN]), suspend: false };
1840
+ case "filter": return f([{ tag: "func", params: [inner], ret: exports.T_BOOL, suspend: false }], classType("Flow", [inner]));
1841
+ default: return exports.T_UNKNOWN;
1842
+ }
1843
+ }
1844
+ // Flow<T> members
1845
+ if (targetType.tag === "class" && targetType.name === "Flow") {
1846
+ const inner = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
1847
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1848
+ switch (member) {
1849
+ case "collect": return f([{ tag: "func", params: [inner], ret: exports.T_UNIT, suspend: false }], exports.T_UNIT);
1850
+ case "map": return { tag: "func", params: [{ tag: "func", params: [inner], ret: exports.T_UNKNOWN, suspend: false }], ret: classType("Flow", [exports.T_UNKNOWN]), suspend: false };
1851
+ case "filter": return f([{ tag: "func", params: [inner], ret: exports.T_BOOL, suspend: false }], classType("Flow", [inner]));
1852
+ case "onEach": return f([{ tag: "func", params: [inner], ret: exports.T_UNIT, suspend: false }], classType("Flow", [inner]));
1853
+ case "take": return f([exports.T_INT], classType("Flow", [inner]));
1854
+ case "drop": return f([exports.T_INT], classType("Flow", [inner]));
1855
+ case "debounce": return f([exports.T_LONG], classType("Flow", [inner]));
1856
+ default: return exports.T_UNKNOWN;
1857
+ }
1858
+ }
1859
+ // Deferred<T> members (async {})
1860
+ if (targetType.tag === "class" && targetType.name === "Deferred") {
1861
+ const inner = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
1862
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1863
+ switch (member) {
1864
+ case "await": return { tag: "func", params: [], ret: inner, suspend: true };
1865
+ case "isCompleted":
1866
+ case "isCancelled":
1867
+ case "isActive": return exports.T_BOOL;
1868
+ case "cancel": return f([], exports.T_UNIT);
1869
+ case "getCompleted": return f([], inner);
1870
+ default: return exports.T_UNKNOWN;
1871
+ }
1872
+ }
1873
+ // Job members (launch {})
1874
+ if (targetType.tag === "class" && targetType.name === "Job") {
1875
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1876
+ switch (member) {
1877
+ case "isCompleted":
1878
+ case "isCancelled":
1879
+ case "isActive": return exports.T_BOOL;
1880
+ case "cancel": return f([], exports.T_UNIT);
1881
+ case "join": return { tag: "func", params: [], ret: exports.T_UNIT, suspend: true };
1882
+ default: return exports.T_UNKNOWN;
1883
+ }
1884
+ }
1885
+ // ViewModel members
1886
+ if (targetType.tag === "class" && targetType.name === "ViewModel") {
1887
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1888
+ switch (member) {
1889
+ case "onCleared": return f([], exports.T_UNIT);
1890
+ case "viewModelScope": return classType("CoroutineScope");
1891
+ default: return exports.T_UNKNOWN;
1892
+ }
1893
+ }
1894
+ // CoroutineScope members
1895
+ if (targetType.tag === "class" && targetType.name === "CoroutineScope") {
1896
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1897
+ switch (member) {
1898
+ case "launch": return f([{ tag: "func", params: [], ret: exports.T_UNIT, suspend: true }], classType("Job"));
1899
+ case "cancel": return f([], exports.T_UNIT);
1900
+ case "isActive": return exports.T_BOOL;
1901
+ default: return exports.T_UNKNOWN;
1902
+ }
1903
+ }
1904
+ // User class member lookup
1905
+ if (targetType.tag === "class" && targetType.decl && targetType.decl.body) {
1906
+ // Data class copy()
1907
+ if (member === "copy" && targetType.decl.kind === "DataClassDecl") {
1908
+ const params = targetType.decl.primaryConstructor.params.map(p => this.resolveTypeRef(p.type));
1909
+ const paramNames = targetType.decl.primaryConstructor.params.map(p => p.name);
1910
+ return { tag: "func", params, paramNames, ret: targetType, suspend: false };
1911
+ }
1912
+ for (const m of targetType.decl.body.members) {
1913
+ if (m.kind === "FunDecl" && m.name === member) {
1914
+ return this.funDeclType(m);
1915
+ }
1916
+ if (m.kind === "PropertyDecl" && m.name === member) {
1917
+ if (m.type)
1918
+ return this.resolveTypeRef(m.type);
1919
+ // Infer type from delegate (e.g. `val x by lazy { expr }`)
1920
+ if (m.delegate)
1921
+ return this.inferDelegateType(m.delegate);
1922
+ // Infer type from initializer
1923
+ if (m.initializer)
1924
+ return this.checkExpr(m.initializer);
1925
+ return exports.T_UNKNOWN;
1926
+ }
1927
+ // Nested ObjectDecl (singleton) — return the singleton type
1928
+ if ((m.kind === "ObjectDecl") && m.name === member) {
1929
+ return classType(m.name);
1930
+ }
1931
+ // Nested DataClassDecl or ClassDecl — return as a callable constructor type
1932
+ if ((m.kind === "DataClassDecl" || m.kind === "ClassDecl") && m.name === member) {
1933
+ return classType(m.name, [], m);
1934
+ }
1935
+ // Nested EnumClassDecl — return as class type
1936
+ if (m.kind === "EnumClassDecl" && m.name === member) {
1937
+ return classType(m.name, [], m);
1938
+ }
1939
+ // CompanionObject — return its type (for class.companion syntax)
1940
+ if (m.kind === "CompanionObject" && (member === "companion" || member === "Companion")) {
1941
+ return classType(`${targetType.name}.Companion`);
1942
+ }
1943
+ }
1944
+ }
1945
+ // Bibi HTTP client member types
1946
+ if (targetType.tag === "class" && targetType.name === "Bibi") {
1947
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: true });
1948
+ const responseType = classType("BibiResponse", [exports.T_UNKNOWN]);
1949
+ switch (member) {
1950
+ case "get":
1951
+ case "delete":
1952
+ case "head":
1953
+ return f([exports.T_STRING], responseType);
1954
+ case "post":
1955
+ case "put":
1956
+ case "patch":
1957
+ return f([exports.T_STRING, exports.T_ANY], responseType);
1958
+ case "timeout":
1959
+ case "headers":
1960
+ case "bearer":
1961
+ case "baseUrl":
1962
+ return f([exports.T_ANY], classType("Bibi"));
1963
+ default: return exports.T_UNKNOWN;
1964
+ }
1965
+ }
1966
+ // BibiResponse members
1967
+ if (targetType.tag === "class" && targetType.name === "BibiResponse") {
1968
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1969
+ switch (member) {
1970
+ case "body": return f([], exports.T_UNKNOWN);
1971
+ case "status": return exports.T_INT;
1972
+ case "headers": return classType("Map", [exports.T_STRING, exports.T_STRING]);
1973
+ case "ok": return exports.T_BOOL;
1974
+ default: return exports.T_UNKNOWN;
1975
+ }
1976
+ }
1977
+ // Dispatchers — static-like singleton with fields
1978
+ if (targetType.tag === "class" && targetType.name === "Dispatchers") {
1979
+ switch (member) {
1980
+ case "IO":
1981
+ case "Main":
1982
+ case "Default":
1983
+ case "Unconfined":
1984
+ return classType("CoroutineDispatcher");
1985
+ default: return exports.T_UNKNOWN;
1986
+ }
1987
+ }
1988
+ // Pair<A, B> members
1989
+ if (targetType.tag === "class" && targetType.name === "Pair") {
1990
+ const a = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
1991
+ const b = targetType.typeArgs[1] ?? exports.T_UNKNOWN;
1992
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
1993
+ switch (member) {
1994
+ case "first": return a;
1995
+ case "second": return b;
1996
+ case "component1": return f([], a);
1997
+ case "component2": return f([], b);
1998
+ case "toList": return f([], classType("List", [exports.T_UNKNOWN]));
1999
+ case "toString": return f([], exports.T_STRING);
2000
+ default: return exports.T_UNKNOWN;
2001
+ }
2002
+ }
2003
+ // Triple<A, B, C> members
2004
+ if (targetType.tag === "class" && targetType.name === "Triple") {
2005
+ const a = targetType.typeArgs[0] ?? exports.T_UNKNOWN;
2006
+ const b = targetType.typeArgs[1] ?? exports.T_UNKNOWN;
2007
+ const c = targetType.typeArgs[2] ?? exports.T_UNKNOWN;
2008
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
2009
+ switch (member) {
2010
+ case "first": return a;
2011
+ case "second": return b;
2012
+ case "third": return c;
2013
+ case "component1": return f([], a);
2014
+ case "component2": return f([], b);
2015
+ case "component3": return f([], c);
2016
+ case "toList": return f([], classType("List", [exports.T_UNKNOWN]));
2017
+ case "toString": return f([], exports.T_STRING);
2018
+ default: return exports.T_UNKNOWN;
2019
+ }
2020
+ }
2021
+ // Random<T> members
2022
+ if (targetType.tag === "class" && targetType.name === "Random") {
2023
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
2024
+ switch (member) {
2025
+ case "nextInt": return f([exports.T_INT, exports.T_INT], exports.T_INT);
2026
+ case "nextLong": return f([], exports.T_LONG);
2027
+ case "nextFloat": return f([], exports.T_FLOAT);
2028
+ case "nextDouble": return f([], exports.T_DOUBLE);
2029
+ case "nextBoolean": return f([], exports.T_BOOL);
2030
+ case "nextBytes": return f([exports.T_INT], classType("ByteArray"));
2031
+ default: return exports.T_UNKNOWN;
2032
+ }
2033
+ }
2034
+ // Scope functions available on any type
2035
+ {
2036
+ const f = (params, ret) => ({ tag: "func", params, ret, suspend: false });
2037
+ const predFn = { tag: "func", params: [targetType], ret: exports.T_BOOL, suspend: false };
2038
+ switch (member) {
2039
+ case "let":
2040
+ return f([{ tag: "func", params: [targetType], ret: exports.T_UNKNOWN, suspend: false }], exports.T_UNKNOWN);
2041
+ case "also":
2042
+ return f([{ tag: "func", params: [targetType], ret: exports.T_UNIT, suspend: false }], targetType);
2043
+ case "apply":
2044
+ return f([{ tag: "func", params: [], ret: exports.T_UNIT, suspend: false }], targetType);
2045
+ case "run":
2046
+ return f([{ tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], exports.T_UNKNOWN);
2047
+ case "takeIf":
2048
+ return f([predFn], nullable(targetType));
2049
+ case "takeUnless":
2050
+ return f([predFn], nullable(targetType));
2051
+ case "to":
2052
+ return f([exports.T_UNKNOWN], classType("Pair", [targetType, exports.T_UNKNOWN]));
2053
+ }
2054
+ }
2055
+ return exports.T_UNKNOWN;
2056
+ }
2057
+ /**
2058
+ * Infer the value type of a delegated property expression.
2059
+ * For `by lazy { expr }` the delegate is a lambda; the property type is the
2060
+ * return type of that lambda.
2061
+ * For other delegates we return T_UNKNOWN.
2062
+ */
2063
+ inferDelegateType(delegate) {
2064
+ // `lazy { expr }` — delegate is a CallExpr whose callee is "lazy" and whose
2065
+ // trailing lambda (or first arg) is a LambdaExpr.
2066
+ if (delegate.kind === "CallExpr") {
2067
+ // `Delegates.observable(initial, callback)` — type is the type of `initial`
2068
+ if (delegate.callee.kind === "MemberExpr" &&
2069
+ delegate.callee.member === "observable" &&
2070
+ delegate.callee.target.kind === "NameExpr" &&
2071
+ delegate.callee.target.name === "Delegates" &&
2072
+ delegate.args.length > 0) {
2073
+ return this.checkExpr(delegate.args[0].value);
2074
+ }
2075
+ // `Delegates.vetoable(initial, callback)` — same pattern
2076
+ if (delegate.callee.kind === "MemberExpr" &&
2077
+ delegate.callee.member === "vetoable" &&
2078
+ delegate.callee.target.kind === "NameExpr" &&
2079
+ delegate.callee.target.name === "Delegates" &&
2080
+ delegate.args.length > 0) {
2081
+ return this.checkExpr(delegate.args[0].value);
2082
+ }
2083
+ const lambda = delegate.trailingLambda ??
2084
+ (delegate.args.length === 1 && delegate.args[0].value.kind === "LambdaExpr"
2085
+ ? delegate.args[0].value
2086
+ : null);
2087
+ if (lambda) {
2088
+ // LambdaExpr.body is ReadonlyArray<Stmt> — infer from last statement
2089
+ const stmts = lambda.body;
2090
+ if (stmts.length > 0) {
2091
+ const last = stmts[stmts.length - 1];
2092
+ if (last.kind === "ExprStmt")
2093
+ return this.checkExpr(last.expr);
2094
+ if (last.kind === "ReturnStmt" && last.value)
2095
+ return this.checkExpr(last.value);
2096
+ }
2097
+ }
2098
+ }
2099
+ // For `by someDelegate`, check the expression and hope for the best
2100
+ return this.checkExpr(delegate);
2101
+ }
2102
+ elementTypeOf(iterType) {
2103
+ if (iterType.tag === "class") {
2104
+ const firstArg = iterType.typeArgs[0];
2105
+ if (firstArg)
2106
+ return firstArg;
2107
+ }
2108
+ return exports.T_UNKNOWN;
2109
+ }
2110
+ numericType(a, b) {
2111
+ if (a.tag === "double" || b.tag === "double")
2112
+ return exports.T_DOUBLE;
2113
+ if (a.tag === "float" || b.tag === "float")
2114
+ return exports.T_FLOAT;
2115
+ if (a.tag === "long" || b.tag === "long")
2116
+ return exports.T_LONG;
2117
+ return exports.T_INT;
2118
+ }
2119
+ unify(types) {
2120
+ if (types.length === 0)
2121
+ return exports.T_UNIT;
2122
+ const filtered = types.filter((t) => t.tag !== "nothing");
2123
+ if (filtered.length === 0)
2124
+ return exports.T_NOTHING;
2125
+ if (filtered.length === 1)
2126
+ return filtered[0];
2127
+ // All same?
2128
+ const first = filtered[0];
2129
+ if (filtered.every((t) => t.tag === first.tag))
2130
+ return first;
2131
+ return exports.T_ANY;
2132
+ }
2133
+ assertAssignable(span, from, to) {
2134
+ if (this.isAssignable(from, to))
2135
+ return;
2136
+ this.diag.error(span, diagnostics_js_1.E_TYPE_MISMATCH, `Type mismatch: expected '${this.typeToString(to)}', got '${this.typeToString(from)}'`);
2137
+ }
2138
+ isAssignable(from, to) {
2139
+ if (from.tag === "error" || to.tag === "error")
2140
+ return true;
2141
+ if (from.tag === "nothing" || to.tag === "any")
2142
+ return true;
2143
+ if (from.tag === "unknown" || to.tag === "unknown")
2144
+ return true;
2145
+ if (to.tag === "nullable")
2146
+ return this.isAssignable(from, to.inner) || from.tag === "nullable";
2147
+ if (from.tag === "nullable")
2148
+ return false;
2149
+ return from.tag === to.tag;
2150
+ }
2151
+ withScope(scope, fn) {
2152
+ const prev = this.scope;
2153
+ this.scope = scope;
2154
+ const result = fn();
2155
+ this.scope = prev;
2156
+ return result;
2157
+ }
2158
+ typeToString(t) {
2159
+ switch (t.tag) {
2160
+ case "int": return "Int";
2161
+ case "long": return "Long";
2162
+ case "float": return "Float";
2163
+ case "double": return "Double";
2164
+ case "boolean": return "Boolean";
2165
+ case "string": return "String";
2166
+ case "char": return "Char";
2167
+ case "byte": return "Byte";
2168
+ case "short": return "Short";
2169
+ case "unit": return "Unit";
2170
+ case "any": return "Any";
2171
+ case "nothing": return "Nothing";
2172
+ case "nullable": return `${this.typeToString(t.inner)}?`;
2173
+ case "class": return t.typeArgs.length > 0
2174
+ ? `${t.name}<${t.typeArgs.map((a) => this.typeToString(a)).join(", ")}>`
2175
+ : t.name;
2176
+ case "func": {
2177
+ const p = t.params.map((p) => this.typeToString(p)).join(", ");
2178
+ const ret = this.typeToString(t.ret);
2179
+ return `${t.suspend ? "suspend " : ""}(${p}) -> ${ret}`;
2180
+ }
2181
+ case "typeparam": return t.name;
2182
+ case "error": return "<error>";
2183
+ case "unknown": return "<unknown>";
2184
+ }
2185
+ }
2186
+ }
2187
+ exports.TypeChecker = TypeChecker;
2188
+ // ---------------------------------------------------------------------------
2189
+ // Public helper
2190
+ // ---------------------------------------------------------------------------
2191
+ function typeCheck(program, diag) {
2192
+ const checker = new TypeChecker(diag);
2193
+ checker.checkProgram(program);
2194
+ return checker;
2195
+ }
2196
+ //# sourceMappingURL=typechecker.js.map