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