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