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