Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: don't wrap and serialize functions that are attribute values #7284

Merged
merged 1 commit into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/six-games-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': patch
---

fix: don't wrap and serialize functions that are attribute values
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
source: packages/qwik/src/optimizer/core/src/test.rs
assertion_line: 4245
expression: output
snapshot_kind: text
---
==INPUT==


import { component$, useSignal } from "@qwik.dev/core";
import { A } from "./componentA";

export const Cmp = component$(() => {
const currentStep = useSignal('STEP_1');
const currentType = useSignal<'NEXT' | 'PREVIOUS'>('PREVIOUS');

const getStep = (step: string, type: 'NEXT' | 'PREVIOUS') => {
return step === 'STEP_1' ? 'STEP_2' : 'STEP_1';
};

return (
<>
<button onClick$={() => (currentType.value = 'NEXT')}>CLICK</button>
<A href={getStep(currentStep.value, currentType.value)} />
</>
);
});

============================= test.tsx_Cmp_component_Fragment_button_onClick_GyS1w0fRTAk.js (ENTRY POINT)==

import { useLexicalScope } from "@qwik.dev/core";
export const Cmp_component_Fragment_button_onClick_GyS1w0fRTAk = ()=>{
const [currentType] = useLexicalScope();
return currentType.value = 'NEXT';
};


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";iEAcqB;;WAAO,YAAY,KAAK,GAAG\"}")
/*
{
"origin": "test.tsx",
"name": "Cmp_component_Fragment_button_onClick_GyS1w0fRTAk",
"entry": null,
"displayName": "test.tsx_Cmp_component_Fragment_button_onClick",
"hash": "GyS1w0fRTAk",
"canonicalFilename": "test.tsx_Cmp_component_Fragment_button_onClick_GyS1w0fRTAk",
"path": "",
"extension": "js",
"parent": "Cmp_component_4ryKJTOKjWE",
"ctxKind": "eventHandler",
"ctxName": "onClick$",
"captures": true,
"loc": [
394,
428
]
}
*/
============================= test.js ==

import { componentQrl } from "@qwik.dev/core";
import { qrl } from "@qwik.dev/core";
export const Cmp = /*#__PURE__*/ componentQrl(/*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp_component_4ryKJTOKjWE"), "Cmp_component_4ryKJTOKjWE"));


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;AAIA,OAAO,MAAM,oBAAM,iHAchB\"}")
============================= test.tsx_Cmp_component_4ryKJTOKjWE.js (ENTRY POINT)==

import { A } from "./componentA";
import { Fragment as _Fragment } from "@qwik.dev/core/jsx-runtime";
import { _jsxSorted } from "@qwik.dev/core";
import { qrl } from "@qwik.dev/core";
import { useSignal } from "@qwik.dev/core";
export const Cmp_component_4ryKJTOKjWE = ()=>{
const currentStep = useSignal('STEP_1');
const currentType = useSignal('PREVIOUS');
const getStep = (step, type)=>{
return step === 'STEP_1' ? 'STEP_2' : 'STEP_1';
};
return /*#__PURE__*/ _jsxSorted(_Fragment, null, null, [
/*#__PURE__*/ _jsxSorted("button", null, {
onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp_component_Fragment_button_onClick_GyS1w0fRTAk"), "Cmp_component_Fragment_button_onClick_GyS1w0fRTAk", [
currentType
])
}, "CLICK", 3, null),
/*#__PURE__*/ _jsxSorted(A, {
href: getStep(currentStep.value, currentType.value)
}, null, null, 3, "u6_0")
], 1, "u6_1");
};


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;yCAI8B;IAC7B,MAAM,cAAc,UAAU;IAC9B,MAAM,cAAc,UAA+B;IAEnD,MAAM,UAAU,CAAC,MAAc;QAC9B,OAAO,SAAS,WAAW,WAAW;IACvC;IAEA,qBACC;sBACC,WAAC;YAAO,QAAQ;;;WAAsC;sBACtD,WAAC;YAAE,MAAM,QAAQ,YAAY,KAAK,EAAE,YAAY,KAAK;;;AAGxD\"}")
/*
{
"origin": "test.tsx",
"name": "Cmp_component_4ryKJTOKjWE",
"entry": null,
"displayName": "test.tsx_Cmp_component",
"hash": "4ryKJTOKjWE",
"canonicalFilename": "test.tsx_Cmp_component_4ryKJTOKjWE",
"path": "",
"extension": "js",
"parent": null,
"ctxKind": "function",
"ctxName": "component$",
"captures": false,
"loc": [
123,
518
]
}
*/
== DIAGNOSTICS ==

[]
29 changes: 29 additions & 0 deletions packages/qwik/src/optimizer/core/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4240,6 +4240,35 @@ export const ModelImg = component$<ModelProps>((props) => {
});
}

#[test]
fn should_not_wrap_fn() {
test_input!(TestInput {
code: r#"
import { component$, useSignal } from "@qwik.dev/core";
import { A } from "./componentA";

export const Cmp = component$(() => {
const currentStep = useSignal('STEP_1');
const currentType = useSignal<'NEXT' | 'PREVIOUS'>('PREVIOUS');

const getStep = (step: string, type: 'NEXT' | 'PREVIOUS') => {
return step === 'STEP_1' ? 'STEP_2' : 'STEP_1';
};

return (
<>
<button onClick$={() => (currentType.value = 'NEXT')}>CLICK</button>
<A href={getStep(currentStep.value, currentType.value)} />
</>
);
});
"#
.to_string(),
transpile_ts: true,
transpile_jsx: true,
..TestInput::default()
});
}
// TODO(misko): Make this test work by implementing strict serialization.
// #[test]
// fn example_of_synchronous_qrl_that_cant_be_serialized() {
Expand Down
13 changes: 7 additions & 6 deletions packages/qwik/src/optimizer/core/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,34 +553,35 @@ impl<'a> QwikTransform<'a> {

let folded = first_arg;

let mut set: HashSet<Id> = HashSet::new();
let mut contains_side_effect = false;
for ident in &descendent_idents {
if self.options.global_collect.is_global(ident) {
contains_side_effect = true;
} else if invalid_decl.iter().any(|entry| entry.0 == *ident) {
return (None, false);
} else if decl_collect.iter().any(|entry| entry.0 == *ident) {
set.insert(ident.clone());
continue;
} else {
// anything else, we can't inline
return (None, false);
}
}
let mut scoped_idents: Vec<Id> = set.into_iter().collect();

let (scoped_idents, is_const) = compute_scoped_idents(&descendent_idents, &decl_collect);

if contains_side_effect {
return (None, scoped_idents.is_empty());
}
scoped_idents.sort();

let (scoped_idents, is_const) = compute_scoped_idents(&descendent_idents, &decl_collect);

// simple variable expression, no need to inline
if let ast::Expr::Ident(_) = folded {
return (None, is_const);
}

if !is_const && matches!(folded, ast::Expr::Call(_)) {
return (None, false);
}

// Handle `obj.prop` case
if let ast::Expr::Member(member) = folded.clone() {
if let ast::Expr::Ident(_) = *member.obj {
Expand Down
Loading