From 1d62cd929204e171ea85a6af927533490c3908d1 Mon Sep 17 00:00:00 2001 From: Varixo Date: Wed, 22 Jul 2026 17:49:44 +0200 Subject: [PATCH] fix: track props initialized with function call defaults --- .changeset/tidy-defaults-track.md | 5 + .../optimizer/core/src/props_destructuring.rs | 107 ++++++++++++++++-- ...ore__test__example_props_optimization.snap | 65 ++++++++--- packages/optimizer/core/src/test.rs | 14 ++- 4 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 .changeset/tidy-defaults-track.md diff --git a/.changeset/tidy-defaults-track.md b/.changeset/tidy-defaults-track.md new file mode 100644 index 00000000000..1c1eaf91f39 --- /dev/null +++ b/.changeset/tidy-defaults-track.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/optimizer': patch +--- + +fix: track props initialized with function call defaults diff --git a/packages/optimizer/core/src/props_destructuring.rs b/packages/optimizer/core/src/props_destructuring.rs index 71967264cb9..4ae51e1ca6a 100644 --- a/packages/optimizer/core/src/props_destructuring.rs +++ b/packages/optimizer/core/src/props_destructuring.rs @@ -8,7 +8,7 @@ use swc_atoms::Atom; use swc_common::DUMMY_SP; use swc_ecmascript::ast; use swc_ecmascript::utils::private_ident; -use swc_ecmascript::visit::{VisitMut, VisitMutWith}; +use swc_ecmascript::visit::{Visit, VisitMut, VisitMutWith, VisitWith}; struct PropsDestructuring<'a> { component_ident: Option, @@ -82,8 +82,8 @@ impl<'a> PropsDestructuring<'a> { } let new_ident = private_ident!("_rawProps"); - if let Some((rest_id_opt, local)) = - transform_pat(ast::Expr::Ident(new_ident.clone()), obj, self) + if let Some((rest_id_opt, local, default_stmts)) = + transform_pat(ast::Expr::Ident(new_ident.clone()), obj, self, true) { if let Some(rest_id) = rest_id_opt { let omit_fn = self.global_collect.import(&_REST_PROPS, self.core_module); @@ -100,6 +100,7 @@ impl<'a> PropsDestructuring<'a> { self.identifiers.insert(id, expr); } arrow.params[0] = ast::Pat::Ident(ast::BindingIdent::from(new_ident)); + prepend_stmts(arrow, default_stmts); } } if let ast::BlockStmtOrExpr::BlockStmt(body) = &mut *arrow.body { @@ -240,8 +241,8 @@ impl<'a> PropsDestructuring<'a> { } } ast::Pat::Object(obj_pat) => { - if let Some((rest_id, local)) = - transform_pat(new_ref.clone(), obj_pat, self) + if let Some((rest_id, local, _)) = + transform_pat(new_ref.clone(), obj_pat, self, false) { if let Some(rest_id) = rest_id { let omit_fn = self @@ -377,15 +378,18 @@ impl<'a> VisitMut for PropsDestructuring<'a> { } } -type TransformPatReturn = (Option, Vec<(Id, Atom, ast::Expr)>); +type TransformPatReturn = (Option, Vec<(Id, Atom, ast::Expr)>, Vec); fn transform_pat( new_ident: ast::Expr, obj: &ast::ObjectPat, props_transform: &mut PropsDestructuring, + allow_dynamic_defaults: bool, ) -> Option { let mut local = vec![]; + let mut default_stmts = vec![]; let mut skip = false; let mut rest_id = None; + let destructured_names = collect_destructured_names(obj); for prop in &obj.props { match prop { ast::ObjectPatProp::Assign(ref v) => { @@ -406,6 +410,13 @@ fn transform_pat( right: value.clone(), }), )); + } else if allow_dynamic_defaults + && !references_destructured_name(value, &destructured_names) + { + let (default_expr, default_stmt) = + create_dynamic_default(access, value.clone()); + local.push((id!(v.key), v.key.sym.clone(), default_expr)); + default_stmts.push(default_stmt); } else { skip = true; } @@ -469,6 +480,18 @@ fn transform_pat( right: value.clone(), }), )); + } else if allow_dynamic_defaults + && !references_destructured_name(value, &destructured_names) + { + let access = ast::Expr::Member(ast::MemberExpr { + obj: Box::new(new_ident.clone()), + prop, + span: DUMMY_SP, + }); + let (default_expr, default_stmt) = + create_dynamic_default(access, value.clone()); + local.push((id!(ident.id), key_atom.clone(), default_expr)); + default_stmts.push(default_stmt); } else { skip = true; } @@ -494,7 +517,65 @@ fn transform_pat( return None; } // Allow case with only rest binding (no local fields) - Some((rest_id, local)) + Some((rest_id, local, default_stmts)) +} + +fn create_dynamic_default(access: ast::Expr, value: Box) -> (ast::Expr, ast::Stmt) { + let default_ident = private_ident!("_defaultValue"); + let default_stmt = ast::Stmt::Decl(ast::Decl::Var(Box::new(ast::VarDecl { + kind: ast::VarDeclKind::Const, + decls: vec![ast::VarDeclarator { + definite: false, + span: DUMMY_SP, + init: Some(Box::new(ast::Expr::Cond(ast::CondExpr { + span: DUMMY_SP, + test: Box::new(is_undefined(access.clone())), + cons: value, + alt: ast::Expr::undefined(DUMMY_SP), + }))), + name: ast::Pat::Ident(ast::BindingIdent::from(default_ident.clone())), + }], + ..Default::default() + }))); + let default_expr = ast::Expr::Cond(ast::CondExpr { + span: DUMMY_SP, + test: Box::new(is_undefined(access.clone())), + cons: Box::new(ast::Expr::Ident(default_ident)), + alt: Box::new(access), + }); + + (default_expr, default_stmt) +} + +fn is_undefined(expr: ast::Expr) -> ast::Expr { + ast::Expr::Bin(ast::BinExpr { + span: DUMMY_SP, + op: ast::BinaryOp::EqEqEq, + left: Box::new(expr), + right: ast::Expr::undefined(DUMMY_SP), + }) +} + +fn references_destructured_name(expr: &ast::Expr, names: &HashSet) -> bool { + struct Finder<'a> { + names: &'a HashSet, + found: bool, + } + + impl Visit for Finder<'_> { + fn visit_ident(&mut self, ident: &ast::Ident) { + if self.names.contains(&id!(ident)) { + self.found = true; + } + } + } + + let mut finder = Finder { + names, + found: false, + }; + expr.visit_with(&mut finder); + finder.found } fn transform_rest( @@ -528,13 +609,21 @@ fn transform_rest( } else { create_omit_props(omit_fn, rest_id, props_expr, omit) }; + prepend_stmts(arrow, vec![new_stmt]); +} + +fn prepend_stmts(arrow: &mut ast::ArrowExpr, mut stmts: Vec) { + if stmts.is_empty() { + return; + } match &mut arrow.body { box ast::BlockStmtOrExpr::BlockStmt(block) => { - block.stmts.insert(0, new_stmt); + block.stmts.splice(0..0, stmts); } box ast::BlockStmtOrExpr::Expr(ref expr) => { + stmts.push(create_return_stmt(expr.clone())); arrow.body = Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { - stmts: vec![new_stmt, create_return_stmt(expr.clone())], + stmts, ..Default::default() })); } diff --git a/packages/optimizer/core/src/snapshots/qwik_core__test__example_props_optimization.snap b/packages/optimizer/core/src/snapshots/qwik_core__test__example_props_optimization.snap index df660f6c882..f632cf01351 100644 --- a/packages/optimizer/core/src/snapshots/qwik_core__test__example_props_optimization.snap +++ b/packages/optimizer/core/src/snapshots/qwik_core__test__example_props_optimization.snap @@ -8,6 +8,7 @@ expression: output import { $, component$, useTask$ } from '@qwik.dev/core'; import { CONST } from 'const'; +const getValue = () => 0; export const Works = component$(({ count, some = 1+2, @@ -36,17 +37,22 @@ export const NoWorks2 = component$(({count, stuff: {hey}}) => { ); }); -export const NoWorks3 = component$(({count, stuff = hola()}) => { +export const DynamicDefaults = component$(({count, stuff = getValue(), other: value = getValue()}) => { console.log(stuff); useTask$(({track}) => { - track(() => count); - console.log(count); + track(() => stuff); + track(() => value); + console.log(count, stuff, value); }); return ( -
{count}
+
{value}
); }); +export const ReferencedDefault = component$(({first = getValue(second), second}) => ( +
{first}{second}
+)); + ============================= test.js == import { _restProps } from "@qwik.dev/core"; @@ -67,11 +73,16 @@ const _hf1 = (p0)=>({ some: p0.some ?? 3 }); const _hf1_str = "{some:p0.some??1+2}"; +const _hf2 = (p0, p1)=>p1.stuff === void 0 ? p0 : p1.stuff; +const _hf2_str = "p1.stuff===void 0?p0:p1.stuff"; +const _hf3 = (p0, p1)=>p1.other === void 0 ? p0 : p1.other; +const _hf3_str = "p1.other===void 0?p0:p1.other"; // +const q_DynamicDefaults_component_nZOHO1uiqf4 = /*#__PURE__*/ _noopQrl("DynamicDefaults_component_nZOHO1uiqf4"); +const q_DynamicDefaults_component_useTask_Od2wjq7X07c = /*#__PURE__*/ _noopQrl("DynamicDefaults_component_useTask_Od2wjq7X07c"); const q_NoWorks2_component_JPD9t2HyEKg = /*#__PURE__*/ _noopQrl("NoWorks2_component_JPD9t2HyEKg"); const q_NoWorks2_component_useTask_lXiqwbxxjq0 = /*#__PURE__*/ _noopQrl("NoWorks2_component_useTask_lXiqwbxxjq0"); -const q_NoWorks3_component_fc13h5yYn14 = /*#__PURE__*/ _noopQrl("NoWorks3_component_fc13h5yYn14"); -const q_NoWorks3_component_useTask_3cQGU0s1VwU = /*#__PURE__*/ _noopQrl("NoWorks3_component_useTask_3cQGU0s1VwU"); +const q_ReferencedDefault_component_xTfXU3xq5Ow = /*#__PURE__*/ _noopQrl("ReferencedDefault_component_xTfXU3xq5Ow"); const q_Works_component_t45qL4vNGv0 = /*#__PURE__*/ _noopQrl("Works_component_t45qL4vNGv0"); const q_Works_component_useTask_pjo5U5Ikll0 = /*#__PURE__*/ _noopQrl("Works_component_useTask_pjo5U5Ikll0"); // @@ -121,26 +132,44 @@ q_NoWorks2_component_JPD9t2HyEKg.s(({ count, stuff: { hey } })=>{ class: count }, null, count, 1, "u6_1"); }); -q_NoWorks3_component_useTask_3cQGU0s1VwU.s(({ track })=>{ - const count = _captures[0]; - track(()=>count); - console.log(count); +q_DynamicDefaults_component_useTask_Od2wjq7X07c.s(({ track })=>{ + const _defaultValue = _captures[0], _defaultValue1 = _captures[1], _rawProps = _captures[2]; + track(()=>_rawProps.stuff === void 0 ? _defaultValue : _rawProps.stuff); + track(()=>_rawProps.other === void 0 ? _defaultValue1 : _rawProps.other); + console.log(_rawProps.count, _rawProps.stuff === void 0 ? _defaultValue : _rawProps.stuff, _rawProps.other === void 0 ? _defaultValue1 : _rawProps.other); }); -q_NoWorks3_component_fc13h5yYn14.s(({ count, stuff = hola() })=>{ - console.log(stuff); - useTaskQrl(q_NoWorks3_component_useTask_3cQGU0s1VwU.w([ - count +const getValue = ()=>0; +q_DynamicDefaults_component_nZOHO1uiqf4.s((_rawProps)=>{ + const _defaultValue = _rawProps.stuff === void 0 ? getValue() : void 0; + const _defaultValue1 = _rawProps.other === void 0 ? getValue() : void 0; + console.log(_rawProps.stuff === void 0 ? _defaultValue : _rawProps.stuff); + useTaskQrl(q_DynamicDefaults_component_useTask_Od2wjq7X07c.w([ + _defaultValue, + _defaultValue1, + _rawProps ])); return /*#__PURE__*/ _jsxSorted("div", { - class: count - }, null, count, 1, "u6_2"); + class: _fnSignal(_hf2, [ + _defaultValue, + _rawProps + ], _hf2_str) + }, null, _fnSignal(_hf3, [ + _defaultValue1, + _rawProps + ], _hf3_str), 1, "u6_2"); }); +q_ReferencedDefault_component_xTfXU3xq5Ow.s(({ first = getValue(second), second })=>/*#__PURE__*/ _jsxSorted("div", null, null, [ + first, + second + ], 1, "u6_3")); export const Works = /*#__PURE__*/ componentQrl(q_Works_component_t45qL4vNGv0); export const NoWorks2 = /*#__PURE__*/ componentQrl(q_NoWorks2_component_JPD9t2HyEKg); -export const NoWorks3 = /*#__PURE__*/ componentQrl(q_NoWorks3_component_fc13h5yYn14); +export const DynamicDefaults = /*#__PURE__*/ componentQrl(q_DynamicDefaults_component_nZOHO1uiqf4); +export const ReferencedDefault = /*#__PURE__*/ componentQrl(q_ReferencedDefault_component_xTfXU3xq5Ow); +export { getValue as _auto_getValue }; -Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;;;;;;;;sBAKC,QAAO;;mBAWmB,CAAA;QAAE,IAAI,KAXhC,QAAO;IAW0B,CAAA;;;;;;;;;;wCALxB,CAAC,EAAC,KAAK,EAAC;;IAChB,MAAM,cARP;IASC,QAAQ,GAAG,WATZ,OASoB,gBANpB,iBAFA,QAAO,aAGP,gBAAqB;;gCALU;;;;;;;;IAO/B,QAAQ,GAAG,WAHX,iBAFA,QAAO;IAMP;;;;IAIA,qBACC,UAAC;QAAI,IAAI;;;QAAQ,MAAM;;;QAAY,KAAK;wBAAa;;0BAAA;QAAM,QAAQ;;AAErE;2CAIU,CAAC,EAAC,KAAK,EAAC;;IAChB,MAAM,IAAM;IACZ,QAAQ,GAAG,CAAC;;mCAJqB,CAAC,EAAC,KAAK,EAAE,OAAO,EAAC,GAAG,EAAC,EAAC;IACxD,QAAQ,GAAG,CAAC;IACZ;;;IAIA,qBACC,WAAC;QAAI,OAAO;aAAQ;AAEtB;2CAIU,CAAC,EAAC,KAAK,EAAC;;IAChB,MAAM,IAAM;IACZ,QAAQ,GAAG,CAAC;;mCAJqB,CAAC,EAAC,KAAK,EAAE,QAAQ,MAAM,EAAC;IAC1D,QAAQ,GAAG,CAAC;IACZ;;;IAIA,qBACC,WAAC;QAAI,OAAO;aAAQ;AAEtB;AArCA,OAAO,MAAM,sBAAQ,4CAelB;AAEH,OAAO,MAAM,yBAAW,+CASrB;AAEH,OAAO,MAAM,yBAAW,+CASrB\"}") +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;;;;;;;;sBAMC,QAAO;;mBAWmB,CAAA;QAAE,IAAI,KAXhC,QAAO;IAW0B,CAAA;;0BAeiB,2BAAA;;0BAAoB,2BAAA;;;;;;;;;;;wCApB7D,CAAC,EAAC,KAAK,EAAC;;IAChB,MAAM,cARP;IASC,QAAQ,GAAG,WATZ,OASoB,gBANpB,iBAFA,QAAO,aAGP,gBAAqB;;gCALU;;;;;;;;IAO/B,QAAQ,GAAG,WAHX,iBAFA,QAAO;IAMP;;;;IAIA,qBACC,UAAC;QAAI,IAAI;;;QAAQ,MAAM;;;QAAY,KAAK;wBAAa;;0BAAA;QAAM,QAAQ;;AAErE;2CAIU,CAAC,EAAC,KAAK,EAAC;;IAChB,MAAM,IAAM;IACZ,QAAQ,GAAG,CAAC;;mCAJqB,CAAC,EAAC,KAAK,EAAE,OAAO,EAAC,GAAG,EAAC,EAAC;IACxD,QAAQ,GAAG,CAAC;IACZ;;;IAIA,qBACC,WAAC;QAAI,OAAO;aAAQ;AAEtB;kDAIU,CAAC,EAAC,KAAK,EAAC;;IAChB,MAAM,cAH2C,6CAAA;IAIjD,MAAM,cAJ+D,8CAAA;IAKrE,QAAQ,GAAG,WAL+B,iBAAO,6CAAA,iBAAoB,8CAAA;;AA7BvE,MAAM,WAAW,IAAM;0CA6BmB;oCAAS,mBAAQ;qCAAY,mBAAe;IACrF,QAAQ,GAAG,WADuC,6CAAA;IAElD;;;;;IAKA,qBACC,WAAC;QAAI,KAAK;;;;;;;;AAEZ;4CAE4C,CAAC,EAAC,QAAQ,SAAS,OAAO,EAAE,MAAM,EAAC,iBAC9E,WAAC;QAAK;QAAO;;AAzCd,OAAO,MAAM,sBAAQ,4CAelB;AAEH,OAAO,MAAM,yBAAW,+CASrB;AAEH,OAAO,MAAM,gCAAkB,sDAU5B;AAEH,OAAO,MAAM,kCAAoB,wDAE9B\"}") == DIAGNOSTICS == [] diff --git a/packages/optimizer/core/src/test.rs b/packages/optimizer/core/src/test.rs index 31fe6227eaf..9c0869f1793 100644 --- a/packages/optimizer/core/src/test.rs +++ b/packages/optimizer/core/src/test.rs @@ -517,6 +517,7 @@ fn example_props_optimization() { code: r#" import { $, component$, useTask$ } from '@qwik.dev/core'; import { CONST } from 'const'; +const getValue = () => 0; export const Works = component$(({ count, some = 1+2, @@ -545,16 +546,21 @@ export const NoWorks2 = component$(({count, stuff: {hey}}) => { ); }); -export const NoWorks3 = component$(({count, stuff = hola()}) => { +export const DynamicDefaults = component$(({count, stuff = getValue(), other: value = getValue()}) => { console.log(stuff); useTask$(({track}) => { - track(() => count); - console.log(count); + track(() => stuff); + track(() => value); + console.log(count, stuff, value); }); return ( -
{count}
+
{value}
); }); + +export const ReferencedDefault = component$(({first = getValue(second), second}) => ( +
{first}{second}
+)); "# .to_string(), transpile_jsx: true,