@jalvin/compiler 2.0.43 → 2.0.45
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.d.ts +11 -53
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +344 -839
- package/dist/codegen.js.map +1 -1
- 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/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/typechecker.d.ts +12 -0
- package/dist/typechecker.d.ts.map +1 -1
- package/dist/typechecker.js +244 -152
- package/dist/typechecker.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typechecker.d.ts","sourceRoot":"","sources":["../src/typechecker.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EACL,aAAa,
|
|
1
|
+
{"version":3,"file":"typechecker.d.ts","sourceRoot":"","sources":["../src/typechecker.ts"],"names":[],"mappings":"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"}
|
package/dist/typechecker.js
CHANGED
|
@@ -184,11 +184,9 @@ class TypeEnv {
|
|
|
184
184
|
// State & MVVM
|
|
185
185
|
this.sym("mutableStateOf", { tag: "func", params: [exports.T_UNKNOWN], paramNames: ["value"], ret: classType("MutableState", [exports.T_UNKNOWN]), suspend: false });
|
|
186
186
|
this.sym("remember", { tag: "func", params: [{ tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["calculation"], ret: exports.T_UNKNOWN, suspend: false });
|
|
187
|
-
this.sym("useViewModel", { tag: "func", params: [exports.T_STRING, { tag: "func", params: [], ret: exports.T_UNKNOWN, suspend: false }], paramNames: ["key", "factory"], ret: classType("ViewModel"), suspend: false });
|
|
188
|
-
// Keep old `viewModel` alias for source compat
|
|
189
187
|
this.sym("viewModel", { tag: "func", params: [], ret: classType("ViewModel"), suspend: false });
|
|
190
188
|
this.sym("collectAsState", { tag: "func", params: [exports.T_UNKNOWN], paramNames: ["flow"], ret: exports.T_UNKNOWN, suspend: false });
|
|
191
|
-
//
|
|
189
|
+
// UI effect hooks
|
|
192
190
|
const effectDepsType = classType("List", [exports.T_UNKNOWN]);
|
|
193
191
|
this.sym("LaunchedEffect", { tag: "func", params: [effectDepsType, { tag: "func", params: [], ret: exports.T_UNIT, suspend: true }], paramNames: ["deps", "block"], ret: exports.T_UNIT, suspend: false });
|
|
194
192
|
this.sym("DisposableEffect", { tag: "func", params: [effectDepsType, { tag: "func", params: [], ret: exports.T_UNIT, suspend: false }], paramNames: ["deps", "block"], ret: exports.T_UNIT, suspend: false });
|
|
@@ -243,6 +241,10 @@ class TypeChecker {
|
|
|
243
241
|
inSuspend = false;
|
|
244
242
|
/** Whether we're inside a component */
|
|
245
243
|
inComponent = false;
|
|
244
|
+
/** Nesting depth where component-only APIs would violate stable call order. */
|
|
245
|
+
componentCallOrderDepth = 0;
|
|
246
|
+
/** Context stack describing where call-order nesting was introduced. */
|
|
247
|
+
componentCallOrderContextStack = [];
|
|
246
248
|
/** Whether the program has a wildcard import from a @jalvin/* package */
|
|
247
249
|
hasWildcardJalvinImport = false;
|
|
248
250
|
/** Map from node to resolved type (for IDE use) */
|
|
@@ -797,12 +799,14 @@ class TypeChecker {
|
|
|
797
799
|
this.checkWhileStmt(stmt);
|
|
798
800
|
break;
|
|
799
801
|
case "DoWhileStmt": {
|
|
800
|
-
this.
|
|
801
|
-
|
|
802
|
+
this.withComponentOrderNesting("do-while loop", () => {
|
|
803
|
+
this.checkBlock(stmt.body);
|
|
804
|
+
this.checkExpr(stmt.condition);
|
|
805
|
+
});
|
|
802
806
|
break;
|
|
803
807
|
}
|
|
804
808
|
case "TryCatchStmt":
|
|
805
|
-
this.checkTryCatch(stmt);
|
|
809
|
+
this.withComponentOrderNesting("try/catch statement", () => this.checkTryCatch(stmt));
|
|
806
810
|
break;
|
|
807
811
|
case "LabeledStmt":
|
|
808
812
|
this.checkStmt(stmt.body);
|
|
@@ -810,41 +814,43 @@ class TypeChecker {
|
|
|
810
814
|
}
|
|
811
815
|
}
|
|
812
816
|
checkIfStmt(stmt) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
}
|
|
824
|
-
this.withScope(thenScope, () => this.checkBlock(stmt.then));
|
|
825
|
-
if (stmt.else) {
|
|
826
|
-
const elseNarrowings = this.extractElseSmartCasts(stmt.condition);
|
|
827
|
-
if (elseNarrowings.size > 0) {
|
|
828
|
-
const elseScope = new Scope(this.scope);
|
|
829
|
-
for (const [name, type] of elseNarrowings) {
|
|
830
|
-
const existing = this.scope.lookup(name);
|
|
831
|
-
if (existing)
|
|
832
|
-
elseScope.define({ ...existing, type });
|
|
817
|
+
this.withComponentOrderNesting("if statement", () => {
|
|
818
|
+
const condType = this.checkExpr(stmt.condition);
|
|
819
|
+
this.assertAssignable(stmt.condition.span, condType, exports.T_BOOL);
|
|
820
|
+
// Smart cast: if (x is T) { ... } narrows x to T inside the then branch
|
|
821
|
+
const narrowings = this.extractSmartCasts(stmt.condition);
|
|
822
|
+
const thenScope = new Scope(this.scope);
|
|
823
|
+
for (const [name, type] of narrowings) {
|
|
824
|
+
const existing = this.scope.lookup(name);
|
|
825
|
+
if (existing) {
|
|
826
|
+
thenScope.define({ ...existing, type });
|
|
833
827
|
}
|
|
834
|
-
|
|
828
|
+
}
|
|
829
|
+
this.withScope(thenScope, () => this.checkBlock(stmt.then));
|
|
830
|
+
if (stmt.else) {
|
|
831
|
+
const elseNarrowings = this.extractElseSmartCasts(stmt.condition);
|
|
832
|
+
if (elseNarrowings.size > 0) {
|
|
833
|
+
const elseScope = new Scope(this.scope);
|
|
834
|
+
for (const [name, type] of elseNarrowings) {
|
|
835
|
+
const existing = this.scope.lookup(name);
|
|
836
|
+
if (existing)
|
|
837
|
+
elseScope.define({ ...existing, type });
|
|
838
|
+
}
|
|
839
|
+
this.withScope(elseScope, () => {
|
|
840
|
+
if (stmt.else.kind === "Block")
|
|
841
|
+
this.checkBlock(stmt.else);
|
|
842
|
+
else if (stmt.else.kind === "IfStmt")
|
|
843
|
+
this.checkIfStmt(stmt.else);
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
835
847
|
if (stmt.else.kind === "Block")
|
|
836
848
|
this.checkBlock(stmt.else);
|
|
837
849
|
else if (stmt.else.kind === "IfStmt")
|
|
838
850
|
this.checkIfStmt(stmt.else);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
else {
|
|
842
|
-
if (stmt.else.kind === "Block")
|
|
843
|
-
this.checkBlock(stmt.else);
|
|
844
|
-
else if (stmt.else.kind === "IfStmt")
|
|
845
|
-
this.checkIfStmt(stmt.else);
|
|
851
|
+
}
|
|
846
852
|
}
|
|
847
|
-
}
|
|
853
|
+
});
|
|
848
854
|
}
|
|
849
855
|
/**
|
|
850
856
|
* Extract smart-cast bindings from an `is`-check condition.
|
|
@@ -909,61 +915,63 @@ class TypeChecker {
|
|
|
909
915
|
return result;
|
|
910
916
|
}
|
|
911
917
|
checkWhenStmt(stmt) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
type: subjectType,
|
|
922
|
-
mutable: false,
|
|
923
|
-
span: stmt.subject.span,
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
this.withScope(branchScope, () => {
|
|
927
|
-
// Smart cast narrowing: for single `is Type` condition, narrow subject type
|
|
928
|
-
if (stmt.subject?.binding &&
|
|
929
|
-
subjectType &&
|
|
930
|
-
branch.conditions.length === 1 &&
|
|
931
|
-
branch.conditions[0].kind === "WhenIsCondition" &&
|
|
932
|
-
!branch.conditions[0].negated) {
|
|
933
|
-
const isC = branch.conditions[0];
|
|
934
|
-
const narrowedType = this.resolveTypeRef(isC.type);
|
|
935
|
-
const narrowScope = new Scope(branchScope);
|
|
936
|
-
narrowScope.define({
|
|
918
|
+
this.withComponentOrderNesting("when statement", () => {
|
|
919
|
+
const subjectType = stmt.subject ? this.checkExpr(stmt.subject.expr) : null;
|
|
920
|
+
// Bind `when (val x = expr)` into each branch scope
|
|
921
|
+
for (const branch of stmt.branches) {
|
|
922
|
+
const branchScope = stmt.subject?.binding
|
|
923
|
+
? new Scope(this.scope)
|
|
924
|
+
: this.scope;
|
|
925
|
+
if (stmt.subject?.binding && subjectType) {
|
|
926
|
+
branchScope.define({
|
|
937
927
|
name: stmt.subject.binding,
|
|
938
|
-
type:
|
|
928
|
+
type: subjectType,
|
|
939
929
|
mutable: false,
|
|
940
|
-
span:
|
|
930
|
+
span: stmt.subject.span,
|
|
941
931
|
});
|
|
942
|
-
|
|
932
|
+
}
|
|
933
|
+
this.withScope(branchScope, () => {
|
|
934
|
+
// Smart cast narrowing: for single `is Type` condition, narrow subject type
|
|
935
|
+
if (stmt.subject?.binding &&
|
|
936
|
+
subjectType &&
|
|
937
|
+
branch.conditions.length === 1 &&
|
|
938
|
+
branch.conditions[0].kind === "WhenIsCondition" &&
|
|
939
|
+
!branch.conditions[0].negated) {
|
|
940
|
+
const isC = branch.conditions[0];
|
|
941
|
+
const narrowedType = this.resolveTypeRef(isC.type);
|
|
942
|
+
const narrowScope = new Scope(branchScope);
|
|
943
|
+
narrowScope.define({
|
|
944
|
+
name: stmt.subject.binding,
|
|
945
|
+
type: narrowedType,
|
|
946
|
+
mutable: false,
|
|
947
|
+
span: isC.span,
|
|
948
|
+
});
|
|
949
|
+
this.withScope(narrowScope, () => {
|
|
950
|
+
if (branch.body.kind === "Block")
|
|
951
|
+
this.checkBlock(branch.body);
|
|
952
|
+
else
|
|
953
|
+
this.checkExpr(branch.body);
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
for (const cond of branch.conditions) {
|
|
958
|
+
if (cond.kind === "WhenExprCondition")
|
|
959
|
+
this.checkExpr(cond.expr);
|
|
960
|
+
else if (cond.kind === "WhenInCondition")
|
|
961
|
+
this.checkExpr(cond.expr);
|
|
962
|
+
}
|
|
943
963
|
if (branch.body.kind === "Block")
|
|
944
964
|
this.checkBlock(branch.body);
|
|
945
965
|
else
|
|
946
966
|
this.checkExpr(branch.body);
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
else {
|
|
950
|
-
for (const cond of branch.conditions) {
|
|
951
|
-
if (cond.kind === "WhenExprCondition")
|
|
952
|
-
this.checkExpr(cond.expr);
|
|
953
|
-
else if (cond.kind === "WhenInCondition")
|
|
954
|
-
this.checkExpr(cond.expr);
|
|
955
967
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
// Exhaustiveness check for sealed classes
|
|
964
|
-
if (subjectType) {
|
|
965
|
-
this.checkWhenExhaustiveness(stmt.span, stmt.branches, subjectType);
|
|
966
|
-
}
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
// Exhaustiveness check for sealed classes
|
|
971
|
+
if (subjectType) {
|
|
972
|
+
this.checkWhenExhaustiveness(stmt.span, stmt.branches, subjectType);
|
|
973
|
+
}
|
|
974
|
+
});
|
|
967
975
|
}
|
|
968
976
|
/**
|
|
969
977
|
* If `subjectType` is a sealed class or enum, verify all variants are
|
|
@@ -1030,17 +1038,21 @@ class TypeChecker {
|
|
|
1030
1038
|
}
|
|
1031
1039
|
}
|
|
1032
1040
|
checkForStmt(stmt) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1041
|
+
this.withComponentOrderNesting("for loop", () => {
|
|
1042
|
+
const iterType = this.checkExpr(stmt.iterable);
|
|
1043
|
+
const elemType = this.elementTypeOf(iterType);
|
|
1044
|
+
const childScope = new Scope(this.scope);
|
|
1045
|
+
if (typeof stmt.binding === "string") {
|
|
1046
|
+
childScope.define({ name: stmt.binding, type: elemType, mutable: false, span: stmt.span });
|
|
1047
|
+
}
|
|
1048
|
+
this.withScope(childScope, () => this.checkBlock(stmt.body));
|
|
1049
|
+
});
|
|
1040
1050
|
}
|
|
1041
1051
|
checkWhileStmt(stmt) {
|
|
1042
|
-
this.
|
|
1043
|
-
|
|
1052
|
+
this.withComponentOrderNesting("while loop", () => {
|
|
1053
|
+
this.checkExpr(stmt.condition);
|
|
1054
|
+
this.checkBlock(stmt.body);
|
|
1055
|
+
});
|
|
1044
1056
|
}
|
|
1045
1057
|
checkTryCatch(stmt) {
|
|
1046
1058
|
this.checkBlock(stmt.body);
|
|
@@ -1178,29 +1190,35 @@ class TypeChecker {
|
|
|
1178
1190
|
case "LambdaExpr": return this.checkLambda(expr);
|
|
1179
1191
|
case "IfExpr": return this.checkIfExpr(expr);
|
|
1180
1192
|
case "WhenExpr": {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1193
|
+
let resultType = exports.T_UNKNOWN;
|
|
1194
|
+
this.withComponentOrderNesting("when expression", () => {
|
|
1195
|
+
const subjectType = expr.subject ? this.checkExpr(expr.subject.expr) : null;
|
|
1196
|
+
const branchTypes = [];
|
|
1197
|
+
for (const b of expr.branches) {
|
|
1198
|
+
for (const c of b.conditions) {
|
|
1199
|
+
if (c.kind === "WhenExprCondition")
|
|
1200
|
+
this.checkExpr(c.expr);
|
|
1201
|
+
else if (c.kind === "WhenInCondition")
|
|
1202
|
+
this.checkExpr(c.expr);
|
|
1203
|
+
}
|
|
1204
|
+
branchTypes.push(b.body.kind === "Block" ? (this.checkBlock(b.body), exports.T_UNIT) : this.checkExpr(b.body));
|
|
1189
1205
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
this.
|
|
1195
|
-
}
|
|
1196
|
-
return
|
|
1206
|
+
// Exhaustiveness: when-expr on a sealed class MUST be exhaustive
|
|
1207
|
+
if (subjectType) {
|
|
1208
|
+
this.checkWhenExhaustiveness(expr.span, expr.branches, subjectType);
|
|
1209
|
+
}
|
|
1210
|
+
resultType = this.unify(branchTypes);
|
|
1211
|
+
});
|
|
1212
|
+
return resultType;
|
|
1197
1213
|
}
|
|
1198
1214
|
case "TryCatchExpr": {
|
|
1199
|
-
this.
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1215
|
+
this.withComponentOrderNesting("try/catch expression", () => {
|
|
1216
|
+
this.checkBlock(expr.body);
|
|
1217
|
+
for (const c of expr.catches)
|
|
1218
|
+
this.checkBlock(c.body);
|
|
1219
|
+
if (expr.finally)
|
|
1220
|
+
this.checkBlock(expr.finally);
|
|
1221
|
+
});
|
|
1204
1222
|
return exports.T_UNKNOWN;
|
|
1205
1223
|
}
|
|
1206
1224
|
case "TypeCheckExpr": {
|
|
@@ -1239,14 +1257,14 @@ class TypeChecker {
|
|
|
1239
1257
|
}
|
|
1240
1258
|
const prevSuspend = this.inSuspend;
|
|
1241
1259
|
this.inSuspend = true;
|
|
1242
|
-
this.checkBlock(expr.body);
|
|
1260
|
+
this.withComponentOrderNesting("launch block", () => this.checkBlock(expr.body));
|
|
1243
1261
|
this.inSuspend = prevSuspend;
|
|
1244
1262
|
return classType("Job");
|
|
1245
1263
|
}
|
|
1246
1264
|
case "AsyncExpr": {
|
|
1247
1265
|
const prevSuspend = this.inSuspend;
|
|
1248
1266
|
this.inSuspend = true;
|
|
1249
|
-
this.checkBlock(expr.body);
|
|
1267
|
+
this.withComponentOrderNesting("async block", () => this.checkBlock(expr.body));
|
|
1250
1268
|
this.inSuspend = prevSuspend;
|
|
1251
1269
|
return classType("Deferred", [exports.T_UNKNOWN]);
|
|
1252
1270
|
}
|
|
@@ -1395,6 +1413,27 @@ class TypeChecker {
|
|
|
1395
1413
|
return null;
|
|
1396
1414
|
}
|
|
1397
1415
|
checkCallExpr(expr) {
|
|
1416
|
+
const componentOnlyName = this.getComponentOnlyCalleeName(expr.callee);
|
|
1417
|
+
if (componentOnlyName) {
|
|
1418
|
+
if (!this.inComponent) {
|
|
1419
|
+
this.diag.error(expr.span, diagnostics_js_1.E_COMPONENT_CONTEXT_REQUIRED, `'${componentOnlyName}' can only be called inside a component function`);
|
|
1420
|
+
}
|
|
1421
|
+
else if (this.componentCallOrderDepth > 0) {
|
|
1422
|
+
const context = this.currentComponentOrderContext();
|
|
1423
|
+
this.diag.error(expr.span, diagnostics_js_1.E_COMPONENT_CALL_ORDER, `'${componentOnlyName}' must be called unconditionally at the top level of a component function`, [
|
|
1424
|
+
{
|
|
1425
|
+
span: null,
|
|
1426
|
+
message: context
|
|
1427
|
+
? `Invalid call occurs inside ${context}`
|
|
1428
|
+
: "Invalid call occurs inside nested control flow",
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
span: null,
|
|
1432
|
+
message: "Move this call to the top-level component body",
|
|
1433
|
+
},
|
|
1434
|
+
]);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1398
1437
|
const calleeType = this.checkExpr(expr.callee);
|
|
1399
1438
|
for (const arg of expr.args)
|
|
1400
1439
|
this.checkExpr(arg.value);
|
|
@@ -1439,6 +1478,53 @@ class TypeChecker {
|
|
|
1439
1478
|
this.diag.error(expr.span, diagnostics_js_1.E_NOT_A_FUNCTION, `Type '${this.typeToString(calleeType)}' is not callable`);
|
|
1440
1479
|
return exports.T_ERROR;
|
|
1441
1480
|
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Runtime APIs that are only valid in component functions.
|
|
1483
|
+
* We key by callee name to keep checks fast and diagnostics clear.
|
|
1484
|
+
*/
|
|
1485
|
+
isComponentOnlyApi(name) {
|
|
1486
|
+
return name === "mutableStateOf" ||
|
|
1487
|
+
name === "remember" ||
|
|
1488
|
+
name === "rememberMutableStateOf" ||
|
|
1489
|
+
name === "collectAsState" ||
|
|
1490
|
+
name === "useViewModel" ||
|
|
1491
|
+
name === "LaunchedEffect" ||
|
|
1492
|
+
name === "DisposableEffect" ||
|
|
1493
|
+
name === "SideEffect" ||
|
|
1494
|
+
name === "useMutableInteractionSource" ||
|
|
1495
|
+
name === "useIsHovered" ||
|
|
1496
|
+
name === "useIsFocused" ||
|
|
1497
|
+
name === "useIsPressed";
|
|
1498
|
+
}
|
|
1499
|
+
getComponentOnlyCalleeName(callee) {
|
|
1500
|
+
if (callee.kind === "NameExpr" && this.isComponentOnlyApi(callee.name)) {
|
|
1501
|
+
return callee.name;
|
|
1502
|
+
}
|
|
1503
|
+
if ((callee.kind === "MemberExpr" || callee.kind === "SafeMemberExpr") && this.isComponentOnlyApi(callee.member)) {
|
|
1504
|
+
return callee.member;
|
|
1505
|
+
}
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
currentComponentOrderContext() {
|
|
1509
|
+
if (this.componentCallOrderContextStack.length === 0)
|
|
1510
|
+
return null;
|
|
1511
|
+
return this.componentCallOrderContextStack[this.componentCallOrderContextStack.length - 1];
|
|
1512
|
+
}
|
|
1513
|
+
withComponentOrderNesting(context, fn) {
|
|
1514
|
+
if (!this.inComponent) {
|
|
1515
|
+
fn();
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
this.componentCallOrderDepth++;
|
|
1519
|
+
this.componentCallOrderContextStack.push(context);
|
|
1520
|
+
try {
|
|
1521
|
+
fn();
|
|
1522
|
+
}
|
|
1523
|
+
finally {
|
|
1524
|
+
this.componentCallOrderContextStack.pop();
|
|
1525
|
+
this.componentCallOrderDepth--;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1442
1528
|
checkLambda(expr, expectedParamTypes) {
|
|
1443
1529
|
const childScope = new Scope(this.scope);
|
|
1444
1530
|
// If the lambda has no explicit params, synthesise an `it` binding.
|
|
@@ -1467,12 +1553,14 @@ class TypeChecker {
|
|
|
1467
1553
|
}
|
|
1468
1554
|
let retType = exports.T_UNIT;
|
|
1469
1555
|
this.withScope(childScope, () => {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1556
|
+
this.withComponentOrderNesting("lambda body", () => {
|
|
1557
|
+
for (const stmt of expr.body) {
|
|
1558
|
+
if (stmt.kind === "ExprStmt")
|
|
1559
|
+
retType = this.checkExpr(stmt.expr);
|
|
1560
|
+
else
|
|
1561
|
+
this.checkStmt(stmt);
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1476
1564
|
});
|
|
1477
1565
|
const paramTypes = expr.params.length > 0
|
|
1478
1566
|
? expr.params.map((p) => (p.type ? this.resolveTypeRef(p.type) : exports.T_UNKNOWN))
|
|
@@ -1480,42 +1568,46 @@ class TypeChecker {
|
|
|
1480
1568
|
return { tag: "func", params: paramTypes, ret: retType, suspend: false };
|
|
1481
1569
|
}
|
|
1482
1570
|
checkIfExpr(expr) {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1490
|
-
thenScope.define({ ...existing, type });
|
|
1491
|
-
}
|
|
1492
|
-
const thenType = this.withScope(thenScope, () => expr.then.kind === "Block"
|
|
1493
|
-
? (this.checkBlock(expr.then), exports.T_UNIT)
|
|
1494
|
-
: this.checkExpr(expr.then));
|
|
1495
|
-
// Apply narrowings for the else branch (e.g. `x !is T` → else: x IS T)
|
|
1496
|
-
const elseNarrowings = this.extractElseSmartCasts(expr.condition);
|
|
1497
|
-
let elseType;
|
|
1498
|
-
if (elseNarrowings.size > 0) {
|
|
1499
|
-
const elseScope = new Scope(this.scope);
|
|
1500
|
-
for (const [name, type] of elseNarrowings) {
|
|
1571
|
+
let resultType = exports.T_UNKNOWN;
|
|
1572
|
+
this.withComponentOrderNesting("if expression", () => {
|
|
1573
|
+
this.checkExpr(expr.condition);
|
|
1574
|
+
// Apply smart casts in the `then` branch
|
|
1575
|
+
const narrowings = this.extractSmartCasts(expr.condition);
|
|
1576
|
+
const thenScope = new Scope(this.scope);
|
|
1577
|
+
for (const [name, type] of narrowings) {
|
|
1501
1578
|
const existing = this.scope.lookup(name);
|
|
1502
1579
|
if (existing)
|
|
1503
|
-
|
|
1580
|
+
thenScope.define({ ...existing, type });
|
|
1581
|
+
}
|
|
1582
|
+
const thenType = this.withScope(thenScope, () => expr.then.kind === "Block"
|
|
1583
|
+
? (this.checkBlock(expr.then), exports.T_UNIT)
|
|
1584
|
+
: this.checkExpr(expr.then));
|
|
1585
|
+
// Apply narrowings for the else branch (e.g. `x !is T` → else: x IS T)
|
|
1586
|
+
const elseNarrowings = this.extractElseSmartCasts(expr.condition);
|
|
1587
|
+
let elseType;
|
|
1588
|
+
if (elseNarrowings.size > 0) {
|
|
1589
|
+
const elseScope = new Scope(this.scope);
|
|
1590
|
+
for (const [name, type] of elseNarrowings) {
|
|
1591
|
+
const existing = this.scope.lookup(name);
|
|
1592
|
+
if (existing)
|
|
1593
|
+
elseScope.define({ ...existing, type });
|
|
1594
|
+
}
|
|
1595
|
+
elseType = this.withScope(elseScope, () => expr.else.kind === "Block"
|
|
1596
|
+
? (this.checkBlock(expr.else), exports.T_UNIT)
|
|
1597
|
+
: expr.else.kind === "IfExpr"
|
|
1598
|
+
? this.checkIfExpr(expr.else)
|
|
1599
|
+
: this.checkExpr(expr.else));
|
|
1504
1600
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
? this.checkIfExpr(expr.else)
|
|
1516
|
-
: this.checkExpr(expr.else);
|
|
1517
|
-
}
|
|
1518
|
-
return this.unify([thenType, elseType]);
|
|
1601
|
+
else {
|
|
1602
|
+
elseType = expr.else.kind === "Block"
|
|
1603
|
+
? (this.checkBlock(expr.else), exports.T_UNIT)
|
|
1604
|
+
: expr.else.kind === "IfExpr"
|
|
1605
|
+
? this.checkIfExpr(expr.else)
|
|
1606
|
+
: this.checkExpr(expr.else);
|
|
1607
|
+
}
|
|
1608
|
+
resultType = this.unify([thenType, elseType]);
|
|
1609
|
+
});
|
|
1610
|
+
return resultType;
|
|
1519
1611
|
}
|
|
1520
1612
|
// ── Type resolution ────────────────────────────────────────────────────────
|
|
1521
1613
|
lookupType(nameParts) {
|