Skip to main content

i_slint_compiler/passes/
move_declarations.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//! This pass moves all declaration of properties or callback to the root
5
6#![allow(clippy::mutable_key_type)] // ByAddress<ElementRc> keys rely on Rc identity semantics
7
8use crate::expression_tree::{Expression, NamedReference};
9use crate::langtype::ElementType;
10use crate::object_tree::*;
11use by_address::ByAddress;
12use core::cell::RefCell;
13use smol_str::{SmolStr, format_smolstr};
14use std::collections::{BTreeMap, HashMap, HashSet};
15use std::rc::Rc;
16
17/// The root-level name of each declaration that is going to be moved, keyed by the
18/// declaring element and the declared name.
19/// Concatenating the element id and the declared name alone can give the same name to two
20/// different declarations: the ids `a-2` and `a-2-b-4` with the properties `b-4-c` and `c`
21/// both concatenate to `a-2-b-4-c`.
22type RenameMap = HashMap<(ByAddress<ElementRc>, SmolStr), SmolStr>;
23
24struct Declarations {
25    property_declarations: BTreeMap<SmolStr, PropertyDeclaration>,
26}
27impl Declarations {
28    fn take_from_element(e: &mut Element) -> Self {
29        Declarations { property_declarations: core::mem::take(&mut e.property_declarations) }
30    }
31}
32
33pub fn move_declarations(component: &Rc<Component>) {
34    simplify_optimized_items_recursive(component);
35    let mut renames = RenameMap::new();
36    collect_renames(component, &mut renames);
37    do_move_declarations(component, &renames);
38}
39
40/// Pick a unique root-level name for every declaration that `do_move_declarations` moves
41fn collect_renames(component: &Rc<Component>, renames: &mut RenameMap) {
42    let mut elements = Vec::new();
43    recurse_elem(&component.root_element, &(), &mut |elem, _| {
44        if elem.borrow().repeated.is_some() {
45            if let ElementType::Component(base) = &elem.borrow().base_type {
46                collect_renames(base, renames);
47            }
48        } else if !Rc::ptr_eq(elem, &component.root_element) {
49            elements.push(elem.clone());
50        }
51    });
52    elements.extend(component.optimized_elements.borrow().iter().cloned());
53
54    let mut used: HashSet<SmolStr> =
55        component.root_element.borrow().property_declarations.keys().cloned().collect();
56    for elem in elements {
57        for prop in elem.borrow().property_declarations.keys() {
58            let base = map_name(&elem, prop);
59            let mut name = base.clone();
60            let mut suffix = 1;
61            while !used.insert(name.clone()) {
62                name = format_smolstr!("{base}-{suffix}");
63                suffix += 1;
64            }
65            renames.insert((ByAddress(elem.clone()), prop.clone()), name);
66        }
67    }
68
69    component.popup_windows.borrow().iter().for_each(|p| collect_renames(&p.component, renames));
70    component.menu_item_tree.borrow().iter().for_each(|c| collect_renames(c, renames));
71}
72
73fn do_move_declarations(component: &Rc<Component>, renames: &RenameMap) {
74    let mut decl = Declarations::take_from_element(&mut component.root_element.borrow_mut());
75    component
76        .popup_windows
77        .borrow()
78        .iter()
79        .for_each(|f| do_move_declarations(&f.component, renames));
80    component.menu_item_tree.borrow().iter().for_each(|c| do_move_declarations(c, renames));
81
82    let mut new_root_bindings = HashMap::new();
83    let mut new_root_change_callbacks = HashMap::new();
84    let mut new_root_property_analysis = BTreeMap::new();
85
86    let move_bindings_and_animations = &mut |elem: &ElementRc| {
87        visit_all_named_references_in_element(elem, |nr| fixup_reference(nr, renames));
88
89        if elem.borrow().repeated.is_some() {
90            if let ElementType::Component(base) = &elem.borrow().base_type {
91                do_move_declarations(base, renames);
92            } else {
93                panic!(
94                    "Repeated element should have a component as base because of the repeater_component.rs pass"
95                )
96            }
97            debug_assert!(
98                elem.borrow().property_declarations.is_empty() && elem.borrow().children.is_empty(),
99                "Repeated element should be empty because of the repeater_component.rs pass"
100            );
101            return;
102        }
103
104        // take the bindings so we do not keep the borrow_mut of the element
105        let bindings = core::mem::take(&mut elem.borrow_mut().bindings);
106        let mut new_bindings = BindingsMap::default();
107        for (k, e) in bindings {
108            let will_be_moved = elem.borrow().property_declarations.contains_key(&k);
109            if will_be_moved {
110                new_root_bindings.insert(moved_name(renames, elem, &k), e);
111            } else {
112                new_bindings.insert(k, e);
113            }
114        }
115        elem.borrow_mut().bindings = new_bindings;
116
117        let property_analysis = elem.borrow().property_analysis.take();
118        let mut new_property_analysis = BTreeMap::new();
119        for (prop, a) in property_analysis {
120            let will_be_moved = elem.borrow().property_declarations.contains_key(&prop);
121            if will_be_moved {
122                new_root_property_analysis.insert(moved_name(renames, elem, &prop), a);
123            } else {
124                new_property_analysis.insert(prop, a);
125            }
126        }
127        *elem.borrow().property_analysis.borrow_mut() = new_property_analysis;
128
129        // Also move the changed callback
130        let change_callbacks = core::mem::take(&mut elem.borrow_mut().change_callbacks);
131        let mut new_change_callbacks = BTreeMap::<SmolStr, RefCell<Vec<Expression>>>::default();
132        for (k, e) in change_callbacks {
133            let will_be_moved = elem.borrow().property_declarations.contains_key(&k);
134            if will_be_moved {
135                new_root_change_callbacks.insert(moved_name(renames, elem, &k), e);
136            } else {
137                new_change_callbacks.insert(k, e);
138            }
139        }
140        elem.borrow_mut().change_callbacks = new_change_callbacks;
141    };
142
143    component.optimized_elements.borrow().iter().for_each(&mut *move_bindings_and_animations);
144    recurse_elem(&component.root_element, &(), &mut |e, _| move_bindings_and_animations(e));
145
146    component
147        .root_constraints
148        .borrow_mut()
149        .visit_named_references(&mut |nr| fixup_reference(nr, renames));
150    component.popup_windows.borrow_mut().iter_mut().for_each(|p| {
151        fixup_reference(&mut p.x, renames);
152        fixup_reference(&mut p.y, renames);
153        // `is_open` references the synthesized property on this (the parent) component; it must be
154        // remapped to the moved-to-root property just like `x`/`y`, otherwise the runtime setter
155        // (see the interpreter's `show_popup`) cannot find it once the declaration is hoisted.
156        if let Some(is_open) = &mut p.is_open {
157            fixup_reference(is_open, renames);
158        }
159        visit_all_named_references(&p.component, &mut |nr| fixup_reference(nr, renames))
160    });
161    component.timers.borrow_mut().iter_mut().for_each(|t| {
162        fixup_reference(&mut t.interval, renames);
163        fixup_reference(&mut t.running, renames);
164        fixup_reference(&mut t.triggered, renames);
165    });
166    component.menu_item_tree.borrow_mut().iter_mut().for_each(|c| {
167        visit_all_named_references(c, &mut |nr| fixup_reference(nr, renames));
168    });
169    component.init_code.borrow_mut().iter_mut().for_each(|expr| {
170        visit_named_references_in_expression(expr, &mut |nr| fixup_reference(nr, renames));
171    });
172    for pd in decl.property_declarations.values_mut() {
173        if let Some(nr) = pd.is_alias.as_mut() {
174            fixup_reference(nr, renames)
175        }
176    }
177
178    let move_properties = &mut |elem: &ElementRc| {
179        let elem_decl = Declarations::take_from_element(&mut elem.borrow_mut());
180        decl.property_declarations.extend(
181            elem_decl
182                .property_declarations
183                .into_iter()
184                .map(|(p, d)| (moved_name(renames, elem, &p), d)),
185        );
186    };
187
188    recurse_elem(&component.root_element, &(), &mut |elem, _| move_properties(elem));
189
190    component.optimized_elements.borrow().iter().for_each(move_properties);
191
192    {
193        let mut r = component.root_element.borrow_mut();
194        r.property_declarations = decl.property_declarations;
195        r.bindings.extend(new_root_bindings);
196        r.property_analysis.borrow_mut().extend(new_root_property_analysis);
197        r.change_callbacks.extend(new_root_change_callbacks);
198    }
199}
200
201/// Map the reference to the previous properties to the new moved property at the root
202fn fixup_reference(nr: &mut NamedReference, renames: &RenameMap) {
203    let e = nr.element();
204    let parent_component = e.borrow().enclosing_component.upgrade().unwrap();
205    if !Rc::ptr_eq(&e, &parent_component.root_element)
206        && e.borrow().property_declarations.contains_key(nr.name())
207    {
208        *nr =
209            NamedReference::new(&parent_component.root_element, moved_name(renames, &e, nr.name()));
210    }
211}
212
213fn map_name(e: &ElementRc, s: &SmolStr) -> SmolStr {
214    format_smolstr!("{}-{}", e.borrow().id, s)
215}
216
217fn moved_name(renames: &RenameMap, e: &ElementRc, s: &SmolStr) -> SmolStr {
218    renames.get(&(ByAddress(e.clone()), s.clone())).cloned().unwrap_or_else(|| map_name(e, s))
219}
220
221fn simplify_optimized_items_recursive(component: &Rc<Component>) {
222    simplify_optimized_items(component.optimized_elements.borrow().as_slice());
223    component
224        .popup_windows
225        .borrow()
226        .iter()
227        .for_each(|f| simplify_optimized_items_recursive(&f.component));
228    recurse_elem(&component.root_element, &(), &mut |elem, _| {
229        if elem.borrow().repeated.is_some()
230            && let ElementType::Component(base) = &elem.borrow().base_type
231        {
232            simplify_optimized_items_recursive(base);
233        }
234    });
235}
236
237/// Optimized items are not used for the fact that they are items, but their properties
238/// might still be used.  So we must pretend all the properties are declared in the
239/// item itself so the move_declaration pass can move the declaration in the component root
240fn simplify_optimized_items(items: &[ElementRc]) {
241    for elem in items {
242        recurse_elem(elem, &(), &mut |elem, _| {
243            let base = core::mem::take(&mut elem.borrow_mut().base_type);
244            if let ElementType::Builtin(c) = base {
245                // This assume that all properties of builtin items are fine with the default value
246                elem.borrow_mut().property_declarations.extend(c.properties.iter().map(
247                    |(k, v)| {
248                        (
249                            k.clone(),
250                            PropertyDeclaration {
251                                property_type: v.ty.clone(),
252                                ..Default::default()
253                            },
254                        )
255                    },
256                ));
257            } else {
258                unreachable!("Only builtin items should be optimized")
259            }
260        })
261    }
262}