@jalvin/compiler 2.0.41 → 2.0.43
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 +53 -11
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +831 -339
- package/dist/codegen.js.map +1 -1
- package/dist/diagnostics.d.ts +0 -2
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +1 -3
- package/dist/diagnostics.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/typechecker.d.ts +0 -12
- package/dist/typechecker.d.ts.map +1 -1
- package/dist/typechecker.js +152 -244
- 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,9 +184,11 @@ 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
|
|
187
189
|
this.sym("viewModel", { tag: "func", params: [], ret: classType("ViewModel"), suspend: false });
|
|
188
190
|
this.sym("collectAsState", { tag: "func", params: [exports.T_UNKNOWN], paramNames: ["flow"], ret: exports.T_UNKNOWN, suspend: false });
|
|
189
|
-
//
|
|
191
|
+
// React effect hooks
|
|
190
192
|
const effectDepsType = classType("List", [exports.T_UNKNOWN]);
|
|
191
193
|
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 });
|
|
192
194
|
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 });
|
|
@@ -241,10 +243,6 @@ class TypeChecker {
|
|
|
241
243
|
inSuspend = false;
|
|
242
244
|
/** Whether we're inside a component */
|
|
243
245
|
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 = [];
|
|
248
246
|
/** Whether the program has a wildcard import from a @jalvin/* package */
|
|
249
247
|
hasWildcardJalvinImport = false;
|
|
250
248
|
/** Map from node to resolved type (for IDE use) */
|
|
@@ -799,14 +797,12 @@ class TypeChecker {
|
|
|
799
797
|
this.checkWhileStmt(stmt);
|
|
800
798
|
break;
|
|
801
799
|
case "DoWhileStmt": {
|
|
802
|
-
this.
|
|
803
|
-
|
|
804
|
-
this.checkExpr(stmt.condition);
|
|
805
|
-
});
|
|
800
|
+
this.checkBlock(stmt.body);
|
|
801
|
+
this.checkExpr(stmt.condition);
|
|
806
802
|
break;
|
|
807
803
|
}
|
|
808
804
|
case "TryCatchStmt":
|
|
809
|
-
this.
|
|
805
|
+
this.checkTryCatch(stmt);
|
|
810
806
|
break;
|
|
811
807
|
case "LabeledStmt":
|
|
812
808
|
this.checkStmt(stmt.body);
|
|
@@ -814,43 +810,41 @@ class TypeChecker {
|
|
|
814
810
|
}
|
|
815
811
|
}
|
|
816
812
|
checkIfStmt(stmt) {
|
|
817
|
-
this.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
});
|
|
813
|
+
const condType = this.checkExpr(stmt.condition);
|
|
814
|
+
this.assertAssignable(stmt.condition.span, condType, exports.T_BOOL);
|
|
815
|
+
// Smart cast: if (x is T) { ... } narrows x to T inside the then branch
|
|
816
|
+
const narrowings = this.extractSmartCasts(stmt.condition);
|
|
817
|
+
const thenScope = new Scope(this.scope);
|
|
818
|
+
for (const [name, type] of narrowings) {
|
|
819
|
+
const existing = this.scope.lookup(name);
|
|
820
|
+
if (existing) {
|
|
821
|
+
thenScope.define({ ...existing, type });
|
|
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 });
|
|
845
833
|
}
|
|
846
|
-
|
|
834
|
+
this.withScope(elseScope, () => {
|
|
847
835
|
if (stmt.else.kind === "Block")
|
|
848
836
|
this.checkBlock(stmt.else);
|
|
849
837
|
else if (stmt.else.kind === "IfStmt")
|
|
850
838
|
this.checkIfStmt(stmt.else);
|
|
851
|
-
}
|
|
839
|
+
});
|
|
852
840
|
}
|
|
853
|
-
|
|
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);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
854
848
|
}
|
|
855
849
|
/**
|
|
856
850
|
* Extract smart-cast bindings from an `is`-check condition.
|
|
@@ -915,63 +909,61 @@ class TypeChecker {
|
|
|
915
909
|
return result;
|
|
916
910
|
}
|
|
917
911
|
checkWhenStmt(stmt) {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
912
|
+
const subjectType = stmt.subject ? this.checkExpr(stmt.subject.expr) : null;
|
|
913
|
+
// Bind `when (val x = expr)` into each branch scope
|
|
914
|
+
for (const branch of stmt.branches) {
|
|
915
|
+
const branchScope = stmt.subject?.binding
|
|
916
|
+
? new Scope(this.scope)
|
|
917
|
+
: this.scope;
|
|
918
|
+
if (stmt.subject?.binding && subjectType) {
|
|
919
|
+
branchScope.define({
|
|
920
|
+
name: stmt.subject.binding,
|
|
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({
|
|
927
937
|
name: stmt.subject.binding,
|
|
928
|
-
type:
|
|
938
|
+
type: narrowedType,
|
|
929
939
|
mutable: false,
|
|
930
|
-
span:
|
|
940
|
+
span: isC.span,
|
|
931
941
|
});
|
|
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
|
-
}
|
|
942
|
+
this.withScope(narrowScope, () => {
|
|
963
943
|
if (branch.body.kind === "Block")
|
|
964
944
|
this.checkBlock(branch.body);
|
|
965
945
|
else
|
|
966
946
|
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);
|
|
967
955
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
}
|
|
974
|
-
}
|
|
956
|
+
if (branch.body.kind === "Block")
|
|
957
|
+
this.checkBlock(branch.body);
|
|
958
|
+
else
|
|
959
|
+
this.checkExpr(branch.body);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
// Exhaustiveness check for sealed classes
|
|
964
|
+
if (subjectType) {
|
|
965
|
+
this.checkWhenExhaustiveness(stmt.span, stmt.branches, subjectType);
|
|
966
|
+
}
|
|
975
967
|
}
|
|
976
968
|
/**
|
|
977
969
|
* If `subjectType` is a sealed class or enum, verify all variants are
|
|
@@ -1038,21 +1030,17 @@ class TypeChecker {
|
|
|
1038
1030
|
}
|
|
1039
1031
|
}
|
|
1040
1032
|
checkForStmt(stmt) {
|
|
1041
|
-
this.
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
this.withScope(childScope, () => this.checkBlock(stmt.body));
|
|
1049
|
-
});
|
|
1033
|
+
const iterType = this.checkExpr(stmt.iterable);
|
|
1034
|
+
const elemType = this.elementTypeOf(iterType);
|
|
1035
|
+
const childScope = new Scope(this.scope);
|
|
1036
|
+
if (typeof stmt.binding === "string") {
|
|
1037
|
+
childScope.define({ name: stmt.binding, type: elemType, mutable: false, span: stmt.span });
|
|
1038
|
+
}
|
|
1039
|
+
this.withScope(childScope, () => this.checkBlock(stmt.body));
|
|
1050
1040
|
}
|
|
1051
1041
|
checkWhileStmt(stmt) {
|
|
1052
|
-
this.
|
|
1053
|
-
|
|
1054
|
-
this.checkBlock(stmt.body);
|
|
1055
|
-
});
|
|
1042
|
+
this.checkExpr(stmt.condition);
|
|
1043
|
+
this.checkBlock(stmt.body);
|
|
1056
1044
|
}
|
|
1057
1045
|
checkTryCatch(stmt) {
|
|
1058
1046
|
this.checkBlock(stmt.body);
|
|
@@ -1190,35 +1178,29 @@ class TypeChecker {
|
|
|
1190
1178
|
case "LambdaExpr": return this.checkLambda(expr);
|
|
1191
1179
|
case "IfExpr": return this.checkIfExpr(expr);
|
|
1192
1180
|
case "WhenExpr": {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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));
|
|
1181
|
+
const subjectType = expr.subject ? this.checkExpr(expr.subject.expr) : null;
|
|
1182
|
+
const branchTypes = [];
|
|
1183
|
+
for (const b of expr.branches) {
|
|
1184
|
+
for (const c of b.conditions) {
|
|
1185
|
+
if (c.kind === "WhenExprCondition")
|
|
1186
|
+
this.checkExpr(c.expr);
|
|
1187
|
+
else if (c.kind === "WhenInCondition")
|
|
1188
|
+
this.checkExpr(c.expr);
|
|
1205
1189
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1212
|
-
return
|
|
1190
|
+
branchTypes.push(b.body.kind === "Block" ? (this.checkBlock(b.body), exports.T_UNIT) : this.checkExpr(b.body));
|
|
1191
|
+
}
|
|
1192
|
+
// Exhaustiveness: when-expr on a sealed class MUST be exhaustive
|
|
1193
|
+
if (subjectType) {
|
|
1194
|
+
this.checkWhenExhaustiveness(expr.span, expr.branches, subjectType);
|
|
1195
|
+
}
|
|
1196
|
+
return this.unify(branchTypes);
|
|
1213
1197
|
}
|
|
1214
1198
|
case "TryCatchExpr": {
|
|
1215
|
-
this.
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
this.checkBlock(expr.finally);
|
|
1221
|
-
});
|
|
1199
|
+
this.checkBlock(expr.body);
|
|
1200
|
+
for (const c of expr.catches)
|
|
1201
|
+
this.checkBlock(c.body);
|
|
1202
|
+
if (expr.finally)
|
|
1203
|
+
this.checkBlock(expr.finally);
|
|
1222
1204
|
return exports.T_UNKNOWN;
|
|
1223
1205
|
}
|
|
1224
1206
|
case "TypeCheckExpr": {
|
|
@@ -1257,14 +1239,14 @@ class TypeChecker {
|
|
|
1257
1239
|
}
|
|
1258
1240
|
const prevSuspend = this.inSuspend;
|
|
1259
1241
|
this.inSuspend = true;
|
|
1260
|
-
this.
|
|
1242
|
+
this.checkBlock(expr.body);
|
|
1261
1243
|
this.inSuspend = prevSuspend;
|
|
1262
1244
|
return classType("Job");
|
|
1263
1245
|
}
|
|
1264
1246
|
case "AsyncExpr": {
|
|
1265
1247
|
const prevSuspend = this.inSuspend;
|
|
1266
1248
|
this.inSuspend = true;
|
|
1267
|
-
this.
|
|
1249
|
+
this.checkBlock(expr.body);
|
|
1268
1250
|
this.inSuspend = prevSuspend;
|
|
1269
1251
|
return classType("Deferred", [exports.T_UNKNOWN]);
|
|
1270
1252
|
}
|
|
@@ -1413,27 +1395,6 @@ class TypeChecker {
|
|
|
1413
1395
|
return null;
|
|
1414
1396
|
}
|
|
1415
1397
|
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
|
-
}
|
|
1437
1398
|
const calleeType = this.checkExpr(expr.callee);
|
|
1438
1399
|
for (const arg of expr.args)
|
|
1439
1400
|
this.checkExpr(arg.value);
|
|
@@ -1478,53 +1439,6 @@ class TypeChecker {
|
|
|
1478
1439
|
this.diag.error(expr.span, diagnostics_js_1.E_NOT_A_FUNCTION, `Type '${this.typeToString(calleeType)}' is not callable`);
|
|
1479
1440
|
return exports.T_ERROR;
|
|
1480
1441
|
}
|
|
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
|
-
}
|
|
1528
1442
|
checkLambda(expr, expectedParamTypes) {
|
|
1529
1443
|
const childScope = new Scope(this.scope);
|
|
1530
1444
|
// If the lambda has no explicit params, synthesise an `it` binding.
|
|
@@ -1553,14 +1467,12 @@ class TypeChecker {
|
|
|
1553
1467
|
}
|
|
1554
1468
|
let retType = exports.T_UNIT;
|
|
1555
1469
|
this.withScope(childScope, () => {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
}
|
|
1563
|
-
});
|
|
1470
|
+
for (const stmt of expr.body) {
|
|
1471
|
+
if (stmt.kind === "ExprStmt")
|
|
1472
|
+
retType = this.checkExpr(stmt.expr);
|
|
1473
|
+
else
|
|
1474
|
+
this.checkStmt(stmt);
|
|
1475
|
+
}
|
|
1564
1476
|
});
|
|
1565
1477
|
const paramTypes = expr.params.length > 0
|
|
1566
1478
|
? expr.params.map((p) => (p.type ? this.resolveTypeRef(p.type) : exports.T_UNKNOWN))
|
|
@@ -1568,46 +1480,42 @@ class TypeChecker {
|
|
|
1568
1480
|
return { tag: "func", params: paramTypes, ret: retType, suspend: false };
|
|
1569
1481
|
}
|
|
1570
1482
|
checkIfExpr(expr) {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1483
|
+
this.checkExpr(expr.condition);
|
|
1484
|
+
// Apply smart casts in the `then` branch
|
|
1485
|
+
const narrowings = this.extractSmartCasts(expr.condition);
|
|
1486
|
+
const thenScope = new Scope(this.scope);
|
|
1487
|
+
for (const [name, type] of narrowings) {
|
|
1488
|
+
const existing = this.scope.lookup(name);
|
|
1489
|
+
if (existing)
|
|
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) {
|
|
1578
1501
|
const existing = this.scope.lookup(name);
|
|
1579
1502
|
if (existing)
|
|
1580
|
-
|
|
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));
|
|
1600
|
-
}
|
|
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);
|
|
1503
|
+
elseScope.define({ ...existing, type });
|
|
1607
1504
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1505
|
+
elseType = this.withScope(elseScope, () => expr.else.kind === "Block"
|
|
1506
|
+
? (this.checkBlock(expr.else), exports.T_UNIT)
|
|
1507
|
+
: expr.else.kind === "IfExpr"
|
|
1508
|
+
? this.checkIfExpr(expr.else)
|
|
1509
|
+
: this.checkExpr(expr.else));
|
|
1510
|
+
}
|
|
1511
|
+
else {
|
|
1512
|
+
elseType = expr.else.kind === "Block"
|
|
1513
|
+
? (this.checkBlock(expr.else), exports.T_UNIT)
|
|
1514
|
+
: expr.else.kind === "IfExpr"
|
|
1515
|
+
? this.checkIfExpr(expr.else)
|
|
1516
|
+
: this.checkExpr(expr.else);
|
|
1517
|
+
}
|
|
1518
|
+
return this.unify([thenType, elseType]);
|
|
1611
1519
|
}
|
|
1612
1520
|
// ── Type resolution ────────────────────────────────────────────────────────
|
|
1613
1521
|
lookupType(nameParts) {
|