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