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