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