i_slint_compiler/passes/
remove_aliases.rs1use 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};
12use std::rc::Rc;
13
14type Mapping = HashMap<NamedReference, (NamedReference, PropertySet)>;
16type PropertySet = Rc<RefCell<HashSet<NamedReference>>>;
17
18#[derive(Default, Debug)]
19struct PropertySets {
20 map: HashMap<NamedReference, Rc<RefCell<HashSet<NamedReference>>>>,
21 all_sets: Vec<PropertySet>,
22}
23
24impl PropertySets {
25 fn add_link(&mut self, p1: NamedReference, p2: NamedReference) {
26 let mut members = vec![p1.clone(), p2.clone()];
27 for p in [&p1, &p2] {
28 if let Some(s) = self.map.get(p) {
29 members.extend(s.borrow().iter().cloned());
30 }
31 }
32 let initial_component = members[0].element().borrow().enclosing_component.clone();
33 let link_is_same_component = std::rc::Weak::ptr_eq(
34 &p1.element().borrow().enclosing_component,
35 &p2.element().borrow().enclosing_component,
36 );
37 let contains_changed_handlers = members.iter().any(|property| {
38 property.element().borrow().change_callbacks.contains_key(property.name())
39 });
40 let set_is_same_component = members.iter().all(|property| {
41 std::rc::Weak::ptr_eq(
42 &initial_component,
43 &property.element().borrow().enclosing_component,
44 )
45 });
46 let link_involves_global = [&p1, &p2].iter().any(|property| {
47 property.element().borrow().enclosing_component.upgrade().unwrap().is_global()
48 });
49
50 if !link_is_same_component {
51 let can_merge_across_components = !contains_changed_handlers && link_involves_global;
54 if !can_merge_across_components {
55 return;
56 }
57 } else {
58 let can_merge_within_component = !contains_changed_handlers || set_is_same_component;
63 if !can_merge_within_component {
64 return;
65 }
66 }
67
68 if let Some(s1) = self.map.get(&p1).cloned() {
69 if let Some(s2) = self.map.get(&p2).cloned() {
70 if Rc::ptr_eq(&s1, &s2) {
71 return;
72 }
73 for x in s1.borrow().iter() {
74 self.map.insert(x.clone(), s2.clone());
75 s2.borrow_mut().insert(x.clone());
76 }
77 *s1.borrow_mut() = HashSet::new();
78 } else {
79 s1.borrow_mut().insert(p2.clone());
80 self.map.insert(p2, s1);
81 }
82 } else if let Some(s2) = self.map.get(&p2).cloned() {
83 s2.borrow_mut().insert(p1.clone());
84 self.map.insert(p1, s2);
85 } else {
86 let mut set = HashSet::new();
87 set.insert(p1.clone());
88 set.insert(p2.clone());
89 let set = Rc::new(RefCell::new(set));
90 self.map.insert(p1, set.clone());
91 self.map.insert(p2, set.clone());
92 self.all_sets.push(set)
93 }
94 }
95}
96
97pub fn remove_aliases(doc: &Document, diag: &mut BuildDiagnostics) {
98 let mut property_sets = PropertySets::default();
100
101 let mut callback_aliases: HashMap<NamedReference, Vec<NamedReference>> = HashMap::new();
104
105 let mut process_element = |e: &ElementRc| {
106 'bindings: for (name, binding) in e.borrow().real_bindings() {
107 for twb in &binding.borrow().two_way_bindings {
108 if let TwoWayBinding::Property { property, field_access } = twb {
109 if !field_access.is_empty() {
110 continue;
112 }
113 let other_e = property.element();
114 if name == property.name() && Rc::ptr_eq(e, &other_e) {
115 diag.push_error(
116 "Property cannot alias to itself".into(),
117 &*binding.borrow(),
118 );
119 continue 'bindings;
120 }
121 let source = NamedReference::new(e, name.clone());
122 if matches!(source.ty(), Type::Callback(..)) {
123 let bucket = callback_aliases.entry(property.clone()).or_default();
124 if !bucket.contains(&source) {
125 bucket.push(source.clone());
126 }
127 }
128 property_sets.add_link(source, property.clone());
129 }
130 }
131 }
132 };
133
134 doc.visit_all_used_components(|component| {
135 recurse_elem_including_sub_components(component, &(), &mut |e, &()| process_element(e))
136 });
137
138 for (target, sources) in &callback_aliases {
139 if sources.len() < 2 {
140 continue;
141 }
142 let has_implementation = property_sets.map.get(target).is_some_and(|set_rc| {
144 set_rc.borrow().iter().any(|nr| {
145 let elem = nr.element();
146 let elem = elem.borrow();
147 elem.binding(nr.name())
148 .is_some_and(|b| !matches!(b.value_expression(), Expression::Invalid))
149 })
150 });
151 if has_implementation {
152 continue;
153 }
154 for nr in sources {
155 let other_names = sources
156 .iter()
157 .filter(|other| *other != nr)
158 .map(|other| format!("'{}'", other.name()))
159 .collect::<Vec<_>>()
160 .join(", ");
161 let elem = nr.element();
162 let elem = elem.borrow();
163 if let Some(b) = elem.binding(nr.name()) {
164 diag.push_warning(
166 format!(
167 "Callback '{}' shares a handler slot with {other_names}, so only one of them can have an implementation",
168 nr.name()
169 ),
170 &*b,
171 );
172 }
173 }
174 }
175
176 let mut aliases_to_remove = Mapping::new();
178
179 for set_rc in property_sets.all_sets {
182 let set = set_rc.borrow();
183
184 let implementers: Vec<NamedReference> = set
189 .iter()
190 .filter(|nr| {
191 if !matches!(nr.ty(), Type::Callback(..)) {
192 return false;
193 }
194 let elem = nr.element();
195 let elem = elem.borrow();
196 elem.enclosing_component.upgrade().is_some_and(|c| c.is_global())
197 && elem
198 .binding(nr.name())
199 .is_some_and(|b| !matches!(b.value_expression(), Expression::Invalid))
200 })
201 .cloned()
202 .collect();
203 if implementers.len() > 1 {
204 for nr in &implementers {
205 let elem = nr.element();
206 let elem = elem.borrow();
207 if let Some(b) = elem.binding(nr.name()) {
208 diag.push_error(
209 format!("Callback '{}' is implemented in more than one global", nr.name()),
210 &*b,
211 );
212 }
213 }
214 }
215
216 let mut set_iter = set.iter();
217 if let Some(mut best) = set_iter.next().cloned() {
218 for candidate in set_iter {
219 best = best_property(best.clone(), candidate.clone());
220 }
221 let best_is_global =
222 best.element().borrow().enclosing_component.upgrade().unwrap().is_global();
223 for x in set.iter() {
224 if *x != best {
225 let elem = x.element();
229 let elem = elem.borrow();
230 let carries_local_state = elem.change_callbacks.contains_key(x.name())
231 || elem
232 .binding(x.name())
233 .is_some_and(|binding| binding.animation.is_some());
234 if best_is_global
235 && !elem.enclosing_component.upgrade().unwrap().is_global()
236 && carries_local_state
237 {
238 continue;
239 }
240 aliases_to_remove.insert(x.clone(), (best.clone(), Rc::clone(&set_rc)));
241 }
242 }
243 }
244 }
245
246 doc.visit_all_used_components(|component| {
247 visit_all_named_references(component, &mut |nr: &mut NamedReference| {
249 if let Some((new, _set)) = aliases_to_remove.get(nr) {
250 *nr = new.clone();
251 }
252 })
253 });
254
255 let mut aliases_to_remove = aliases_to_remove.into_iter().collect::<Vec<_>>();
258 aliases_to_remove.sort_by_cached_key(|(remove, _)| {
259 (remove.element().borrow().id.clone(), remove.name().clone())
260 });
261 for (remove, (to, set)) in aliases_to_remove {
262 let elem = remove.element();
263 let to_elem = to.element();
264
265 let old_binding = elem.borrow_mut().take_binding(remove.name());
267 let mut old_binding = old_binding.unwrap_or_else(|| {
268 let mut b = BindingExpression::from(Expression::default_value_for_type(&to.ty()));
271 b.priority = to_elem
272 .borrow()
273 .binding(to.name())
274 .map_or(i32::MAX, |to_binding| to_binding.priority.saturating_add(1));
275 b
276 });
277
278 remove_from_binding_expression(&mut old_binding, &to);
279
280 if to_elem.borrow().enclosing_component.upgrade().unwrap().is_global() {
304 old_binding.two_way_bindings.retain(|twb| {
305 let TwoWayBinding::Property { property, field_access } = twb else { return true };
306 if !field_access.is_empty() || set.borrow().contains(property) {
307 return true;
310 }
311 let target_elem = property.element();
312 let mut target_elem = target_elem.borrow_mut();
313 let mut target_binding = if let Some(b) = target_elem.binding_mut(property.name()) {
314 b
317 } else {
318 target_elem.set_binding(
319 property.name().clone(),
320 BindingExpression::new_two_way(to.clone().into()),
321 );
322 target_elem.binding_mut(property.name()).unwrap()
323 };
324 if !target_binding.two_way_bindings.iter().any(|x| x.property() == Some(&to)) {
326 target_binding.two_way_bindings.push(to.clone().into());
327 }
328 false
330 });
331 }
332
333 let same_component = std::rc::Weak::ptr_eq(
334 &elem.borrow().enclosing_component,
335 &to_elem.borrow().enclosing_component,
336 );
337 let both_global =
340 elem.borrow().enclosing_component.upgrade().is_some_and(|c| c.is_global())
341 && to_elem.borrow().enclosing_component.upgrade().is_some_and(|c| c.is_global());
342 {
343 let mut to_elem = to_elem.borrow_mut();
344 if let Some(mut b) = to_elem.binding_mut(to.name()) {
347 let b = &mut *b;
348 remove_from_binding_expression(b, &to);
349 if !same_component || b.priority < old_binding.priority || !b.has_binding() {
350 b.merge_with(&old_binding);
351 } else {
352 old_binding.merge_with(b);
353 *b = old_binding;
354 }
355 } else {
356 if (same_component || both_global)
357 && (old_binding.has_binding() || old_binding.animation.is_some())
358 {
359 to_elem.set_binding(to.name().clone(), old_binding);
360 }
361 }
362 }
363
364 {
366 let mut elem = elem.borrow_mut();
367 if let Some(old_change_callback) = elem.change_callbacks.remove(remove.name()) {
368 drop(elem);
369 let mut old_change_callback = old_change_callback.into_inner();
370 to_elem
371 .borrow_mut()
372 .change_callbacks
373 .entry(to.name().clone())
374 .or_default()
375 .borrow_mut()
376 .append(&mut old_change_callback);
377 }
378 }
379
380 {
382 let mut elem = elem.borrow_mut();
383 let used_externally = elem
384 .property_analysis
385 .borrow()
386 .get(remove.name())
387 .is_some_and(|v| v.is_read_externally || v.is_set_externally);
388 if let Some(d) = elem.property_declarations.get_mut(remove.name()) {
389 if d.expose_in_public_api || used_externally {
390 d.is_alias = Some(to.clone());
391 drop(elem);
392 to.mark_as_set();
394 } else {
395 elem.property_declarations.remove(remove.name());
396 let analysis = elem.property_analysis.borrow().get(remove.name()).cloned();
397 if let Some(analysis) = analysis {
398 drop(elem);
399 to.element()
400 .borrow()
401 .property_analysis
402 .borrow_mut()
403 .entry(to.name().clone())
404 .or_default()
405 .merge(&analysis);
406 };
407 }
408 } else {
409 elem.set_binding(
411 remove.name().clone(),
412 BindingExpression::new_two_way(to.clone().into()),
413 );
414 drop(elem);
415 if remove.is_externally_modified() {
416 to.mark_as_set();
417 }
418 }
419 }
420 }
421}
422
423fn is_declaration(x: &NamedReference) -> bool {
424 x.element().borrow().property_declarations.contains_key(x.name())
425}
426
427fn best_property(p1: NamedReference, p2: NamedReference) -> NamedReference {
429 macro_rules! canonical_order {
431 ($x: expr) => {{
432 (
433 !$x.element().borrow().enclosing_component.upgrade().unwrap().is_global(),
434 is_declaration(&$x),
435 $x.element().borrow().id.clone(),
436 $x.name(),
437 )
438 }};
439 }
440
441 if canonical_order!(p1) < canonical_order!(p2) { p1 } else { p2 }
442}
443
444fn remove_from_binding_expression(expression: &mut BindingExpression, to: &NamedReference) {
446 expression.two_way_bindings.retain(|x| x.property() != Some(to));
447}