Skip to content

Commit af71080

Browse files
authored
Merge pull request #7619 from QwikDev/v2-fix-wrap-signal-parenthesis
fix(optimizer): support wrapping type-asserted variables and logical expressions in templates
2 parents a588b48 + 24585a5 commit af71080

File tree

7 files changed

+240
-8
lines changed

7 files changed

+240
-8
lines changed

.changeset/shy-walls-shake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: reactivity for type-asserted variables in templates

.changeset/some-birds-juggle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: reactivity for logical expressions in templates

packages/qwik/src/optimizer/core/src/inlined_fn.rs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,54 @@ struct ObjectUsageChecker<'a> {
213213
used_as_object: bool,
214214
}
215215

216-
impl<'a> Visit for ObjectUsageChecker<'a> {
217-
fn visit_member_expr(&mut self, node: &ast::MemberExpr) {
218-
if let ast::Expr::Ident(obj_ident) = &*node.obj {
219-
for id in self.identifiers {
220-
if obj_ident.sym == id.0 {
216+
impl<'a> ObjectUsageChecker<'a> {
217+
// Helper function to recursively check if an expression contains one of the target identifiers.
218+
fn recursively_check_object_expr(&mut self, expr: &ast::Expr) {
219+
if self.used_as_object {
220+
return; // Already found
221+
}
222+
match expr {
223+
ast::Expr::Ident(ident) => {
224+
// Check if this identifier is one of the target identifiers
225+
if self
226+
.identifiers
227+
.iter()
228+
.any(|id| id.0 == ident.sym /* && id.1 == ident.ctxt */)
229+
{
221230
self.used_as_object = true;
222-
return;
223231
}
224232
}
233+
ast::Expr::Bin(bin_expr) => {
234+
// If it's a binary expression, specifically look for logical OR
235+
if bin_expr.op == ast::BinaryOp::LogicalOr {
236+
self.recursively_check_object_expr(&bin_expr.left);
237+
if self.used_as_object {
238+
return;
239+
}
240+
self.recursively_check_object_expr(&bin_expr.right);
241+
}
242+
}
243+
ast::Expr::Paren(paren_expr) => {
244+
// If it's a parenthesized expression, check the inner expression
245+
self.recursively_check_object_expr(&paren_expr.expr);
246+
}
247+
_ => {
248+
// For other expression types, traversal is handled by the Visit trait
249+
}
250+
}
251+
}
252+
}
253+
254+
impl<'a> Visit for ObjectUsageChecker<'a> {
255+
fn visit_member_expr(&mut self, node: &ast::MemberExpr) {
256+
if self.used_as_object {
257+
return;
258+
}
259+
260+
self.recursively_check_object_expr(&node.obj);
261+
262+
if self.used_as_object {
263+
return;
225264
}
226265
node.visit_children_with(self);
227266
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
source: packages/qwik/src/optimizer/core/src/test.rs
3+
assertion_line: 4405
4+
expression: output
5+
snapshot_kind: text
6+
---
7+
==INPUT==
8+
9+
10+
import { component$, useSignal } from '@qwik.dev/core';
11+
12+
export default component$(() => {
13+
const count = useSignal(0);
14+
const count2 = useSignal(0);
15+
return (
16+
<div>
17+
{(count || count2).value}
18+
</div>
19+
);
20+
});
21+
22+
============================= test.js ==
23+
24+
import { componentQrl } from "@qwik.dev/core";
25+
import { qrl } from "@qwik.dev/core";
26+
export default /*#__PURE__*/ componentQrl(/*#__PURE__*/ qrl(()=>import("./test.tsx_test_component_LUXeXe0DQrg"), "test_component_LUXeXe0DQrg"));
27+
28+
29+
Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;AAGE,6BAAe,mHAQZ\"}")
30+
============================= test.tsx_test_component_LUXeXe0DQrg.js (ENTRY POINT)==
31+
32+
import { _fnSignal } from "@qwik.dev/core";
33+
import { _jsxSorted } from "@qwik.dev/core";
34+
import { useSignal } from "@qwik.dev/core";
35+
export const test_component_LUXeXe0DQrg = ()=>{
36+
const count = useSignal(0);
37+
const count2 = useSignal(0);
38+
return /*#__PURE__*/ _jsxSorted("div", null, null, _fnSignal((p0, p1)=>(p0 || p1).value, [
39+
count,
40+
count2
41+
], "(p0||p1).value"), 3, "u6_0");
42+
};
43+
44+
45+
Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;0CAG4B;IACzB,MAAM,QAAQ,UAAU;IACxB,MAAM,SAAS,UAAU;IACzB,qBACC,WAAC,uCACC,CAAC,QAAe,EAAE,KAAK;;;;AAG3B\"}")
46+
/*
47+
{
48+
"origin": "test.tsx",
49+
"name": "test_component_LUXeXe0DQrg",
50+
"entry": null,
51+
"displayName": "test.tsx_test_component",
52+
"hash": "LUXeXe0DQrg",
53+
"canonicalFilename": "test.tsx_test_component_LUXeXe0DQrg",
54+
"path": "",
55+
"extension": "js",
56+
"parent": null,
57+
"ctxKind": "function",
58+
"ctxName": "component$",
59+
"captures": false,
60+
"loc": [
61+
89,
62+
233
63+
]
64+
}
65+
*/
66+
== DIAGNOSTICS ==
67+
68+
[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
source: packages/qwik/src/optimizer/core/src/test.rs
3+
assertion_line: 4360
4+
expression: output
5+
snapshot_kind: text
6+
---
7+
==INPUT==
8+
9+
10+
import { component$, useSignal } from '@qwik.dev/core';
11+
12+
export default component$(() => {
13+
const count = useSignal(0);
14+
return (
15+
<div>
16+
{(count as any).value}
17+
</div>
18+
);
19+
});
20+
21+
============================= test.js ==
22+
23+
import { componentQrl } from "@qwik.dev/core";
24+
import { qrl } from "@qwik.dev/core";
25+
export default /*#__PURE__*/ componentQrl(/*#__PURE__*/ qrl(()=>import("./test.tsx_test_component_LUXeXe0DQrg"), "test_component_LUXeXe0DQrg"));
26+
27+
28+
Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;AAGE,6BAAe,mHAOZ\"}")
29+
============================= test.tsx_test_component_LUXeXe0DQrg.js (ENTRY POINT)==
30+
31+
import { _jsxSorted } from "@qwik.dev/core";
32+
import { _wrapProp } from "@qwik.dev/core";
33+
import { useSignal } from "@qwik.dev/core";
34+
export const test_component_LUXeXe0DQrg = ()=>{
35+
const count = useSignal(0);
36+
return /*#__PURE__*/ _jsxSorted("div", null, null, _wrapProp(count), 3, "u6_0");
37+
};
38+
39+
40+
Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;0CAG4B;IACzB,MAAM,QAAQ,UAAU;IACxB,qBACC,WAAC,6BACE;AAGL\"}")
41+
/*
42+
{
43+
"origin": "test.tsx",
44+
"name": "test_component_LUXeXe0DQrg",
45+
"entry": null,
46+
"displayName": "test.tsx_test_component",
47+
"hash": "LUXeXe0DQrg",
48+
"canonicalFilename": "test.tsx_test_component_LUXeXe0DQrg",
49+
"path": "",
50+
"extension": "js",
51+
"parent": null,
52+
"ctxKind": "function",
53+
"ctxName": "component$",
54+
"captures": false,
55+
"loc": [
56+
89,
57+
198
58+
]
59+
}
60+
*/
61+
== DIAGNOSTICS ==
62+
63+
[]

packages/qwik/src/optimizer/core/src/test.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4355,6 +4355,51 @@ fn should_not_wrap_var_template_string() {
43554355
});
43564356
}
43574357

4358+
#[test]
4359+
fn should_wrap_type_asserted_variables_in_template() {
4360+
test_input!(TestInput {
4361+
code: r#"
4362+
import { component$, useSignal } from '@qwik.dev/core';
4363+
4364+
export default component$(() => {
4365+
const count = useSignal(0);
4366+
return (
4367+
<div>
4368+
{(count as any).value}
4369+
</div>
4370+
);
4371+
});
4372+
"#
4373+
.to_string(),
4374+
transpile_ts: true,
4375+
transpile_jsx: true,
4376+
..TestInput::default()
4377+
});
4378+
}
4379+
4380+
#[test]
4381+
fn should_wrap_logical_expression_in_template() {
4382+
test_input!(TestInput {
4383+
code: r#"
4384+
import { component$, useSignal } from '@qwik.dev/core';
4385+
4386+
export default component$(() => {
4387+
const count = useSignal(0);
4388+
const count2 = useSignal(0);
4389+
return (
4390+
<div>
4391+
{(count || count2).value}
4392+
</div>
4393+
);
4394+
});
4395+
"#
4396+
.to_string(),
4397+
transpile_ts: true,
4398+
transpile_jsx: true,
4399+
..TestInput::default()
4400+
});
4401+
}
4402+
43584403
// TODO(misko): Make this test work by implementing strict serialization.
43594404
// #[test]
43604405
// fn example_of_synchronous_qrl_that_cant_be_serialized() {

packages/qwik/src/optimizer/core/src/transform.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,11 +585,18 @@ impl<'a> QwikTransform<'a> {
585585

586586
// Handle `obj.prop` case
587587
if let ast::Expr::Member(member) = folded.clone() {
588-
if let ast::Expr::Ident(_) = *member.obj {
588+
let obj_expr = if let ast::Expr::Paren(paren_expr) = (*member.obj).clone() {
589+
// for example (obj as any).prop
590+
paren_expr.expr
591+
} else {
592+
member.obj
593+
};
594+
595+
if let ast::Expr::Ident(_) = *obj_expr {
589596
let prop_sym = prop_to_string(&member.prop);
590597
if let Some(prop_sym) = prop_sym {
591598
let id = self.ensure_core_import(&_WRAP_PROP);
592-
return (Some(make_wrap(&id, member.obj, prop_sym)), is_const);
599+
return (Some(make_wrap(&id, obj_expr, prop_sym)), is_const);
593600
}
594601
}
595602
}

0 commit comments

Comments
 (0)