Skip to main content

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