@jalvin/compiler 2.0.12 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast.d.ts +8 -1
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js.map +1 -1
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +15 -0
- package/dist/codegen.js.map +1 -1
- package/dist/diagnostics.d.ts +4 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +8 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +46 -3
- package/dist/parser.js.map +1 -1
- package/dist/typechecker.d.ts +2 -0
- package/dist/typechecker.d.ts.map +1 -1
- package/dist/typechecker.js +118 -22
- package/dist/typechecker.js.map +1 -1
- package/package.json +1 -1
package/dist/typechecker.js
CHANGED
|
@@ -73,12 +73,14 @@ const JS_GLOBALS = new Set([
|
|
|
73
73
|
"fetch", "URL", "URLSearchParams", "FormData", "Blob", "File",
|
|
74
74
|
"localStorage", "sessionStorage", "performance", "navigator", "location",
|
|
75
75
|
"HTMLElement", "Element", "Event", "CustomEvent",
|
|
76
|
+
"confirm", "alert", "prompt",
|
|
76
77
|
]);
|
|
77
78
|
function nullable(t) {
|
|
78
79
|
if (t.tag === "nullable")
|
|
79
80
|
return t;
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
// Note: Nothing? is kept as nullable(nothing), not collapsed to Unit.
|
|
82
|
+
// In Kotlin semantics Nothing? is the type of `null` and is assignable
|
|
83
|
+
// to every nullable type, which isAssignable() handles correctly.
|
|
82
84
|
return { tag: "nullable", inner: t };
|
|
83
85
|
}
|
|
84
86
|
function unwrapNullable(t) {
|
|
@@ -548,6 +550,7 @@ class TypeChecker {
|
|
|
548
550
|
}
|
|
549
551
|
if (!decl.body)
|
|
550
552
|
return;
|
|
553
|
+
const body = decl.body;
|
|
551
554
|
const seen = new Set();
|
|
552
555
|
// Collect all member names from parent class (for override/abstract checks)
|
|
553
556
|
const parentMemberNames = new Set();
|
|
@@ -568,26 +571,75 @@ class TypeChecker {
|
|
|
568
571
|
}
|
|
569
572
|
}
|
|
570
573
|
const concreteMembers = new Set();
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
574
|
+
// Build a class-level scope so that methods can call sibling members and
|
|
575
|
+
// reference `this` by bare name — mirroring Kotlin's behaviour.
|
|
576
|
+
const classScope = new Scope(this.scope);
|
|
577
|
+
classScope.define({
|
|
578
|
+
name: "this",
|
|
579
|
+
type: classType(decl.name, [], decl),
|
|
580
|
+
mutable: false,
|
|
581
|
+
span: decl.span,
|
|
582
|
+
});
|
|
583
|
+
// Hoist primary-constructor val/var params as class properties
|
|
584
|
+
const ctor = decl.primaryConstructor
|
|
585
|
+
?? decl.primaryConstructor
|
|
586
|
+
?? null;
|
|
587
|
+
if (ctor) {
|
|
588
|
+
for (const p of ctor.params) {
|
|
589
|
+
if (p.propertyKind) {
|
|
590
|
+
classScope.define({
|
|
591
|
+
name: p.name,
|
|
592
|
+
type: this.resolveTypeRef(p.type),
|
|
593
|
+
mutable: p.propertyKind === "var",
|
|
594
|
+
span: p.span,
|
|
595
|
+
});
|
|
576
596
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// Hoist all class body members so they're visible to each other
|
|
600
|
+
for (const member of body.members) {
|
|
601
|
+
if (member.kind === "FunDecl") {
|
|
602
|
+
classScope.define({ name: member.name, type: this.funDeclType(member), mutable: false, span: member.span });
|
|
603
|
+
}
|
|
604
|
+
else if (member.kind === "PropertyDecl") {
|
|
605
|
+
classScope.define({
|
|
606
|
+
name: member.name,
|
|
607
|
+
type: member.type ? this.resolveTypeRef(member.type) : exports.T_UNKNOWN,
|
|
608
|
+
mutable: member.mutable,
|
|
609
|
+
span: member.span,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
else if (member.kind === "ComponentDecl") {
|
|
613
|
+
classScope.define({
|
|
614
|
+
name: member.name,
|
|
615
|
+
type: { tag: "func", params: this.componentParamTypes(member), ret: exports.T_UNIT, suspend: false },
|
|
616
|
+
mutable: false,
|
|
617
|
+
span: member.span,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
this.withScope(classScope, () => {
|
|
622
|
+
for (const member of body.members) {
|
|
623
|
+
const mName = this.memberName(member);
|
|
624
|
+
if (mName) {
|
|
625
|
+
if (seen.has(mName)) {
|
|
626
|
+
this.diag.error(member.span, diagnostics_js_1.E_DUPLICATE_CLASS_MEMBER, `Duplicate member '${mName}'`);
|
|
627
|
+
}
|
|
628
|
+
seen.add(mName);
|
|
629
|
+
// Check `override` is actually overriding something
|
|
630
|
+
if ((member.kind === "FunDecl" || member.kind === "PropertyDecl") &&
|
|
631
|
+
member.modifiers.modifiers.includes("override")) {
|
|
632
|
+
if (!parentMemberNames.has(mName)) {
|
|
633
|
+
this.diag.error(member.span, diagnostics_js_1.E_OVERRIDE_NOTHING, `'${mName}' overrides nothing`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (!(member.kind === "FunDecl" && member.modifiers.modifiers.includes("abstract"))) {
|
|
637
|
+
concreteMembers.add(mName);
|
|
583
638
|
}
|
|
584
639
|
}
|
|
585
|
-
|
|
586
|
-
concreteMembers.add(mName);
|
|
587
|
-
}
|
|
640
|
+
this.checkClassMember(member);
|
|
588
641
|
}
|
|
589
|
-
|
|
590
|
-
}
|
|
642
|
+
});
|
|
591
643
|
// Check all abstract parent members are implemented (only for non-abstract, non-sealed subclasses)
|
|
592
644
|
const isAbstract = decl.modifiers.modifiers.includes("abstract");
|
|
593
645
|
const isSealed = decl.kind === "SealedClassDecl";
|
|
@@ -791,6 +843,7 @@ class TypeChecker {
|
|
|
791
843
|
* Extract smart-cast bindings from an `is`-check condition.
|
|
792
844
|
* `x is T` → { x: T }
|
|
793
845
|
* `x is T && y is U` → { x: T, y: U }
|
|
846
|
+
* `x != null` → { x: unwrapped(x) }
|
|
794
847
|
*/
|
|
795
848
|
extractSmartCasts(condition) {
|
|
796
849
|
const result = new Map();
|
|
@@ -799,6 +852,15 @@ class TypeChecker {
|
|
|
799
852
|
result.set(condition.expr.name, this.resolveTypeRef(condition.type));
|
|
800
853
|
}
|
|
801
854
|
}
|
|
855
|
+
else if (condition.kind === "BinaryExpr" &&
|
|
856
|
+
(condition.op === "!=" || condition.op === "!==") &&
|
|
857
|
+
condition.right.kind === "NullLiteralExpr" &&
|
|
858
|
+
condition.left.kind === "NameExpr") {
|
|
859
|
+
// x != null → narrow x to its non-nullable type inside then-branch
|
|
860
|
+
const varType = this.scope.lookup(condition.left.name)?.type;
|
|
861
|
+
if (varType)
|
|
862
|
+
result.set(condition.left.name, unwrapNullable(varType));
|
|
863
|
+
}
|
|
802
864
|
else if (condition.kind === "BinaryExpr" && condition.op === "&&") {
|
|
803
865
|
for (const [k, v] of this.extractSmartCasts(condition.left))
|
|
804
866
|
result.set(k, v);
|
|
@@ -811,6 +873,7 @@ class TypeChecker {
|
|
|
811
873
|
* Extract smart-cast bindings for the ELSE branch.
|
|
812
874
|
* `x !is T` → in else, `x` IS `T` → { x: T }
|
|
813
875
|
* `x !is T || y !is U` → in else (both negations must fail), { x: T, y: U }
|
|
876
|
+
* `x == null` → in else, x is non-null → { x: unwrapped(x) }
|
|
814
877
|
*/
|
|
815
878
|
extractElseSmartCasts(condition) {
|
|
816
879
|
const result = new Map();
|
|
@@ -820,6 +883,15 @@ class TypeChecker {
|
|
|
820
883
|
result.set(condition.expr.name, this.resolveTypeRef(condition.type));
|
|
821
884
|
}
|
|
822
885
|
}
|
|
886
|
+
else if (condition.kind === "BinaryExpr" &&
|
|
887
|
+
(condition.op === "==" || condition.op === "===") &&
|
|
888
|
+
condition.right.kind === "NullLiteralExpr" &&
|
|
889
|
+
condition.left.kind === "NameExpr") {
|
|
890
|
+
// x == null → in else branch, x is non-null
|
|
891
|
+
const varType = this.scope.lookup(condition.left.name)?.type;
|
|
892
|
+
if (varType)
|
|
893
|
+
result.set(condition.left.name, unwrapNullable(varType));
|
|
894
|
+
}
|
|
823
895
|
else if (condition.kind === "BinaryExpr" && condition.op === "||") {
|
|
824
896
|
// x !is T || y !is U → both must be false to reach else, so both narrow
|
|
825
897
|
for (const [k, v] of this.extractElseSmartCasts(condition.left))
|
|
@@ -1003,6 +1075,9 @@ class TypeChecker {
|
|
|
1003
1075
|
// Don't error on Bibi — it's a special runtime symbol
|
|
1004
1076
|
if (expr.name === "Bibi")
|
|
1005
1077
|
return { tag: "func", params: [exports.T_STRING], ret: classType("BibiClient"), suspend: false };
|
|
1078
|
+
// Unit singleton — usable as a value (e.g. LaunchedEffect(Unit))
|
|
1079
|
+
if (expr.name === "Unit")
|
|
1080
|
+
return classType("Unit");
|
|
1006
1081
|
// Companion-like type objects (for Int.MAX_VALUE, Long.MIN_VALUE)
|
|
1007
1082
|
if (expr.name === "Int")
|
|
1008
1083
|
return classType("IntCompanion");
|
|
@@ -1079,6 +1154,20 @@ class TypeChecker {
|
|
|
1079
1154
|
return exports.T_UNKNOWN;
|
|
1080
1155
|
}
|
|
1081
1156
|
case "CallExpr": return this.checkCallExpr(expr);
|
|
1157
|
+
case "SafeCallExpr": {
|
|
1158
|
+
const calleeType = this.checkExpr(expr.callee);
|
|
1159
|
+
for (const arg of expr.args)
|
|
1160
|
+
this.checkExpr(arg.value);
|
|
1161
|
+
if (expr.trailingLambda)
|
|
1162
|
+
this.checkLambda(expr.trailingLambda);
|
|
1163
|
+
const inner = unwrapNullable(calleeType);
|
|
1164
|
+
if (inner.tag === "func")
|
|
1165
|
+
return nullable(inner.ret);
|
|
1166
|
+
if (inner.tag === "any" || inner.tag === "unknown" || inner.tag === "error")
|
|
1167
|
+
return exports.T_UNKNOWN;
|
|
1168
|
+
this.diag.error(expr.span, diagnostics_js_1.E_NOT_A_FUNCTION, `Type '${this.typeToString(calleeType)}' is not callable`);
|
|
1169
|
+
return exports.T_ERROR;
|
|
1170
|
+
}
|
|
1082
1171
|
case "LambdaExpr": return this.checkLambda(expr);
|
|
1083
1172
|
case "IfExpr": return this.checkIfExpr(expr);
|
|
1084
1173
|
case "WhenExpr": {
|
|
@@ -1345,12 +1434,16 @@ class TypeChecker {
|
|
|
1345
1434
|
}
|
|
1346
1435
|
checkLambda(expr, expectedParamTypes) {
|
|
1347
1436
|
const childScope = new Scope(this.scope);
|
|
1348
|
-
// If the lambda has no explicit params, synthesise an `it` binding
|
|
1349
|
-
// the
|
|
1350
|
-
|
|
1437
|
+
// If the lambda has no explicit params, synthesise an `it` binding.
|
|
1438
|
+
// Use the expected parameter type when available, otherwise T_UNKNOWN.
|
|
1439
|
+
// This ensures `it` is always resolvable in single-param lambdas.
|
|
1440
|
+
if (expr.params.length === 0) {
|
|
1441
|
+
const itType = (expectedParamTypes && expectedParamTypes.length === 1)
|
|
1442
|
+
? expectedParamTypes[0]
|
|
1443
|
+
: exports.T_UNKNOWN;
|
|
1351
1444
|
childScope.define({
|
|
1352
1445
|
name: "it",
|
|
1353
|
-
type:
|
|
1446
|
+
type: itType,
|
|
1354
1447
|
mutable: false,
|
|
1355
1448
|
span: expr.span,
|
|
1356
1449
|
});
|
|
@@ -2165,6 +2258,9 @@ class TypeChecker {
|
|
|
2165
2258
|
return true;
|
|
2166
2259
|
if (from.tag === "unknown" || to.tag === "unknown")
|
|
2167
2260
|
return true;
|
|
2261
|
+
// nullable(Nothing) is the type of the `null` literal — assignable to every nullable type
|
|
2262
|
+
if (from.tag === "nullable" && from.inner.tag === "nothing")
|
|
2263
|
+
return to.tag === "nullable";
|
|
2168
2264
|
if (to.tag === "nullable")
|
|
2169
2265
|
return this.isAssignable(from, to.inner) || from.tag === "nullable";
|
|
2170
2266
|
if (from.tag === "nullable")
|