Skip to main content

i_slint_compiler/passes/
inlining.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Inline each object_tree::Component within the main Component
5
6#![allow(clippy::mutable_key_type)] // pass uses identity-based keys backed by interior mutability
7
8use crate::diagnostics::{BuildDiagnostics, Spanned};
9use crate::expression_tree::{BindingExpression, Expression, NamedReference};
10use crate::langtype::{ElementType, PropertyLookupMode, Type};
11use crate::object_tree::*;
12use by_address::ByAddress;
13use smol_str::SmolStr;
14use std::cell::RefCell;
15use std::collections::btree_map::Entry;
16use std::collections::{HashMap, HashSet};
17use std::rc::Rc;
18
19#[derive(Copy, Clone, Eq, PartialEq)]
20pub enum InlineSelection {
21    InlineAllComponents,
22    InlineOnlyRequiredComponents,
23}
24
25pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut BuildDiagnostics) {
26    fn inline_components_recursively(
27        component: &Rc<Component>,
28        roots: &HashSet<ByAddress<Rc<Component>>>,
29        inline_selection: InlineSelection,
30        diag: &mut BuildDiagnostics,
31    ) {
32        recurse_elem_no_borrow(&component.root_element, &(), &mut |elem, _| {
33            let base = elem.borrow().base_type.clone();
34            if let ElementType::Component(c) = base {
35                // First, make sure that the component itself is properly inlined
36                inline_components_recursively(&c, roots, inline_selection, diag);
37
38                if c.parent_element().is_some() {
39                    // We should not inline a repeated element
40                    return;
41                }
42
43                // Inline this component.
44                if match inline_selection {
45                    InlineSelection::InlineAllComponents => true,
46                    InlineSelection::InlineOnlyRequiredComponents => {
47                        component_requires_inlining(&c)
48                            || element_require_inlining(elem)
49                            // We always inline the root in case the element that instantiate this component needs full inlining,
50                            // except when the root is a repeater component, which are never inlined.
51                            || component.parent_element().is_none() && Rc::ptr_eq(elem, &component.root_element)
52                            // We always inline other roots as a component can't be both a sub component and a root
53                            || roots.contains(&ByAddress(c.clone()))
54                    }
55                } {
56                    inline_element(elem, &c, component, diag);
57                }
58            }
59        });
60        component.popup_windows.borrow().iter().for_each(|p| {
61            inline_components_recursively(&p.component, roots, inline_selection, diag)
62        })
63    }
64    let mut roots = HashSet::new();
65    if inline_selection == InlineSelection::InlineOnlyRequiredComponents {
66        for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
67            roots.insert(ByAddress(component.clone()));
68        }
69    }
70    for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
71        inline_components_recursively(&component, &roots, inline_selection, diag);
72        let mut init_code = component.init_code.borrow_mut();
73        let inlined_init_code = core::mem::take(&mut init_code.inlined_init_code);
74        init_code.constructor_code.splice(0..0, inlined_init_code.into_values());
75    }
76}
77
78fn element_key(e: ElementRc) -> ByAddress<ElementRc> {
79    ByAddress(e)
80}
81
82type Mapping = HashMap<ByAddress<ElementRc>, ElementRc>;
83
84fn inline_element(
85    elem: &ElementRc,
86    inlined_component: &Rc<Component>,
87    root_component: &Rc<Component>,
88    diag: &mut BuildDiagnostics,
89) {
90    // inlined_component must be the base type of this element
91    debug_assert_eq!(elem.borrow().base_type, ElementType::Component(inlined_component.clone()));
92    debug_assert!(
93        inlined_component.root_element.borrow().repeated.is_none(),
94        "root element of a component cannot be repeated"
95    );
96    debug_assert!(inlined_component.parent_element().is_none());
97
98    let mut elem_mut = elem.borrow_mut();
99    let priority_delta = 1 + elem_mut.inline_depth;
100    elem_mut.base_type = inlined_component.root_element.borrow().base_type.clone();
101    let Element { id: elem_id, property_declarations, .. } = &mut *elem_mut;
102    for (name, decl) in inlined_component.root_element.borrow().property_declarations.iter() {
103        match property_declarations.entry(name.clone()) {
104            // Only the functions lowered from `forward-focus` can be declared on both sides,
105            // as user code can't declare these reserved names. The derived one overrides.
106            Entry::Occupied(_) => debug_assert!(
107                crate::typeregister::reserved_member_function(name).is_some(),
108                "inlining {} into {} would merge two declarations of the same name",
109                inlined_component.id,
110                elem_id
111            ),
112            Entry::Vacant(e) => {
113                e.insert(PropertyDeclaration { expose_in_public_api: false, ..decl.clone() });
114            }
115        }
116    }
117    // Merge the shadow index, keeping the element's own shadows.
118    for (source_name, internal_name) in &inlined_component.root_element.borrow().shadowing_members {
119        elem_mut
120            .shadowing_members
121            .entry(source_name.clone())
122            .or_insert_with(|| internal_name.clone());
123    }
124
125    for (p, a) in inlined_component.root_element.borrow().property_analysis.borrow().iter() {
126        elem_mut.property_analysis.borrow_mut().entry(p.clone()).or_default().merge_with_base(a);
127    }
128
129    // states and transitions must be lowered before inlining
130    debug_assert!(inlined_component.root_element.borrow().states.is_empty());
131    debug_assert!(inlined_component.root_element.borrow().transitions.is_empty());
132
133    // Map the old element to the new
134    let mut mapping = HashMap::new();
135    mapping.insert(element_key(inlined_component.root_element.clone()), elem.clone());
136
137    let mut new_children = Vec::with_capacity(
138        elem_mut.children.len() + inlined_component.root_element.borrow().children.len(),
139    );
140    new_children.extend(
141        inlined_component.root_element.borrow().children.iter().map(|x| {
142            duplicate_element_with_mapping(x, &mut mapping, root_component, priority_delta)
143        }),
144    );
145
146    // Copy insertion points so we can extend/adjust without mutating the source component.
147    let inlined_insertion_points_orig = inlined_component.child_insertion_points.borrow();
148    let mut inlined_insertion_points = inlined_insertion_points_orig.clone();
149
150    // Ensure @children CIP exists if it's missing but the component is a builtin that accepts children.
151    // This preserves the implicit-children behavior for builtins without explicit placeholders.
152    // Which children a builtin accepts was checked when the object tree was built, so a builtin
153    // restricted to specific child types, such as `Path`, gets the placeholder too.
154    if !inlined_insertion_points.contains_key(DEFAULT_SLOT_NAME)
155        && let Some(builtin) = inlined_component.root_element.borrow().builtin_type()
156        && !builtin.is_non_item_type
157    {
158        let cip_node = inlined_component
159            .node
160            .as_ref()
161            .map(|n| n.clone().into())
162            .or_else(|| {
163                inlined_component
164                    .root_element
165                    .borrow()
166                    .debug
167                    .first()
168                    .map(|debug| debug.node.clone().into())
169            })
170            .or_else(|| elem_mut.debug.first().map(|debug| debug.node.clone().into()))
171            .or_else(|| root_component.node.as_ref().map(|n| n.clone().into()))
172            .expect("Missing syntax node for implicit @children insertion point");
173
174        inlined_insertion_points.insert(
175            DEFAULT_SLOT_NAME.into(),
176            ChildrenInsertionPoint {
177                parent: inlined_component.root_element.clone(),
178                insertion_index: inlined_component.root_element.borrow().children.len(),
179                node: ChildInsertionPointNode::DefaultChildrenPlaceHolder(cip_node),
180            },
181        );
182    }
183
184    // Group instance children by slot target (named slot or the default slot).
185    // This preserves relative order within each slot and allows named slot validation.
186    let mut children_by_slot: HashMap<SmolStr, Vec<ElementRc>> = HashMap::new();
187    for child in std::mem::take(&mut elem_mut.children) {
188        let slot = child
189            .borrow()
190            .slot_target
191            .as_ref()
192            .map(|s| crate::parser::normalize_identifier(s))
193            .unwrap_or_else(|| DEFAULT_SLOT_NAME.into());
194
195        children_by_slot.entry(slot).or_default().push(child);
196    }
197
198    // Validate that all referenced slots exist on the inlined component.
199    let mut unknown_slots = Vec::new();
200    for (slot_name, children) in &children_by_slot {
201        if slot_name == DEFAULT_SLOT_NAME {
202            // Missing default slot diagnostics are emitted earlier while constructing the object tree.
203            // Keep the existing behavior and avoid reporting the default slot as an unknown named slot here.
204            continue;
205        }
206        if !inlined_insertion_points.contains_key(slot_name.as_str()) {
207            for child in children {
208                diag.push_error(
209                    format!("Unknown slot '{slot_name}' in '{}'", inlined_component.id),
210                    &*child.borrow(),
211                );
212            }
213            unknown_slots.push(slot_name.clone());
214        }
215    }
216    for slot_name in unknown_slots {
217        children_by_slot.remove(&slot_name);
218    }
219
220    let mut move_children_into_popup = None;
221    let forwarded_sources_by_target: HashMap<SmolStr, Vec<SmolStr>> =
222        elem_mut.forwarded_slots.iter().fold(HashMap::new(), |mut map, forwarding| {
223            map.entry(crate::parser::normalize_identifier(forwarding.target.as_str()))
224                .or_default()
225                .push(crate::parser::normalize_identifier(forwarding.source.as_str()));
226            map
227        });
228
229    struct SlotInsertion {
230        slot_name: SmolStr,
231        insertion_index: usize,
232        node: ChildInsertionPointNode,
233        insertion_element: ElementRc,
234        children: Vec<ElementRc>,
235    }
236
237    // Collect all insertions per parent element so we can insert in a stable order.
238    let mut insertions_by_parent: HashMap<ByAddress<ElementRc>, (ElementRc, Vec<SlotInsertion>)> =
239        HashMap::new();
240
241    for (slot_name, inlined_cip) in inlined_insertion_points.iter() {
242        let children = children_by_slot.remove(slot_name.as_str()).unwrap_or_default();
243        if let Some(insertion_element) = mapping.get(&element_key(inlined_cip.parent.clone())) {
244            insertions_by_parent
245                .entry(element_key(insertion_element.clone()))
246                .or_insert_with(|| (insertion_element.clone(), Vec::new()))
247                .1
248                .push(SlotInsertion {
249                    slot_name: slot_name.as_str().into(),
250                    insertion_index: inlined_cip.insertion_index,
251                    node: inlined_cip.node.clone(),
252                    insertion_element: insertion_element.clone(),
253                    children,
254                });
255        } else if !children.is_empty() {
256            // @children was into a PopupWindow (named slots inside popups are not supported).
257            debug_assert!(inlined_component.popup_windows.borrow().iter().any(|p| Rc::ptr_eq(
258                &p.component,
259                &inlined_cip.parent.borrow().enclosing_component.upgrade().unwrap()
260            )));
261            if slot_name == DEFAULT_SLOT_NAME {
262                move_children_into_popup = Some(children);
263            } else {
264                diag.push_error(
265                    format!("The slot '{slot_name}' cannot appear in a PopupWindow"),
266                    &inlined_cip.node,
267                );
268            }
269        }
270    }
271
272    // Insert slot children and keep root insertion points in sync.
273    let mut insertions_for_parent =
274        |insertion_element: &ElementRc, insertions: &mut Vec<SlotInsertion>| {
275            insertions.sort_by(|a, b| {
276                a.insertion_index
277                    .cmp(&b.insertion_index)
278                    .then_with(|| a.node.span().offset.cmp(&b.node.span().offset))
279                    .then_with(|| a.slot_name.cmp(&b.slot_name))
280            });
281
282            let mut offset = 0usize;
283            if Rc::ptr_eq(elem, insertion_element) {
284                // Insert into the new inlined root children vector.
285                for insertion in insertions.drain(..) {
286                    let adjusted_index = insertion.insertion_index + offset;
287                    let inserted_len = insertion.children.len();
288                    if inserted_len > 0 {
289                        new_children.splice(adjusted_index..adjusted_index, insertion.children);
290                    }
291
292                    let mut root_insertion_points =
293                        root_component.child_insertion_points.borrow_mut();
294                    for (root_slot_name, cip) in root_insertion_points.iter_mut() {
295                        let forwarded_match = forwarded_sources_by_target
296                            .get(insertion.slot_name.as_str())
297                            .is_some_and(|sources| {
298                                sources.iter().any(|source| source == root_slot_name)
299                            });
300                        if Rc::ptr_eq(&cip.parent, elem)
301                            && (root_slot_name.as_str() == insertion.slot_name.as_str()
302                                || forwarded_match)
303                        {
304                            *cip = ChildrenInsertionPoint {
305                                parent: insertion.insertion_element.clone(),
306                                insertion_index: adjusted_index + cip.insertion_index,
307                                node: insertion.node.clone(),
308                            };
309                        }
310                    }
311                    if root_insertion_points.is_empty()
312                        && Rc::ptr_eq(elem, &root_component.root_element)
313                        && insertion.slot_name == DEFAULT_SLOT_NAME
314                    {
315                        root_insertion_points.insert(
316                            DEFAULT_SLOT_NAME.into(),
317                            ChildrenInsertionPoint {
318                                parent: insertion.insertion_element.clone(),
319                                insertion_index: adjusted_index + inserted_len,
320                                node: insertion.node.clone(),
321                            },
322                        );
323                    }
324
325                    offset += inserted_len;
326                }
327            } else {
328                // Insert into a mapped child element (not the inlined root).
329                let mut insertion_element_mut = insertion_element.borrow_mut();
330                for insertion in insertions.drain(..) {
331                    let adjusted_index = insertion.insertion_index + offset;
332                    let inserted_len = insertion.children.len();
333                    if inserted_len > 0 {
334                        insertion_element_mut
335                            .children
336                            .splice(adjusted_index..adjusted_index, insertion.children);
337                    }
338
339                    let mut root_insertion_points =
340                        root_component.child_insertion_points.borrow_mut();
341                    for (root_slot_name, cip) in root_insertion_points.iter_mut() {
342                        let forwarded_match = forwarded_sources_by_target
343                            .get(insertion.slot_name.as_str())
344                            .is_some_and(|sources| {
345                                sources.iter().any(|source| source == root_slot_name)
346                            });
347                        if Rc::ptr_eq(&cip.parent, elem)
348                            && (root_slot_name.as_str() == insertion.slot_name.as_str()
349                                || forwarded_match)
350                        {
351                            *cip = ChildrenInsertionPoint {
352                                parent: insertion.insertion_element.clone(),
353                                insertion_index: adjusted_index + cip.insertion_index,
354                                node: insertion.node.clone(),
355                            };
356                        }
357                    }
358                    if root_insertion_points.is_empty()
359                        && Rc::ptr_eq(elem, &root_component.root_element)
360                        && insertion.slot_name == DEFAULT_SLOT_NAME
361                    {
362                        root_insertion_points.insert(
363                            DEFAULT_SLOT_NAME.into(),
364                            ChildrenInsertionPoint {
365                                parent: insertion.insertion_element.clone(),
366                                insertion_index: adjusted_index + inserted_len,
367                                node: insertion.node.clone(),
368                            },
369                        );
370                    }
371
372                    offset += inserted_len;
373                }
374            }
375        };
376
377    for (insertion_element, mut insertions) in insertions_by_parent.into_values() {
378        insertions_for_parent(&insertion_element, &mut insertions);
379    }
380
381    elem_mut.children = new_children;
382    elem_mut.debug.extend_from_slice(&inlined_component.root_element.borrow().debug);
383
384    if let ElementType::Component(c) = &mut elem_mut.base_type
385        && c.parent_element().is_some()
386    {
387        debug_assert!(Rc::ptr_eq(elem, &c.parent_element().unwrap()));
388        *c = duplicate_sub_component(c, elem, &mut mapping, priority_delta);
389    };
390
391    root_component.optimized_elements.borrow_mut().extend(
392        inlined_component.optimized_elements.borrow().iter().map(|x| {
393            duplicate_element_with_mapping(x, &mut mapping, root_component, priority_delta)
394        }),
395    );
396    root_component.popup_windows.borrow_mut().extend(
397        inlined_component
398            .popup_windows
399            .borrow()
400            .iter()
401            .map(|p| duplicate_popup(p, &mut mapping, priority_delta)),
402    );
403
404    root_component.menu_item_tree.borrow_mut().extend(
405        inlined_component
406            .menu_item_tree
407            .borrow()
408            .iter()
409            .map(|it| duplicate_sub_component(it, elem, &mut mapping, priority_delta)),
410    );
411
412    root_component.timers.borrow_mut().extend(inlined_component.timers.borrow().iter().map(|t| {
413        let inlined_element = mapping.get(&element_key(t.element.upgrade().unwrap())).unwrap();
414
415        Timer { element: Rc::downgrade(inlined_element), ..t.clone() }
416    }));
417
418    let mut moved_into_popup = HashSet::new();
419    if let Some(children) = move_children_into_popup {
420        let inlined_insertion_points = inlined_component.child_insertion_points.borrow();
421        let inlined_cip = inlined_insertion_points.get(DEFAULT_SLOT_NAME).unwrap();
422
423        let insertion_element = mapping.get(&element_key(inlined_cip.parent.clone())).unwrap();
424        debug_assert!(!std::rc::Weak::ptr_eq(
425            &insertion_element.borrow().enclosing_component,
426            &elem_mut.enclosing_component,
427        ));
428        debug_assert!(root_component.popup_windows.borrow().iter().any(|p| Rc::ptr_eq(
429            &p.component,
430            &insertion_element.borrow().enclosing_component.upgrade().unwrap()
431        )));
432        for c in &children {
433            recurse_elem(c, &(), &mut |e, _| {
434                e.borrow_mut().enclosing_component =
435                    insertion_element.borrow().enclosing_component.clone();
436                moved_into_popup.insert(element_key(e.clone()));
437            });
438        }
439        insertion_element
440            .borrow_mut()
441            .children
442            .splice(inlined_cip.insertion_index..inlined_cip.insertion_index, children);
443        let mut root_insertion_points = root_component.child_insertion_points.borrow_mut();
444        for cip in root_insertion_points.values_mut() {
445            if Rc::ptr_eq(&cip.parent, elem) {
446                *cip = ChildrenInsertionPoint {
447                    parent: insertion_element.clone(),
448                    insertion_index: inlined_cip.insertion_index + cip.insertion_index,
449                    node: inlined_cip.node.clone(),
450                };
451            }
452        }
453        if root_insertion_points.is_empty() {
454            root_insertion_points.insert(
455                DEFAULT_SLOT_NAME.into(),
456                ChildrenInsertionPoint {
457                    parent: insertion_element.clone(),
458                    insertion_index: inlined_cip.insertion_index,
459                    node: inlined_cip.node.clone(),
460                },
461            );
462        };
463    }
464
465    for (property, root_binding) in
466        inlined_component.root_element.borrow().bindings_including_synthetic()
467    {
468        match elem_mut.binding_cell_including_synthetic(property) {
469            Some(elem_binding) => {
470                let mut binding = elem_binding.borrow_mut();
471                if binding.merge_with(&root_binding.borrow()) {
472                    binding.priority = binding.priority.saturating_add(priority_delta);
473                }
474            }
475            None => {
476                let mut elem_binding = root_binding.borrow().clone();
477                elem_binding.priority = elem_binding.priority.saturating_add(priority_delta);
478                elem_mut.set_binding(property.clone(), elem_binding);
479            }
480        }
481    }
482    for (k, val) in inlined_component.root_element.borrow().change_callbacks.iter() {
483        match elem_mut.change_callbacks.entry(k.clone()) {
484            std::collections::btree_map::Entry::Vacant(entry) => {
485                entry.insert(val.clone());
486            }
487            std::collections::btree_map::Entry::Occupied(mut entry) => {
488                entry.get_mut().get_mut().splice(0..0, val.borrow().iter().cloned());
489            }
490        }
491    }
492
493    if let Some(orig) = &inlined_component.root_element.borrow().layout_info_prop {
494        if let Some(_new) = &mut elem_mut.layout_info_prop {
495            todo!("Merge layout infos");
496        } else {
497            elem_mut.layout_info_prop = Some(orig.clone());
498        }
499    }
500    if let Some(orig) = &inlined_component.root_element.borrow().layout_info_h_at_own_height {
501        if let Some(_new) = &mut elem_mut.layout_info_h_at_own_height {
502            todo!("Merge layout infos");
503        } else {
504            elem_mut.layout_info_h_at_own_height = Some(orig.clone());
505        }
506    }
507    if let Some(orig) = &inlined_component.root_element.borrow().layout_info_v_with_constraint {
508        if let Some(_new) = &mut elem_mut.layout_info_v_with_constraint {
509            todo!("Merge layout infos");
510        } else {
511            elem_mut.layout_info_v_with_constraint = Some(orig.clone());
512        }
513    }
514
515    core::mem::drop(elem_mut);
516
517    let fixup_init_expression = |mut init_code: Expression| {
518        // Fix up any property references from within already collected init code.
519        visit_named_references_in_expression(&mut init_code, &mut |nr| {
520            fixup_reference(nr, &mapping)
521        });
522        fixup_element_references(&mut init_code, &mapping);
523        init_code
524    };
525    let inlined_init_code = inlined_component
526        .init_code
527        .borrow()
528        .inlined_init_code
529        .values()
530        .cloned()
531        .chain(inlined_component.init_code.borrow().constructor_code.iter().cloned())
532        .map(fixup_init_expression)
533        .collect();
534
535    root_component
536        .init_code
537        .borrow_mut()
538        .inlined_init_code
539        .insert(elem.borrow().span().offset, Expression::CodeBlock(inlined_init_code));
540
541    // Now fixup all bindings and references
542    for e in mapping.values() {
543        // Must run before visit_all_named_references_in_element to break shared
544        // GridLayoutCell Rcs (otherwise NR fixup would modify the original's cells).
545        visit_element_expressions(e, |expr, _, _| fixup_element_references(expr, &mapping));
546        // Also clone grid cells in the debug layout, which
547        // visit_all_named_references_in_element also visits.
548        for d in &mut e.borrow_mut().debug {
549            if let Some(crate::layout::Layout::GridLayout(grid)) = d.layout.as_mut() {
550                grid.clone_cells();
551            }
552        }
553        visit_all_named_references_in_element(e, |nr| fixup_reference(nr, &mapping));
554    }
555    for p in root_component.popup_windows.borrow_mut().iter_mut() {
556        fixup_reference(&mut p.x, &mapping);
557        fixup_reference(&mut p.y, &mapping);
558        if let Some(is_open) = &mut p.is_open {
559            fixup_reference(is_open, &mapping);
560        }
561    }
562    for t in root_component.timers.borrow_mut().iter_mut() {
563        fixup_reference(&mut t.interval, &mapping);
564        fixup_reference(&mut t.running, &mapping);
565        fixup_reference(&mut t.triggered, &mapping);
566    }
567    // If some element were moved into PopupWindow, we need to report error if they are used outside of the popup window.
568    if !moved_into_popup.is_empty() {
569        recurse_elem_no_borrow(&root_component.root_element.clone(), &(), &mut |e, _| {
570            if !moved_into_popup.contains(&element_key(e.clone())) {
571                visit_all_named_references_in_element(e, |nr| {
572                    if moved_into_popup.contains(&element_key(nr.element())) {
573                        diag.push_error(format!("Access to property '{nr:?}' which is inlined into a PopupWindow via @children is forbidden"), &*e.borrow());
574                    }
575                });
576            }
577        });
578    }
579}
580
581// Duplicate the element elem and all its children. And fill the mapping to point from the old to the new
582fn duplicate_element_with_mapping(
583    element: &ElementRc,
584    mapping: &mut Mapping,
585    root_component: &Rc<Component>,
586    priority_delta: i32,
587) -> ElementRc {
588    let elem = element.borrow();
589    let new = Rc::new(RefCell::new(Element {
590        base_type: elem.base_type.clone(),
591        id: elem.id.clone(),
592        is_injected_wrapper_element: elem.is_injected_wrapper_element,
593        property_declarations: elem.property_declarations.clone(),
594        shadowing_members: elem.shadowing_members.clone(),
595        // We will do the fixup of the references in bindings later
596        bindings: elem
597            .bindings_including_synthetic()
598            .map(|b| duplicate_binding(b, mapping, root_component, priority_delta))
599            .collect(),
600        change_callbacks: elem.change_callbacks.clone(),
601        property_analysis: elem.property_analysis.clone(),
602        children: elem
603            .children
604            .iter()
605            .map(|x| duplicate_element_with_mapping(x, mapping, root_component, priority_delta))
606            .collect(),
607        repeated: elem.repeated.clone(),
608        is_component_placeholder: elem.is_component_placeholder,
609        debug: elem.debug.clone(),
610        enclosing_component: Rc::downgrade(root_component),
611        states: elem.states.clone(),
612        match_elements: Default::default(),
613        transitions: elem
614            .transitions
615            .iter()
616            .map(|t| duplicate_transition(t, mapping, root_component, priority_delta))
617            .collect(),
618        child_of_layout: elem.child_of_layout,
619        child_of_flexbox: elem.child_of_flexbox,
620        parent_box_layout_orientation: elem.parent_box_layout_orientation,
621        layout_info_prop: elem.layout_info_prop.clone(),
622        layout_info_v_with_constraint: elem.layout_info_v_with_constraint.clone(),
623        layout_info_h_at_own_height: elem.layout_info_h_at_own_height.clone(),
624        height_is_literal: elem.height_is_literal,
625        default_fill_parent: elem.default_fill_parent,
626        accessibility_props: elem.accessibility_props.clone(),
627        geometry_props: elem.geometry_props.clone(),
628        named_references: Default::default(),
629        item_index: Default::default(), // Not determined yet
630        item_index_of_first_children: Default::default(),
631        is_flickable_content: elem.is_flickable_content,
632        has_popup_child: elem.has_popup_child,
633        is_tooltip: elem.is_tooltip,
634        z_order: elem.z_order.clone(),
635        is_legacy_syntax: elem.is_legacy_syntax,
636        inline_depth: elem.inline_depth + 1,
637        slot_target: elem.slot_target.clone(),
638        forwarded_slots: elem.forwarded_slots.clone(),
639        // Deep-clone grid_layout_cell to avoid sharing between original and inlined copies.
640        // This is important because children_constraints contain NamedReferences that need
641        // to be fixed up independently for each inlined copy.
642        grid_layout_cell: elem
643            .grid_layout_cell
644            .as_ref()
645            .map(|cell| Rc::new(RefCell::new(cell.borrow().clone()))),
646    }));
647    mapping.insert(element_key(element.clone()), new.clone());
648    if let ElementType::Component(c) = &mut new.borrow_mut().base_type
649        && c.parent_element().is_some()
650    {
651        debug_assert!(Rc::ptr_eq(element, &c.parent_element().unwrap()));
652        *c = duplicate_sub_component(c, &new, mapping, priority_delta);
653    };
654
655    new
656}
657
658/// Duplicate Component for repeated element or popup window that have a parent_element
659fn duplicate_sub_component(
660    component_to_duplicate: &Rc<Component>,
661    new_parent: &ElementRc,
662    mapping: &mut Mapping,
663    priority_delta: i32,
664) -> Rc<Component> {
665    debug_assert!(component_to_duplicate.parent_element().is_some());
666    let new_component = Component {
667        node: component_to_duplicate.node.clone(),
668        id: component_to_duplicate.id.clone(),
669        root_element: duplicate_element_with_mapping(
670            &component_to_duplicate.root_element,
671            mapping,
672            component_to_duplicate, // that's the wrong one, but we fixup further
673            priority_delta,
674        ),
675        parent_element: RefCell::new(Rc::downgrade(new_parent)),
676        optimized_elements: RefCell::new(
677            component_to_duplicate
678                .optimized_elements
679                .borrow()
680                .iter()
681                .map(|e| {
682                    duplicate_element_with_mapping(
683                        e,
684                        mapping,
685                        component_to_duplicate,
686                        priority_delta,
687                    )
688                })
689                .collect(),
690        ),
691        root_constraints: component_to_duplicate.root_constraints.clone(),
692        child_insertion_points: component_to_duplicate.child_insertion_points.clone(),
693        declared_slots: component_to_duplicate.declared_slots.clone(),
694        init_code: component_to_duplicate.init_code.clone(),
695        popup_windows: Default::default(),
696        timers: component_to_duplicate.timers.clone(),
697        menu_item_tree: Default::default(),
698        exported_global_names: component_to_duplicate.exported_global_names.clone(),
699        used: component_to_duplicate.used.clone(),
700        private_properties: Default::default(),
701        inherits_popup_window: core::cell::Cell::new(false),
702        from_library: core::cell::Cell::new(false),
703    };
704
705    let new_component = Rc::new(new_component);
706    let weak = Rc::downgrade(&new_component);
707    recurse_elem(&new_component.root_element, &(), &mut |e, _| {
708        e.borrow_mut().enclosing_component = weak.clone()
709    });
710    for o in new_component.optimized_elements.borrow().iter() {
711        o.borrow_mut().enclosing_component = weak.clone()
712    }
713    *new_component.popup_windows.borrow_mut() = component_to_duplicate
714        .popup_windows
715        .borrow()
716        .iter()
717        .map(|p| duplicate_popup(p, mapping, priority_delta))
718        .collect();
719    for p in new_component.popup_windows.borrow_mut().iter_mut() {
720        fixup_reference(&mut p.x, mapping);
721        fixup_reference(&mut p.y, mapping);
722        if let Some(is_open) = &mut p.is_open {
723            fixup_reference(is_open, mapping);
724        }
725    }
726    for t in new_component.timers.borrow_mut().iter_mut() {
727        fixup_reference(&mut t.interval, mapping);
728        fixup_reference(&mut t.running, mapping);
729        fixup_reference(&mut t.triggered, mapping);
730        if let Some(e) = mapping.get(&element_key(t.element.upgrade().unwrap())) {
731            t.element = Rc::downgrade(e);
732        }
733    }
734    *new_component.menu_item_tree.borrow_mut() = component_to_duplicate
735        .menu_item_tree
736        .borrow()
737        .iter()
738        .map(|it| {
739            let new_parent =
740                mapping.get(&element_key(it.parent_element().unwrap())).unwrap().clone();
741            duplicate_sub_component(it, &new_parent, mapping, priority_delta)
742        })
743        .collect();
744    new_component
745        .root_constraints
746        .borrow_mut()
747        .visit_named_references(&mut |nr| fixup_reference(nr, mapping));
748    new_component
749}
750
751fn duplicate_popup(p: &PopupWindow, mapping: &mut Mapping, priority_delta: i32) -> PopupWindow {
752    let parent = mapping
753        .get(&element_key(p.component.parent_element().expect("must have a parent")))
754        .expect("Parent must be in the mapping")
755        .clone();
756    PopupWindow {
757        x: p.x.clone(),
758        y: p.y.clone(),
759        close_policy: p.close_policy.clone(),
760        component: duplicate_sub_component(&p.component, &parent, mapping, priority_delta),
761        parent_element: mapping
762            .get(&element_key(p.parent_element.clone()))
763            .expect("Parent element must be in the mapping")
764            .clone(),
765        is_tooltip: p.is_tooltip,
766        is_open: p.is_open.clone(),
767    }
768}
769
770/// Clone and increase the priority of a binding
771/// and duplicate its animation
772fn duplicate_binding(
773    (k, b): (&SmolStr, &RefCell<BindingExpression>),
774    mapping: &mut Mapping,
775    root_component: &Rc<Component>,
776    priority_delta: i32,
777) -> (SmolStr, RefCell<BindingExpression>) {
778    let b = b.borrow();
779    let b = BindingExpression {
780        expression: b.expression.clone(),
781        span: b.span.clone(),
782        priority: b.priority.saturating_add(priority_delta),
783        animation: b
784            .animation
785            .as_ref()
786            .map(|pa| duplicate_property_animation(pa, mapping, root_component, priority_delta)),
787        analysis: b.analysis.clone(),
788        two_way_bindings: b.two_way_bindings.clone(),
789    };
790    (k.clone(), b.into())
791}
792
793fn duplicate_property_animation(
794    v: &PropertyAnimation,
795    mapping: &mut Mapping,
796    root_component: &Rc<Component>,
797    priority_delta: i32,
798) -> PropertyAnimation {
799    match v {
800        PropertyAnimation::Static(a) => PropertyAnimation::Static(duplicate_element_with_mapping(
801            a,
802            mapping,
803            root_component,
804            priority_delta,
805        )),
806        PropertyAnimation::Transition { state_ref, animations } => PropertyAnimation::Transition {
807            state_ref: state_ref.clone(),
808            animations: animations
809                .iter()
810                .map(|a| TransitionPropertyAnimation {
811                    state_id: a.state_id,
812                    direction: a.direction,
813                    animation: duplicate_element_with_mapping(
814                        &a.animation,
815                        mapping,
816                        root_component,
817                        priority_delta,
818                    ),
819                })
820                .collect(),
821        },
822    }
823}
824
825fn fixup_reference(nr: &mut NamedReference, mapping: &Mapping) {
826    if let Some(e) = mapping.get(&element_key(nr.element())) {
827        *nr = NamedReference::new(e, nr.name().clone());
828    }
829}
830
831/// Remap all the element references stored in a grid layout (the cell items and
832/// the repeated-row child templates) through the inlining `mapping`.
833fn fixup_grid_layout(layout: &mut crate::layout::GridLayout, fxe: &impl Fn(&mut ElementRc)) {
834    for e in &mut layout.elems {
835        fxe(&mut e.item.element);
836    }
837    // Break the cell Rc sharing with the original before remapping the elements
838    // stored inside the repeated-row cells.
839    layout.clone_cells();
840    for elem in &mut layout.elems {
841        let mut cell = elem.cell.borrow_mut();
842        let Some(child_items) = &mut cell.child_items else { continue };
843        for child in child_items.iter_mut() {
844            fxe(&mut child.layout_item_mut().element);
845            if let crate::layout::RowChildTemplate::Repeated { repeated_element, .. } = child {
846                fxe(repeated_element);
847            }
848        }
849    }
850}
851
852fn fixup_element_references(expr: &mut Expression, mapping: &Mapping) {
853    let fx = |element: &mut std::rc::Weak<RefCell<Element>>| {
854        if let Some(e) = element.upgrade().and_then(|e| mapping.get(&element_key(e))) {
855            *element = Rc::downgrade(e);
856        }
857    };
858    let fxe = |element: &mut ElementRc| {
859        if let Some(e) = mapping.get(&element_key(element.clone())) {
860            *element = e.clone();
861        }
862    };
863    match expr {
864        Expression::ElementReference(element) => fx(element),
865        Expression::SolveBoxLayout(layout, _) => {
866            for e in &mut layout.elems {
867                fxe(&mut e.element);
868            }
869        }
870        Expression::ComputeBoxLayoutInfo { layout, cross_axis_size, .. } => {
871            for e in &mut layout.elems {
872                fxe(&mut e.element);
873            }
874            if let Some(cas) = cross_axis_size {
875                fixup_element_references(cas, mapping);
876            }
877        }
878        Expression::SolveGridLayout { layout, .. } | Expression::OrganizeGridLayout(layout) => {
879            fixup_grid_layout(layout, &fxe);
880        }
881        Expression::ComputeGridLayoutInfo { layout, cross_axis_size, .. } => {
882            fixup_grid_layout(layout, &fxe);
883            if let Some(cas) = cross_axis_size {
884                fixup_element_references(cas, mapping);
885            }
886        }
887        Expression::SolveFlexboxLayout(layout) => {
888            for e in &mut layout.elems {
889                fxe(&mut e.element);
890            }
891        }
892        Expression::ComputeFlexboxLayoutInfo { layout, cross_axis_size, .. } => {
893            for e in &mut layout.elems {
894                fxe(&mut e.element);
895            }
896            if let Some(cas) = cross_axis_size {
897                fixup_element_references(cas, mapping);
898            }
899        }
900        Expression::RepeaterModelReference { element }
901        | Expression::RepeaterIndexReference { element } => fx(element),
902        _ => expr.visit_mut(|e| fixup_element_references(e, mapping)),
903    }
904}
905
906fn duplicate_transition(
907    t: &Transition,
908    mapping: &mut HashMap<ByAddress<ElementRc>, Rc<RefCell<Element>>>,
909    root_component: &Rc<Component>,
910    priority_delta: i32,
911) -> Transition {
912    Transition {
913        direction: t.direction,
914        state_id: t.state_id.clone(),
915        property_animations: t
916            .property_animations
917            .iter()
918            .map(|(r, loc, anim)| {
919                (
920                    r.clone(),
921                    loc.clone(),
922                    duplicate_element_with_mapping(anim, mapping, root_component, priority_delta),
923                )
924            })
925            .collect(),
926        node: t.node.clone(),
927    }
928}
929
930// Some components need to be inlined to avoid increased complexity in handling them
931// in the code generators and subsequent passes.
932fn component_requires_inlining(component: &Rc<Component>) -> bool {
933    let root_element = &component.root_element;
934    if super::flickable::is_flickable_element(root_element) {
935        return true;
936    }
937
938    for (prop, binding) in root_element.borrow().real_bindings() {
939        let binding = binding.borrow();
940        // The passes that dp the drop shadow or the opacity currently won't allow this property
941        // on the top level of a component. This could be changed in the future.
942        if prop.starts_with("drop-shadow-")
943            || prop.starts_with("inner-shadow-")
944            || prop == "opacity"
945            || prop == "cache-rendering-hint"
946            || prop == "visible"
947        {
948            return true;
949        }
950        if (prop == "height" || prop == "width") && binding.expression.ty() == Type::Percent {
951            // percentage size in the root element might not make sense anyway.
952            return true;
953        }
954        if binding.animation.is_some() {
955            let lookup_result =
956                root_element.borrow().lookup_property(prop, PropertyLookupMode::InternalName);
957            if !lookup_result.is_valid()
958                || !lookup_result.is_local_to_component
959                || !matches!(
960                    lookup_result.property_visibility,
961                    PropertyVisibility::Private | PropertyVisibility::Output
962                )
963            {
964                // If there is an animation, we currently inline so that if this property
965                // is set with a binding, it is merged
966                return true;
967            }
968        }
969    }
970
971    false
972}
973
974fn element_require_inlining(elem: &ElementRc) -> bool {
975    if !elem.borrow().children.is_empty() {
976        // the generators assume that the children list is complete, which sub-components may break
977        return true;
978    }
979
980    if !elem.borrow().forwarded_slots.is_empty() {
981        // Slot forwarding relies on slot insertion points being materialized in this element's
982        // subtree, which currently only happens through inlining.
983        return true;
984    }
985
986    // Popup windows need to be inlined for root.close() to work properly.
987    if super::lower_popups::is_popup_window(elem) {
988        return true;
989    }
990
991    for (prop, binding) in elem.borrow().real_bindings() {
992        if prop == "clip" {
993            // otherwise the children of the clipped items won't get moved as child of the Clip element
994            return true;
995        }
996
997        if (prop == "padding"
998            || prop == "spacing"
999            || prop.starts_with("padding-")
1000            || prop.starts_with("spacing-")
1001            || prop == "alignment")
1002            && let ElementType::Component(base) = &elem.borrow().base_type
1003            && crate::layout::is_layout(&base.root_element.borrow().base_type)
1004            && !base.root_element.borrow().is_binding_set(prop, false)
1005        {
1006            // The layout pass need to know that this property is set
1007            return true;
1008        }
1009
1010        let binding = binding.borrow();
1011        if binding.animation.is_some() && matches!(binding.expression, Expression::Invalid) {
1012            // If there is an animation but no binding, we must merge the binding with its animation.
1013            return true;
1014        }
1015    }
1016
1017    false
1018}