i_slint_compiler/passes/
binding_analysis.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//! Compute binding analysis and attempt to find binding loops
5
6use std::collections::HashMap;
7use std::collections::HashSet;
8use std::rc::Rc;
9
10use by_address::ByAddress;
11
12use crate::diagnostics::{BuildDiagnostics, Spanned};
13use crate::expression_tree::{BindingExpression, BuiltinFunction, Expression};
14use crate::langtype::ElementType;
15use crate::layout::{LayoutItem, Orientation};
16use crate::namedreference::NamedReference;
17use crate::object_tree::{find_parent_element, Document, ElementRc, PropertyAnimation};
18use derive_more as dm;
19
20use crate::expression_tree::Callable;
21use crate::CompilerConfiguration;
22use smol_str::{SmolStr, ToSmolStr};
23
24/// Maps the alias in the other direction than what the BindingExpression::two_way_binding does.
25/// So if binding for property A has B in its BindingExpression::two_way_binding, then
26/// ReverseAliases maps B to A.
27type ReverseAliases = HashMap<NamedReference, Vec<NamedReference>>;
28
29pub fn binding_analysis(
30    doc: &Document,
31    compiler_config: &CompilerConfiguration,
32    diag: &mut BuildDiagnostics,
33) {
34    let mut reverse_aliases = Default::default();
35    mark_used_base_properties(doc);
36    propagate_is_set_on_aliases(doc, &mut reverse_aliases);
37    perform_binding_analysis(
38        doc,
39        &reverse_aliases,
40        compiler_config.error_on_binding_loop_with_window_layout,
41        diag,
42    );
43}
44
45/// A reference to a property which might be deep in a component path.
46/// eg: `foo.bar.baz.background`: `baz.background` is the `prop` and `foo` and `bar` are in elements
47#[derive(Hash, PartialEq, Eq, Clone)]
48struct PropertyPath {
49    elements: Vec<ByAddress<ElementRc>>,
50    prop: NamedReference,
51}
52
53impl std::fmt::Debug for PropertyPath {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        for e in &self.elements {
56            write!(f, "{}.", e.borrow().id)?;
57        }
58        self.prop.fmt(f)
59    }
60}
61
62impl PropertyPath {
63    /// Given a namedReference accessed by something on the same leaf component
64    /// as self, return a new PropertyPath that represent the property pointer
65    /// to by nr in the higher possible element
66    fn relative(&self, second: &PropertyPath) -> Self {
67        let mut element =
68            second.elements.first().map_or_else(|| second.prop.element(), |f| f.0.clone());
69        if element.borrow().enclosing_component.upgrade().unwrap().is_global() {
70            return second.clone();
71        }
72        let mut elements = self.elements.clone();
73        loop {
74            let enclosing = element.borrow().enclosing_component.upgrade().unwrap();
75            if enclosing.parent_element.upgrade().is_some()
76                || !Rc::ptr_eq(&element, &enclosing.root_element)
77            {
78                break;
79            }
80
81            if let Some(last) = elements.pop() {
82                #[cfg(debug_assertions)]
83                fn check_that_element_is_in_the_component(
84                    e: &ElementRc,
85                    c: &Rc<crate::object_tree::Component>,
86                ) -> bool {
87                    let enclosing = e.borrow().enclosing_component.upgrade().unwrap();
88                    Rc::ptr_eq(c, &enclosing)
89                        || enclosing
90                            .parent_element
91                            .upgrade()
92                            .is_some_and(|e| check_that_element_is_in_the_component(&e, c))
93                }
94                #[cfg(debug_assertions)]
95                debug_assert!(
96                    check_that_element_is_in_the_component(
97                        &element,
98                        last.borrow().base_type.as_component()
99                    ),
100                    "The element is not in the component pointed at by the path ({self:?} / {second:?})"
101                );
102                element = last.0;
103            } else {
104                break;
105            }
106        }
107        if second.elements.is_empty() {
108            debug_assert!(elements.last().map_or(true, |x| *x != ByAddress(second.prop.element())));
109            Self { elements, prop: NamedReference::new(&element, second.prop.name().clone()) }
110        } else {
111            elements.push(ByAddress(element));
112            elements.extend(second.elements.iter().skip(1).cloned());
113            Self { elements, prop: second.prop.clone() }
114        }
115    }
116}
117
118impl From<NamedReference> for PropertyPath {
119    fn from(prop: NamedReference) -> Self {
120        Self { elements: vec![], prop }
121    }
122}
123
124#[derive(Default)]
125struct AnalysisContext {
126    visited: HashSet<PropertyPath>,
127    /// The stack of properties that depends on each other
128    currently_analyzing: linked_hash_set::LinkedHashSet<PropertyPath>,
129    /// When set, one of the property in the `currently_analyzing` stack is the window layout property
130    /// And we should issue a warning if that's part of a loop instead of an error
131    window_layout_property: Option<PropertyPath>,
132    error_on_binding_loop_with_window_layout: bool,
133}
134
135fn perform_binding_analysis(
136    doc: &Document,
137    reverse_aliases: &ReverseAliases,
138    error_on_binding_loop_with_window_layout: bool,
139    diag: &mut BuildDiagnostics,
140) {
141    let mut context =
142        AnalysisContext { error_on_binding_loop_with_window_layout, ..Default::default() };
143    doc.visit_all_used_components(|component| {
144        crate::object_tree::recurse_elem_including_sub_components_no_borrow(
145            component,
146            &(),
147            &mut |e, _| analyze_element(e, &mut context, reverse_aliases, diag),
148        )
149    });
150}
151
152fn analyze_element(
153    elem: &ElementRc,
154    context: &mut AnalysisContext,
155    reverse_aliases: &ReverseAliases,
156    diag: &mut BuildDiagnostics,
157) {
158    for (name, binding) in &elem.borrow().bindings {
159        if binding.borrow().analysis.is_some() {
160            continue;
161        }
162        analyze_binding(
163            &PropertyPath::from(NamedReference::new(elem, name.clone())),
164            context,
165            reverse_aliases,
166            diag,
167        );
168    }
169    for cb in elem.borrow().change_callbacks.values() {
170        for e in cb.borrow().iter() {
171            recurse_expression(e, &mut |prop, r| {
172                process_property(prop, r, context, reverse_aliases, diag);
173            });
174        }
175    }
176    const P: ReadType = ReadType::PropertyRead;
177    for nr in elem.borrow().accessibility_props.0.values() {
178        process_property(&PropertyPath::from(nr.clone()), P, context, reverse_aliases, diag);
179    }
180    if let Some(g) = elem.borrow().geometry_props.as_ref() {
181        process_property(&g.x.clone().into(), P, context, reverse_aliases, diag);
182        process_property(&g.y.clone().into(), P, context, reverse_aliases, diag);
183        process_property(&g.width.clone().into(), P, context, reverse_aliases, diag);
184        process_property(&g.height.clone().into(), P, context, reverse_aliases, diag);
185    }
186
187    if let Some(component) = elem.borrow().enclosing_component.upgrade() {
188        if Rc::ptr_eq(&component.root_element, elem) {
189            for e in component.init_code.borrow().iter() {
190                recurse_expression(e, &mut |prop, r| {
191                    process_property(prop, r, context, reverse_aliases, diag);
192                });
193            }
194            component.root_constraints.borrow_mut().visit_named_references(&mut |nr| {
195                process_property(&nr.clone().into(), P, context, reverse_aliases, diag);
196            });
197            component.popup_windows.borrow().iter().for_each(|p| {
198                process_property(&p.x.clone().into(), P, context, reverse_aliases, diag);
199                process_property(&p.y.clone().into(), P, context, reverse_aliases, diag);
200            });
201            component.timers.borrow().iter().for_each(|t| {
202                process_property(&t.interval.clone().into(), P, context, reverse_aliases, diag);
203                process_property(&t.running.clone().into(), P, context, reverse_aliases, diag);
204                process_property(&t.triggered.clone().into(), P, context, reverse_aliases, diag);
205            });
206        }
207    }
208
209    if let Some(repeated) = &elem.borrow().repeated {
210        recurse_expression(&repeated.model, &mut |prop, r| {
211            process_property(prop, r, context, reverse_aliases, diag);
212        });
213        if let Some(lv) = &repeated.is_listview {
214            process_property(&lv.viewport_y.clone().into(), P, context, reverse_aliases, diag);
215            process_property(&lv.viewport_height.clone().into(), P, context, reverse_aliases, diag);
216            process_property(&lv.viewport_width.clone().into(), P, context, reverse_aliases, diag);
217            process_property(&lv.listview_height.clone().into(), P, context, reverse_aliases, diag);
218            process_property(&lv.listview_width.clone().into(), P, context, reverse_aliases, diag);
219        }
220    }
221    if let Some((h, v)) = &elem.borrow().layout_info_prop {
222        process_property(&h.clone().into(), P, context, reverse_aliases, diag);
223        process_property(&v.clone().into(), P, context, reverse_aliases, diag);
224    }
225}
226
227#[derive(Copy, Clone, dm::BitAnd, dm::BitOr, dm::BitAndAssign, dm::BitOrAssign)]
228struct DependsOnExternal(bool);
229
230fn analyze_binding(
231    current: &PropertyPath,
232    context: &mut AnalysisContext,
233    reverse_aliases: &ReverseAliases,
234    diag: &mut BuildDiagnostics,
235) -> DependsOnExternal {
236    let mut depends_on_external = DependsOnExternal(false);
237    let element = current.prop.element();
238    let name = current.prop.name();
239    if (context.currently_analyzing.back() == Some(current))
240        && !element.borrow().bindings[name].borrow().two_way_bindings.is_empty()
241    {
242        let span = element.borrow().bindings[name]
243            .borrow()
244            .span
245            .clone()
246            .unwrap_or_else(|| element.borrow().to_source_location());
247        diag.push_error(format!("Property '{name}' cannot refer to itself"), &span);
248        return depends_on_external;
249    }
250
251    if context.currently_analyzing.contains(current) {
252        let mut loop_description = String::new();
253        let mut has_window_layout = false;
254        for it in context.currently_analyzing.iter().rev() {
255            if context.window_layout_property.as_ref().is_some_and(|p| p == it) {
256                has_window_layout = true;
257            }
258            if !loop_description.is_empty() {
259                loop_description.push_str(" -> ");
260            }
261            match it.prop.element().borrow().id.as_str() {
262                "" => loop_description.push_str(it.prop.name()),
263                id => {
264                    loop_description.push_str(id);
265                    loop_description.push_str(".");
266                    loop_description.push_str(it.prop.name());
267                }
268            }
269            if it == current {
270                break;
271            }
272        }
273
274        for it in context.currently_analyzing.iter().rev() {
275            let p = &it.prop;
276            let elem = p.element();
277            let elem = elem.borrow();
278            let binding = elem.bindings[p.name()].borrow();
279            if binding.analysis.as_ref().unwrap().is_in_binding_loop.replace(true) {
280                break;
281            }
282
283            let span = binding.span.clone().unwrap_or_else(|| elem.to_source_location());
284            if !context.error_on_binding_loop_with_window_layout && has_window_layout {
285                diag.push_warning(format!("The binding for the property '{}' is part of a binding loop ({loop_description}).\nThis was allowed in previous version of Slint, but is deprecated and may cause panic at runtime", p.name()), &span);
286            } else {
287                diag.push_error(format!("The binding for the property '{}' is part of a binding loop ({loop_description})", p.name()), &span);
288            }
289            if it == current {
290                break;
291            }
292        }
293        return depends_on_external;
294    }
295
296    let binding = &element.borrow().bindings[name];
297    if binding.borrow().analysis.as_ref().is_some_and(|a| a.no_external_dependencies) {
298        return depends_on_external;
299    } else if !context.visited.insert(current.clone()) {
300        return DependsOnExternal(true);
301    }
302
303    if let Ok(mut b) = binding.try_borrow_mut() {
304        b.analysis = Some(Default::default());
305    };
306    context.currently_analyzing.insert(current.clone());
307
308    let b = binding.borrow();
309    for nr in &b.two_way_bindings {
310        if nr != &current.prop {
311            depends_on_external |= process_property(
312                &current.relative(&nr.clone().into()),
313                ReadType::PropertyRead,
314                context,
315                reverse_aliases,
316                diag,
317            );
318        }
319    }
320
321    let mut process_prop = |prop: &PropertyPath, r| {
322        depends_on_external |=
323            process_property(&current.relative(prop), r, context, reverse_aliases, diag);
324        for x in reverse_aliases.get(&prop.prop).unwrap_or(&Default::default()) {
325            if x != &current.prop && x != &prop.prop {
326                depends_on_external |= process_property(
327                    &current.relative(&x.clone().into()),
328                    ReadType::PropertyRead,
329                    context,
330                    reverse_aliases,
331                    diag,
332                );
333            }
334        }
335    };
336
337    recurse_expression(&b.expression, &mut process_prop);
338
339    let mut is_const =
340        b.expression.is_constant() && b.two_way_bindings.iter().all(|n| n.is_constant());
341
342    if is_const && matches!(b.expression, Expression::Invalid) {
343        // check the base
344        if let Some(base) = element.borrow().sub_component() {
345            is_const = NamedReference::new(&base.root_element, name.clone()).is_constant();
346        }
347    }
348    drop(b);
349
350    if let Ok(mut b) = binding.try_borrow_mut() {
351        // We have a loop (through different component so we're still borrowed)
352        b.analysis.as_mut().unwrap().is_const = is_const;
353    }
354
355    match &binding.borrow().animation {
356        Some(PropertyAnimation::Static(e)) => analyze_element(e, context, reverse_aliases, diag),
357        Some(PropertyAnimation::Transition { animations, state_ref }) => {
358            recurse_expression(state_ref, &mut process_prop);
359            for a in animations {
360                analyze_element(&a.animation, context, reverse_aliases, diag);
361            }
362        }
363        None => (),
364    }
365
366    let o = context.currently_analyzing.pop_back();
367    assert_eq!(&o.unwrap(), current);
368
369    depends_on_external
370}
371
372#[derive(Copy, Clone, Eq, PartialEq)]
373enum ReadType {
374    // Read from the native code
375    NativeRead,
376    // Read from another property binding in Slint
377    PropertyRead,
378}
379
380/// Process the property `prop`
381///
382/// This will visit all the bindings from that property
383fn process_property(
384    prop: &PropertyPath,
385    read_type: ReadType,
386    context: &mut AnalysisContext,
387    reverse_aliases: &ReverseAliases,
388    diag: &mut BuildDiagnostics,
389) -> DependsOnExternal {
390    let depends_on_external = match prop
391        .prop
392        .element()
393        .borrow()
394        .property_analysis
395        .borrow_mut()
396        .entry(prop.prop.name().clone())
397        .or_default()
398    {
399        a => {
400            if read_type == ReadType::PropertyRead {
401                a.is_read = true;
402            }
403            DependsOnExternal(prop.elements.is_empty() && a.is_set_externally)
404        }
405    };
406
407    let mut prop = prop.clone();
408
409    loop {
410        let element = prop.prop.element();
411        if element.borrow().bindings.contains_key(prop.prop.name()) {
412            analyze_binding(&prop, context, reverse_aliases, diag);
413        }
414        let next = match &element.borrow().base_type {
415            ElementType::Component(base) => {
416                if element.borrow().property_declarations.contains_key(prop.prop.name()) {
417                    break;
418                }
419                base.root_element.clone()
420            }
421            ElementType::Builtin(builtin) => {
422                if builtin.properties.contains_key(prop.prop.name()) {
423                    visit_builtin_property(builtin, &prop, context, reverse_aliases, diag);
424                }
425                break;
426            }
427            _ => break,
428        };
429        next.borrow()
430            .property_analysis
431            .borrow_mut()
432            .entry(prop.prop.name().clone())
433            .or_default()
434            .is_read_externally = true;
435        prop.elements.push(element.into());
436        prop.prop = NamedReference::new(&next, prop.prop.name().clone());
437    }
438    depends_on_external
439}
440
441// Same as in crate::visit_all_named_references_in_element, but not mut
442fn recurse_expression(expr: &Expression, vis: &mut impl FnMut(&PropertyPath, ReadType)) {
443    const P: ReadType = ReadType::PropertyRead;
444    expr.visit(|sub| recurse_expression(sub, vis));
445    match expr {
446        Expression::PropertyReference(r) => vis(&r.clone().into(), P),
447        Expression::LayoutCacheAccess { layout_cache_prop, .. } => {
448            vis(&layout_cache_prop.clone().into(), P)
449        }
450        Expression::SolveLayout(l, o) | Expression::ComputeLayoutInfo(l, o) => {
451            // we should only visit the layout geometry for the orientation
452            if matches!(expr, Expression::SolveLayout(..)) {
453                if let Some(nr) = l.rect().size_reference(*o) {
454                    vis(&nr.clone().into(), P);
455                }
456            }
457            match l {
458                crate::layout::Layout::GridLayout(l) => {
459                    visit_layout_items_dependencies(l.elems.iter().map(|it| &it.item), *o, vis)
460                }
461                crate::layout::Layout::BoxLayout(l) => {
462                    visit_layout_items_dependencies(l.elems.iter(), *o, vis)
463                }
464            }
465
466            let mut g = l.geometry().clone();
467            g.rect = Default::default(); // already visited;
468            g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
469        }
470        Expression::FunctionCall {
471            function: Callable::Callback(nr) | Callable::Function(nr),
472            ..
473        } => vis(&nr.clone().into(), P),
474        Expression::FunctionCall { function: Callable::Builtin(b), arguments, .. } => match b {
475            BuiltinFunction::ImplicitLayoutInfo(orientation) => {
476                if let [Expression::ElementReference(item)] = arguments.as_slice() {
477                    visit_implicit_layout_info_dependencies(
478                        *orientation,
479                        &item.upgrade().unwrap(),
480                        vis,
481                    );
482                }
483            }
484            BuiltinFunction::ItemAbsolutePosition => {
485                if let Some(Expression::ElementReference(item)) = arguments.first() {
486                    let mut item = item.upgrade().unwrap();
487                    while let Some(parent) = find_parent_element(&item) {
488                        item = parent;
489                        vis(
490                            &NamedReference::new(&item, SmolStr::new_static("x")).into(),
491                            ReadType::NativeRead,
492                        );
493                        vis(
494                            &NamedReference::new(&item, SmolStr::new_static("y")).into(),
495                            ReadType::NativeRead,
496                        );
497                    }
498                }
499            }
500            BuiltinFunction::ItemFontMetrics => {
501                if let Some(Expression::ElementReference(item)) = arguments.first() {
502                    let item = item.upgrade().unwrap();
503                    vis(
504                        &NamedReference::new(&item, SmolStr::new_static("font-size")).into(),
505                        ReadType::NativeRead,
506                    );
507                    vis(
508                        &NamedReference::new(&item, SmolStr::new_static("font-weight")).into(),
509                        ReadType::NativeRead,
510                    );
511                    vis(
512                        &NamedReference::new(&item, SmolStr::new_static("font-family")).into(),
513                        ReadType::NativeRead,
514                    );
515                    vis(
516                        &NamedReference::new(&item, SmolStr::new_static("font-italic")).into(),
517                        ReadType::NativeRead,
518                    );
519                }
520            }
521            _ => {}
522        },
523        _ => {}
524    }
525}
526
527fn visit_layout_items_dependencies<'a>(
528    items: impl Iterator<Item = &'a LayoutItem>,
529    orientation: Orientation,
530    vis: &mut impl FnMut(&PropertyPath, ReadType),
531) {
532    for it in items {
533        let mut element = it.element.clone();
534        if element.borrow().repeated.as_ref().map(|r| recurse_expression(&r.model, vis)).is_some() {
535            element = it.element.borrow().base_type.as_component().root_element.clone();
536        }
537
538        if let Some(nr) = element.borrow().layout_info_prop(orientation) {
539            vis(&nr.clone().into(), ReadType::PropertyRead);
540        } else {
541            if let ElementType::Component(base) = &element.borrow().base_type {
542                if let Some(nr) = base.root_element.borrow().layout_info_prop(orientation) {
543                    vis(
544                        &PropertyPath {
545                            elements: vec![ByAddress(element.clone())],
546                            prop: nr.clone(),
547                        },
548                        ReadType::PropertyRead,
549                    );
550                }
551            }
552            visit_implicit_layout_info_dependencies(orientation, &element, vis);
553        }
554
555        for (nr, _) in it.constraints.for_each_restrictions(orientation) {
556            vis(&nr.clone().into(), ReadType::PropertyRead)
557        }
558    }
559}
560
561/// The builtin function can call native code, and we need to visit the properties that are accessed by it
562fn visit_implicit_layout_info_dependencies(
563    orientation: crate::layout::Orientation,
564    item: &ElementRc,
565    vis: &mut impl FnMut(&PropertyPath, ReadType),
566) {
567    let base_type = item.borrow().base_type.to_smolstr();
568    const N: ReadType = ReadType::NativeRead;
569    match base_type.as_str() {
570        "Image" => {
571            vis(&NamedReference::new(item, SmolStr::new_static("source")).into(), N);
572            if orientation == Orientation::Vertical {
573                vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
574            }
575        }
576        "Text" | "TextInput" => {
577            vis(&NamedReference::new(item, SmolStr::new_static("text")).into(), N);
578            vis(&NamedReference::new(item, SmolStr::new_static("font-family")).into(), N);
579            vis(&NamedReference::new(item, SmolStr::new_static("font-size")).into(), N);
580            vis(&NamedReference::new(item, SmolStr::new_static("font-weight")).into(), N);
581            vis(&NamedReference::new(item, SmolStr::new_static("letter-spacing")).into(), N);
582            vis(&NamedReference::new(item, SmolStr::new_static("wrap")).into(), N);
583            let wrap_set = item.borrow().is_binding_set("wrap", false)
584                || item
585                    .borrow()
586                    .property_analysis
587                    .borrow()
588                    .get("wrap")
589                    .is_some_and(|a| a.is_set || a.is_set_externally);
590            if wrap_set && orientation == Orientation::Vertical {
591                vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
592            }
593            if base_type.as_str() == "TextInput" {
594                vis(&NamedReference::new(item, SmolStr::new_static("single-line")).into(), N);
595            } else {
596                vis(&NamedReference::new(item, SmolStr::new_static("overflow")).into(), N);
597            }
598        }
599
600        _ => (),
601    }
602}
603
604fn visit_builtin_property(
605    builtin: &crate::langtype::BuiltinElement,
606    prop: &PropertyPath,
607    context: &mut AnalysisContext,
608    reverse_aliases: &ReverseAliases,
609    diag: &mut BuildDiagnostics,
610) {
611    let name = prop.prop.name();
612    if builtin.name == "Window" {
613        for (p, orientation) in
614            [("width", Orientation::Horizontal), ("height", Orientation::Vertical)]
615        {
616            if name == p {
617                // find the actual root component
618                let is_root = |e: &ElementRc| -> bool {
619                    ElementRc::ptr_eq(
620                        e,
621                        &e.borrow().enclosing_component.upgrade().unwrap().root_element,
622                    )
623                };
624                let mut root = prop.prop.element();
625                if !is_root(&root) {
626                    return;
627                };
628                for e in prop.elements.iter().rev() {
629                    if !is_root(&e.0) {
630                        return;
631                    }
632                    root = e.0.clone();
633                }
634                if let Some(p) = root.borrow().layout_info_prop(orientation) {
635                    let path = PropertyPath::from(p.clone());
636                    let old_layout = context.window_layout_property.replace(path.clone());
637                    process_property(&path, ReadType::NativeRead, context, reverse_aliases, diag);
638                    context.window_layout_property = old_layout;
639                };
640            }
641        }
642    }
643}
644
645/// Make sure that the is_set property analysis is set to any property which has a two way binding
646/// to a property that is, itself, is set
647///
648/// Example:
649/// ```slint
650/// Xx := TouchArea {
651///    property <int> bar <=> foo;
652///    clicked => { bar+=1; }
653///    property <int> foo; // must ensure that this is not considered as const, because the alias with bar
654/// }
655/// ```
656fn propagate_is_set_on_aliases(doc: &Document, reverse_aliases: &mut ReverseAliases) {
657    doc.visit_all_used_components(|component| {
658        crate::object_tree::recurse_elem_including_sub_components_no_borrow(
659            component,
660            &(),
661            &mut |e, _| visit_element(e, reverse_aliases),
662        );
663    });
664
665    fn visit_element(e: &ElementRc, reverse_aliases: &mut ReverseAliases) {
666        for (name, binding) in &e.borrow().bindings {
667            if !binding.borrow().two_way_bindings.is_empty() {
668                check_alias(e, name, &binding.borrow());
669
670                let nr = NamedReference::new(e, name.clone());
671                for a in &binding.borrow().two_way_bindings {
672                    if a != &nr
673                        && !a.element().borrow().enclosing_component.upgrade().unwrap().is_global()
674                    {
675                        reverse_aliases.entry(a.clone()).or_default().push(nr.clone())
676                    }
677                }
678            }
679        }
680        for decl in e.borrow().property_declarations.values() {
681            if let Some(alias) = &decl.is_alias {
682                mark_alias(alias)
683            }
684        }
685    }
686
687    fn check_alias(e: &ElementRc, name: &SmolStr, binding: &BindingExpression) {
688        // Note: since the analysis hasn't been run, any property access will result in a non constant binding. this is slightly non-optimal
689        let is_binding_constant =
690            binding.is_constant() && binding.two_way_bindings.iter().all(|n| n.is_constant());
691        if is_binding_constant && !NamedReference::new(e, name.clone()).is_externally_modified() {
692            for alias in &binding.two_way_bindings {
693                crate::namedreference::mark_property_set_derived_in_base(
694                    alias.element(),
695                    alias.name(),
696                );
697            }
698            return;
699        }
700
701        propagate_alias(binding);
702    }
703
704    fn propagate_alias(binding: &BindingExpression) {
705        for alias in &binding.two_way_bindings {
706            mark_alias(alias);
707        }
708    }
709
710    fn mark_alias(alias: &NamedReference) {
711        alias.mark_as_set();
712        if !alias.is_externally_modified() {
713            if let Some(bind) = alias.element().borrow().bindings.get(alias.name()) {
714                propagate_alias(&bind.borrow())
715            }
716        }
717    }
718}
719
720/// Make sure that the is_set_externally is true for all bindings.
721/// And change bindings are used externally
722fn mark_used_base_properties(doc: &Document) {
723    doc.visit_all_used_components(|component| {
724        crate::object_tree::recurse_elem_including_sub_components_no_borrow(
725            component,
726            &(),
727            &mut |element, _| {
728                if !matches!(element.borrow().base_type, ElementType::Component(_)) {
729                    return;
730                }
731                for (name, binding) in &element.borrow().bindings {
732                    if binding.borrow().has_binding() {
733                        crate::namedreference::mark_property_set_derived_in_base(
734                            element.clone(),
735                            name,
736                        );
737                    }
738                }
739                for name in element.borrow().change_callbacks.keys() {
740                    crate::namedreference::mark_property_read_derived_in_base(
741                        element.clone(),
742                        name,
743                    );
744                }
745            },
746        );
747    });
748}