Skip to content

Commit ff1776f

Browse files
committed
fix: add better nullable check
1 parent dc748b4 commit ff1776f

File tree

5 files changed

+90
-5
lines changed

5 files changed

+90
-5
lines changed

.changeset/fair-spoons-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@marko/runtime-tags": patch
3+
---
4+
5+
Add better nullable check to node evaluation.

packages/runtime-tags/src/__tests__/fixtures/error-const-mutation/__snapshots__/dom.expected/template.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ const $user_middleName = /* @__PURE__ */_$.value("user_middleName", $expr_user_f
1616
const $user_firstName = /* @__PURE__ */_$.value("user_firstName", $expr_user_fullName_user_firstName_user_middleName_user_lastName);
1717
const $user_fullName = /* @__PURE__ */_$.value("user_fullName", $expr_user_fullName_user_firstName_user_middleName_user_lastName);
1818
const $user = /* @__PURE__ */_$.value("user", ($scope, user) => {
19-
$user_fullName($scope, user?.fullName);
20-
$user_firstName($scope, user?.firstName);
21-
$user_middleName($scope, user?.middleName);
22-
$user_lastName($scope, user?.lastName);
19+
$user_fullName($scope, user.fullName);
20+
$user_firstName($scope, user.firstName);
21+
$user_middleName($scope, user.middleName);
22+
$user_lastName($scope, user.lastName);
2323
});
2424
export function $setup($scope) {
2525
$user($scope, {

packages/runtime-tags/src/translator/core/const.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default {
5050
);
5151
}
5252

53-
const valueExtra = evaluate(valueAttr.value); // TODO could perform a more extensive "nullable" check.
53+
const valueExtra = evaluate(valueAttr.value);
5454
const upstreamAlias = t.isIdentifier(valueAttr.value)
5555
? tag.scope.getBinding(valueAttr.value.name)?.identifier.extra?.binding
5656
: undefined;
@@ -62,6 +62,7 @@ export default {
6262
const binding = trackVarReferences(tag, BindingType.derived, upstreamAlias);
6363

6464
if (binding) {
65+
if (!valueExtra.nullable) binding.nullable = false;
6566
setBindingValueExpr(binding, valueExtra);
6667
}
6768
},

packages/runtime-tags/src/translator/util/evaluate.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ declare module "@marko/compiler/dist/types" {
55
export interface NodeExtra {
66
confident?: boolean;
77
computed?: unknown;
8+
nullable?: boolean;
89
}
910
}
1011

@@ -20,14 +21,90 @@ export default function evaluate<T extends t.Expression>(value: T) {
2021
if (computed) {
2122
extra.computed = computed.value;
2223
extra.confident = true;
24+
extra.nullable = computed.value == null;
2325
} else {
2426
extra.computed = undefined;
2527
extra.confident = false;
28+
extra.nullable = isNullableExpr(value);
2629
}
2730
}
2831

2932
return extra as T["extra"] & {
3033
confident: boolean;
34+
nullable: boolean;
3135
computed: unknown;
3236
};
3337
}
38+
39+
function isNullableExpr(expr: t.Expression): boolean {
40+
switch (expr.type) {
41+
case "ArrayExpression":
42+
case "ArrowFunctionExpression":
43+
case "BigIntLiteral":
44+
case "BinaryExpression":
45+
case "BooleanLiteral":
46+
case "ClassExpression":
47+
case "FunctionExpression":
48+
case "NewExpression":
49+
case "NumericLiteral":
50+
case "ObjectExpression":
51+
case "RegExpLiteral":
52+
case "StringLiteral":
53+
case "TemplateLiteral":
54+
case "UpdateExpression":
55+
return false;
56+
case "AssignmentExpression":
57+
switch (expr.operator) {
58+
case "=":
59+
return isNullableExpr(expr.right);
60+
case "*=":
61+
case "/=":
62+
case "%=":
63+
case "+=":
64+
case "-=":
65+
case "<<=":
66+
case ">>=":
67+
case ">>>=":
68+
case "&=":
69+
case "^=":
70+
case "|=":
71+
case "**=":
72+
return false;
73+
case "||=":
74+
case "??=":
75+
return (
76+
isNullableExpr(expr.right) ||
77+
isNullableExpr(expr.left as t.Expression)
78+
);
79+
case "&&=":
80+
return (
81+
isNullableExpr(expr.left as t.Expression) &&
82+
isNullableExpr(expr.right)
83+
);
84+
default:
85+
return true;
86+
}
87+
case "AwaitExpression":
88+
return isNullableExpr(expr.argument);
89+
case "ConditionalExpression":
90+
return isNullableExpr(expr.consequent) && isNullableExpr(expr.alternate);
91+
case "LogicalExpression":
92+
switch (expr.operator) {
93+
case "||":
94+
case "??":
95+
return isNullableExpr(expr.right) || isNullableExpr(expr.left);
96+
case "&&":
97+
return isNullableExpr(expr.left) && isNullableExpr(expr.right);
98+
default:
99+
return true;
100+
}
101+
case "ParenthesizedExpression":
102+
return isNullableExpr(expr.expression);
103+
case "SequenceExpression":
104+
return isNullableExpr(expr.expressions[expr.expressions.length - 1]);
105+
case "UnaryExpression":
106+
return expr.operator === "void";
107+
default:
108+
return true;
109+
}
110+
}

packages/runtime-tags/src/translator/visitors/program/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ export default {
9595
if (value) {
9696
programExtra.returnValueExpr = value.extra ??= {};
9797
}
98+
99+
break;
98100
}
99101
}
100102
},

0 commit comments

Comments
 (0)