Skip to main content

i_slint_compiler/passes/
remove_aliases.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 removes the property used in a two ways bindings
5
6use crate::diagnostics::BuildDiagnostics;
7use crate::expression_tree::{BindingExpression, Expression, NamedReference};
8use crate::object_tree::*;
9use std::cell::RefCell;
10use std::collections::{HashMap, HashSet, btree_map::Entry};
11use std::rc::Rc;
12
13// The property in the key is to be removed, and replaced by the property in the value
14type Mapping = HashMap<NamedReference, NamedReference>;
15
16#[derive(Default, Debug)]
17struct PropertySets {
18    map: HashMap<NamedReference, Rc<RefCell<HashSet<NamedReference>>>>,
19    all_sets: Vec<Rc<RefCell<HashSet<NamedReference>>>>,
20}
21
22impl PropertySets {
23    fn add_link(&mut self, p1: NamedReference, p2: NamedReference) {
24        let (e1, e2) = (p1.element(), p2.element());
25        let same_component = std::rc::Weak::ptr_eq(
26            &e1.borrow().enclosing_component,
27            &e2.borrow().enclosing_component,
28        );
29        if !same_component {
30            let can_merge_across_components =
31                (e1.borrow().enclosing_component.upgrade().unwrap().is_global()
32                    && !e2.borrow().change_callbacks.contains_key(p2.name()))
33                    || (e2.borrow().enclosing_component.upgrade().unwrap().is_global()
34                        && !e1.borrow().change_callbacks.contains_key(p1.name()));
35            if !can_merge_across_components {
36                // We can only merge aliases if they are in the same Component. (unless one of them is global if the other one don't have change event)
37                // TODO: actually we could still merge two alias in a component pointing to the same
38                // property in a parent component
39                return;
40            }
41        }
42
43        if let Some(s1) = self.map.get(&p1).cloned() {
44            if let Some(s2) = self.map.get(&p2).cloned() {
45                if Rc::ptr_eq(&s1, &s2) {
46                    return;
47                }
48                for x in s1.borrow().iter() {
49                    self.map.insert(x.clone(), s2.clone());
50                    s2.borrow_mut().insert(x.clone());
51                }
52                *s1.borrow_mut() = HashSet::new();
53            } else {
54                s1.borrow_mut().insert(p2.clone());
55                self.map.insert(p2, s1);
56            }
57        } else if let Some(s2) = self.map.get(&p2).cloned() {
58            s2.borrow_mut().insert(p1.clone());
59            self.map.insert(p1, s2);
60        } else {
61            let mut set = HashSet::new();
62            set.insert(p1.clone());
63            set.insert(p2.clone());
64            let set = Rc::new(RefCell::new(set));
65            self.map.insert(p1, set.clone());
66            self.map.insert(p2, set.clone());
67            self.all_sets.push(set)
68        }
69    }
70}
71
72pub fn remove_aliases(doc: &Document, diag: &mut BuildDiagnostics) {
73    // collect all sets that are linked together
74    let mut property_sets = PropertySets::default();
75
76    let mut process_element = |e: &ElementRc| {
77        'bindings: for (name, binding) in &e.borrow().bindings {
78            for twb in &binding.borrow().two_way_bindings {
79                if !twb.field_access.is_empty() {
80                    // Don't optimize two way bindings to fields for now
81                    continue;
82                }
83                let other_e = twb.property.element();
84                if name == twb.property.name() && Rc::ptr_eq(e, &other_e) {
85                    diag.push_error("Property cannot alias to itself".into(), &*binding.borrow());
86                    continue 'bindings;
87                }
88                property_sets.add_link(NamedReference::new(e, name.clone()), twb.property.clone());
89            }
90        }
91    };
92
93    doc.visit_all_used_components(|component| {
94        recurse_elem_including_sub_components(component, &(), &mut |e, &()| process_element(e))
95    });
96
97    // The key will be removed and replaced by the named reference
98    let mut aliases_to_remove = Mapping::new();
99
100    // For each set, find a "master" property. Only reference to this master property will be kept,
101    // and only the master property will keep its binding
102    for set in property_sets.all_sets {
103        let set = set.borrow();
104        let mut set_iter = set.iter();
105        if let Some(mut best) = set_iter.next().cloned() {
106            for candidate in set_iter {
107                best = best_property(best.clone(), candidate.clone());
108            }
109            for x in set.iter() {
110                if *x != best {
111                    aliases_to_remove.insert(x.clone(), best.clone());
112                }
113            }
114        }
115    }
116
117    doc.visit_all_used_components(|component| {
118        // Do the replacements
119        visit_all_named_references(component, &mut |nr: &mut NamedReference| {
120            if let Some(new) = aliases_to_remove.get(nr) {
121                *nr = new.clone();
122            }
123        })
124    });
125
126    // Remove the properties
127    for (remove, to) in aliases_to_remove {
128        let elem = remove.element();
129        let to_elem = to.element();
130
131        // adjust the bindings
132        let old_binding = elem.borrow_mut().bindings.remove(remove.name());
133        let mut old_binding = old_binding.map(RefCell::into_inner).unwrap_or_else(|| {
134            // ensure that we set an expression, because the right hand side of a binding always wins,
135            // and if that was not set, we must still kee the default then
136            let mut b = BindingExpression::from(Expression::default_value_for_type(&to.ty()));
137            b.priority = to_elem
138                .borrow_mut()
139                .bindings
140                .get(to.name())
141                .map_or(i32::MAX, |x| x.borrow().priority.saturating_add(1));
142            b
143        });
144
145        remove_from_binding_expression(&mut old_binding, &to);
146
147        let same_component = std::rc::Weak::ptr_eq(
148            &elem.borrow().enclosing_component,
149            &to_elem.borrow().enclosing_component,
150        );
151        match to_elem.borrow_mut().bindings.entry(to.name().clone()) {
152            Entry::Occupied(mut e) => {
153                let b = e.get_mut().get_mut();
154                remove_from_binding_expression(b, &to);
155                if !same_component || b.priority < old_binding.priority || !b.has_binding() {
156                    b.merge_with(&old_binding);
157                } else {
158                    old_binding.merge_with(b);
159                    *b = old_binding;
160                }
161            }
162            Entry::Vacant(e) => {
163                if same_component && old_binding.has_binding() {
164                    e.insert(old_binding.into());
165                }
166            }
167        };
168
169        // Adjust the change callbacks
170        {
171            let mut elem = elem.borrow_mut();
172            if let Some(old_change_callback) = elem.change_callbacks.remove(remove.name()) {
173                drop(elem);
174                let mut old_change_callback = old_change_callback.into_inner();
175                to_elem
176                    .borrow_mut()
177                    .change_callbacks
178                    .entry(to.name().clone())
179                    .or_default()
180                    .borrow_mut()
181                    .append(&mut old_change_callback);
182            }
183        }
184
185        // Remove the declaration
186        {
187            let mut elem = elem.borrow_mut();
188            let used_externally = elem
189                .property_analysis
190                .borrow()
191                .get(remove.name())
192                .is_some_and(|v| v.is_read_externally || v.is_set_externally);
193            if let Some(d) = elem.property_declarations.get_mut(remove.name()) {
194                if d.expose_in_public_api || used_externally {
195                    d.is_alias = Some(to.clone());
196                    drop(elem);
197                    // one must mark the aliased property as settable from outside
198                    to.mark_as_set();
199                } else {
200                    elem.property_declarations.remove(remove.name());
201                    let analysis = elem.property_analysis.borrow().get(remove.name()).cloned();
202                    if let Some(analysis) = analysis {
203                        drop(elem);
204                        to.element()
205                            .borrow()
206                            .property_analysis
207                            .borrow_mut()
208                            .entry(to.name().clone())
209                            .or_default()
210                            .merge(&analysis);
211                    };
212                }
213            } else {
214                // This is not a declaration, we must re-create the binding
215                elem.bindings.insert(
216                    remove.name().clone(),
217                    BindingExpression::new_two_way(to.clone().into()).into(),
218                );
219                drop(elem);
220                if remove.is_externally_modified() {
221                    to.mark_as_set();
222                }
223            }
224        }
225    }
226}
227
228fn is_declaration(x: &NamedReference) -> bool {
229    x.element().borrow().property_declarations.contains_key(x.name())
230}
231
232/// Out of two named reference, return the one which is the best to keep.
233fn best_property(p1: NamedReference, p2: NamedReference) -> NamedReference {
234    // Try to find which is the more canonical property
235    macro_rules! canonical_order {
236        ($x: expr) => {{
237            (
238                !$x.element().borrow().enclosing_component.upgrade().unwrap().is_global(),
239                is_declaration(&$x),
240                $x.element().borrow().id.clone(),
241                $x.name(),
242            )
243        }};
244    }
245
246    if canonical_order!(p1) < canonical_order!(p2) { p1 } else { p2 }
247}
248
249/// Remove the `to` from the two_way_bindings
250fn remove_from_binding_expression(expression: &mut BindingExpression, to: &NamedReference) {
251    expression.two_way_bindings.retain(|x| &x.property != to);
252}