@jalvin/compiler 2.0.31 → 2.0.33

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