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