1use 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#[derive(Debug, Clone, PartialEq, Default)]
26pub enum DefaultFontSize {
27 #[default]
29 Unknown,
30 LogicalValue(f32),
32 Const,
34 NotSet,
36 Variable,
38}
39impl DefaultFontSize {
40 pub fn is_const(&self) -> bool {
42 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 pub const_image_sizes: bool,
52}
53
54type ReverseAliases = HashMap<NamedReference, Vec<NamedReference>>;
58
59pub fn binding_analysis(
60 doc: &Document,
61 compiler_config: &CompilerConfiguration,
62 diag: &mut BuildDiagnostics,
63) -> GlobalAnalysis {
64 let mut global_analysis = GlobalAnalysis {
65 const_scale_factor: compiler_config.const_scale_factor,
66 const_image_sizes: compiler_config.const_image_sizes,
67 ..Default::default()
68 };
69 let mut reverse_aliases = Default::default();
70 mark_used_base_properties(doc);
71 propagate_is_set_on_aliases(doc, &mut reverse_aliases);
72 check_window_properties(doc, &mut global_analysis);
73 perform_binding_analysis(
74 doc,
75 &reverse_aliases,
76 &mut global_analysis,
77 compiler_config.error_on_binding_loop_with_window_layout,
78 diag,
79 );
80 global_analysis
81}
82#[derive(Hash, PartialEq, Eq, Clone)]
85struct PropertyPath {
86 elements: Vec<ByAddress<ElementRc>>,
87 prop: NamedReference,
88}
89
90impl std::fmt::Debug for PropertyPath {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 for e in &self.elements {
93 write!(f, "{}.", e.borrow().id)?;
94 }
95 self.prop.fmt(f)
96 }
97}
98
99impl PropertyPath {
100 fn relative(&self, second: &PropertyPath) -> Self {
104 let mut element =
105 second.elements.first().map_or_else(|| second.prop.element(), |f| f.0.clone());
106 if element.borrow().enclosing_component.upgrade().unwrap().is_global() {
107 return second.clone();
108 }
109 fn check_that_element_is_in_the_component(
110 e: &ElementRc,
111 c: &Rc<crate::object_tree::Component>,
112 ) -> bool {
113 let enclosing = e.borrow().enclosing_component.upgrade().unwrap();
114 Rc::ptr_eq(c, &enclosing)
115 || enclosing
116 .parent_element
117 .borrow()
118 .upgrade()
119 .is_some_and(|e| check_that_element_is_in_the_component(&e, c))
120 }
121 let mut elements = self.elements.clone();
122 loop {
123 let enclosing = element.borrow().enclosing_component.upgrade().unwrap();
124 if enclosing.parent_element().is_some()
125 || !Rc::ptr_eq(&element, &enclosing.root_element)
126 {
127 break;
128 }
129
130 let Some(last) = elements.last() else {
131 break;
132 };
133 let last_component = last.borrow().base_type.as_component().clone();
134 if !check_that_element_is_in_the_component(&element, &last_component) {
135 debug_assert!(
142 check_that_element_is_in_the_component(
143 &last_component.root_element,
144 &enclosing
145 ),
146 "The element is not in the component pointed at by the path ({self:?} / {second:?})"
147 );
148 return second.clone();
149 }
150 element = elements.pop().unwrap().0;
151 }
152 if second.elements.is_empty() {
153 debug_assert!(elements.last().is_none_or(|x| *x != ByAddress(second.prop.element())));
154 Self { elements, prop: NamedReference::new(&element, second.prop.name().clone()) }
155 } else {
156 elements.push(ByAddress(element));
157 elements.extend(second.elements.iter().skip(1).cloned());
158 Self { elements, prop: second.prop.clone() }
159 }
160 }
161}
162
163impl From<NamedReference> for PropertyPath {
164 fn from(prop: NamedReference) -> Self {
165 Self { elements: Vec::new(), prop }
166 }
167}
168
169struct AnalysisContext<'a> {
170 visited: HashSet<PropertyPath>,
171 currently_analyzing: indexmap::IndexSet<PropertyPath>,
173 window_layout_property: Option<PropertyPath>,
176 error_on_binding_loop_with_window_layout: bool,
177 global_analysis: &'a mut GlobalAnalysis,
178}
179
180fn perform_binding_analysis(
181 doc: &Document,
182 reverse_aliases: &ReverseAliases,
183 global_analysis: &mut GlobalAnalysis,
184 error_on_binding_loop_with_window_layout: bool,
185 diag: &mut BuildDiagnostics,
186) {
187 let mut context = AnalysisContext {
188 error_on_binding_loop_with_window_layout,
189 visited: HashSet::new(),
190 currently_analyzing: Default::default(),
191 window_layout_property: None,
192 global_analysis,
193 };
194 doc.visit_all_used_components(|component| {
195 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
196 component,
197 &(),
198 &mut |e, _| analyze_element(e, &mut context, reverse_aliases, diag),
199 )
200 });
201}
202
203fn analyze_element(
204 elem: &ElementRc,
205 context: &mut AnalysisContext,
206 reverse_aliases: &ReverseAliases,
207 diag: &mut BuildDiagnostics,
208) {
209 for (name, binding) in elem.borrow().real_bindings() {
210 if binding.borrow().analysis.is_some() {
211 continue;
212 }
213 analyze_binding(
214 &PropertyPath::from(NamedReference::new(elem, name.clone())),
215 context,
216 reverse_aliases,
217 diag,
218 );
219 }
220 for cb in elem.borrow().change_callbacks.values() {
221 for e in cb.borrow().iter() {
222 recurse_expression(elem, e, &mut |prop, r| {
223 process_property(prop, r, context, reverse_aliases, diag);
224 });
225 }
226 }
227 const P: ReadType = ReadType::PropertyRead;
228 for nr in elem.borrow().accessibility_props.0.values() {
229 process_property(&PropertyPath::from(nr.clone()), P, context, reverse_aliases, diag);
230 }
231 if let Some(g) = elem.borrow().geometry_props.as_ref() {
232 process_property(&g.x.clone().into(), P, context, reverse_aliases, diag);
233 process_property(&g.y.clone().into(), P, context, reverse_aliases, diag);
234 process_property(&g.width.clone().into(), P, context, reverse_aliases, diag);
235 process_property(&g.height.clone().into(), P, context, reverse_aliases, diag);
236 }
237
238 if let Some(component) = elem.borrow().enclosing_component.upgrade()
239 && Rc::ptr_eq(&component.root_element, elem)
240 {
241 for e in component.init_code.borrow().iter() {
242 recurse_expression(elem, e, &mut |prop, r| {
243 process_property(prop, r, context, reverse_aliases, diag);
244 });
245 }
246 component.root_constraints.borrow_mut().visit_named_references(&mut |nr| {
247 process_property(&nr.clone().into(), P, context, reverse_aliases, diag);
248 });
249 component.popup_windows.borrow().iter().for_each(|p| {
250 process_property(&p.x.clone().into(), P, context, reverse_aliases, diag);
251 process_property(&p.y.clone().into(), P, context, reverse_aliases, diag);
252 });
253 component.timers.borrow().iter().for_each(|t| {
254 process_property(&t.interval.clone().into(), P, context, reverse_aliases, diag);
255 process_property(&t.running.clone().into(), P, context, reverse_aliases, diag);
256 process_property(&t.triggered.clone().into(), P, context, reverse_aliases, diag);
257 });
258 }
259
260 if let Some(repeated) = &elem.borrow().repeated {
261 recurse_expression(elem, &repeated.model, &mut |prop, r| {
262 process_property(prop, r, context, reverse_aliases, diag);
263 });
264 if let Some(lv) = &repeated.is_listview {
265 process_property(&lv.content_y.clone().into(), P, context, reverse_aliases, diag);
266 if let Some(content_height) = &lv.content_height {
267 process_property(&content_height.clone().into(), P, context, reverse_aliases, diag);
268 }
269 if let Some(content_width) = &lv.content_width {
270 process_property(&content_width.clone().into(), P, context, reverse_aliases, diag);
271 }
272 process_property(&lv.listview_height.clone().into(), P, context, reverse_aliases, diag);
273 process_property(&lv.listview_width.clone().into(), P, context, reverse_aliases, diag);
274 }
275 }
276 if let Some((h, v)) = &elem.borrow().layout_info_prop {
283 process_property(&h.clone().into(), P, context, reverse_aliases, diag);
284 process_property(&v.clone().into(), P, context, reverse_aliases, diag);
285 }
286
287 for info in elem.borrow().debug.iter() {
288 if let Some(crate::layout::Layout::GridLayout(grid)) = &info.layout
289 && grid.uses_auto
290 {
291 for rowcol_prop_name in ["row", "col"] {
292 for it in grid.elems.iter() {
293 let child = &it.item.element;
294 if child
295 .borrow()
296 .property_analysis
297 .borrow()
298 .get(rowcol_prop_name)
299 .is_some_and(|a| a.is_set || a.is_set_externally)
300 {
301 diag.push_error(
302 format!("Cannot set property '{}' on '{}' because parent GridLayout uses auto-numbering",
303 rowcol_prop_name, child.borrow().id),
304 &child.borrow().to_source_location(), );
306 }
307 }
308 }
309 }
310 }
311}
312
313#[derive(Copy, Clone, dm::BitAnd, dm::BitOr, dm::BitAndAssign, dm::BitOrAssign)]
314struct DependsOnExternal(bool);
315
316fn analyze_binding(
317 current: &PropertyPath,
318 context: &mut AnalysisContext,
319 reverse_aliases: &ReverseAliases,
320 diag: &mut BuildDiagnostics,
321) -> DependsOnExternal {
322 let mut depends_on_external = DependsOnExternal(false);
323 let element = current.prop.element();
324 let name = current.prop.name();
325 if (context.currently_analyzing.last() == Some(current))
326 && !element
327 .borrow()
328 .binding_cell_including_synthetic(name)
329 .unwrap()
330 .borrow()
331 .two_way_bindings
332 .is_empty()
333 {
334 let span = element
335 .borrow()
336 .binding_cell_including_synthetic(name)
337 .unwrap()
338 .borrow()
339 .span
340 .clone()
341 .unwrap_or_else(|| element.borrow().to_source_location());
342 diag.push_error(format!("Property '{name}' cannot refer to itself"), &span);
343 return depends_on_external;
344 }
345
346 if context.currently_analyzing.contains(current) {
347 let mut loop_description = String::new();
348 let mut has_window_layout = false;
349
350 fn push_prop(prop: &PropertyPath, out: &mut String) {
351 if !out.is_empty() {
352 out.push_str(" -> ");
353 }
354 let name = prop.prop.declared_name();
355 match prop.prop.element().borrow().id.as_str() {
356 "" => out.push_str(&name),
357 id => {
358 out.push_str(id);
359 out.push('.');
360 out.push_str(&name);
361 }
362 }
363 }
364
365 push_prop(current, &mut loop_description);
368 for it in context.currently_analyzing.iter().rev() {
369 if context.window_layout_property.as_ref().is_some_and(|p| p == it) {
370 has_window_layout = true;
371 }
372 push_prop(it, &mut loop_description);
373 if it == current {
374 break;
375 }
376 }
377
378 for it in context.currently_analyzing.iter().rev() {
379 let p = &it.prop;
380 let elem = p.element();
381 let elem = elem.borrow();
382 let binding = elem.binding_cell_including_synthetic(p.name()).unwrap().borrow();
383 if binding.analysis.as_ref().unwrap().is_in_binding_loop.replace(true) {
384 break;
385 }
386
387 let span = binding.span.clone().unwrap_or_else(|| elem.to_source_location());
388 if span.source_file.is_some() {
391 if !context.error_on_binding_loop_with_window_layout && has_window_layout {
392 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.declared_name()), &span);
393 } else {
394 diag.push_error(format!("The binding for the property '{}' is part of a binding loop ({loop_description})", p.declared_name()), &span);
395 }
396 }
397 if it == current {
398 break;
399 }
400 }
401 return depends_on_external;
402 }
403
404 let element_borrow = element.borrow();
405 let binding = element_borrow.binding_cell_including_synthetic(name).unwrap();
406 if binding.borrow().analysis.as_ref().is_some_and(|a| a.no_external_dependencies) {
407 return depends_on_external;
408 } else if !context.visited.insert(current.clone()) {
409 return DependsOnExternal(true);
410 }
411
412 if let Ok(mut b) = binding.try_borrow_mut() {
413 b.analysis = Some(Default::default());
414 };
415 context.currently_analyzing.insert(current.clone());
416
417 let b = binding.borrow();
418 for twb in &b.two_way_bindings {
419 if let Some(p) = twb.property()
420 && p != ¤t.prop
421 {
422 depends_on_external |= process_property(
423 ¤t.relative(&p.clone().into()),
424 ReadType::PropertyRead,
425 context,
426 reverse_aliases,
427 diag,
428 );
429 }
430 }
431
432 let mut process_prop = |prop: &PropertyPath, r, context: &mut AnalysisContext| {
433 depends_on_external |=
434 process_property(¤t.relative(prop), r, context, reverse_aliases, diag);
435 for x in find_alias_targets(prop, reverse_aliases) {
436 if x.prop != prop.prop {
440 depends_on_external |= process_property(
441 ¤t.relative(&x),
442 ReadType::PropertyRead,
443 context,
444 reverse_aliases,
445 diag,
446 );
447 }
448 }
449 };
450
451 recurse_expression(¤t.prop.element(), &b.expression, &mut |p, r| {
452 process_prop(p, r, context)
453 });
454
455 let mut aliased_deps = Vec::new();
459 for alias in reverse_aliases.get(¤t.prop).into_iter().flatten() {
460 let element = alias.element();
461 let element_borrow = element.borrow();
462 if let Some(alias_binding) = element_borrow.binding(alias.name()) {
463 recurse_expression(&element, &alias_binding.expression, &mut |p, r| {
464 if !(p.elements.is_empty() && p.prop == current.prop) {
466 aliased_deps.push((p.clone(), r))
467 }
468 });
469 }
470 }
471 for (p, r) in &aliased_deps {
473 process_prop(p, *r, context);
474 }
475
476 let mut is_const = b.expression.is_constant(Some(context.global_analysis))
477 && b.two_way_bindings.iter().all(|n| n.is_constant());
478
479 if is_const && matches!(b.expression, Expression::Invalid) {
480 if let Some(base) = element.borrow().sub_component() {
482 is_const = NamedReference::new(&base.root_element, name.clone()).is_constant();
483 }
484 }
485 drop(b);
486
487 if let Ok(mut b) = binding.try_borrow_mut() {
488 b.analysis.as_mut().unwrap().is_const = is_const;
490 }
491
492 match &binding.borrow().animation {
493 Some(PropertyAnimation::Static(e)) => analyze_element(e, context, reverse_aliases, diag),
494 Some(PropertyAnimation::Transition { animations, state_ref }) => {
495 recurse_expression(¤t.prop.element(), state_ref, &mut |p, r| {
496 process_prop(p, r, context)
497 });
498 for a in animations {
499 analyze_element(&a.animation, context, reverse_aliases, diag);
500 }
501 }
502 None => (),
503 }
504
505 let o = context.currently_analyzing.pop();
506 assert_eq!(&o.unwrap(), current);
507
508 depends_on_external
509}
510
511fn find_alias_targets(prop: &PropertyPath, reverse_aliases: &ReverseAliases) -> Vec<PropertyPath> {
514 if let Some(v) = reverse_aliases.get(&prop.prop) {
516 return v
517 .iter()
518 .map(|x| PropertyPath { elements: prop.elements.clone(), prop: x.clone() })
519 .collect();
520 }
521
522 let start_element = prop.elements.first().map_or_else(|| prop.prop.element(), |e| e.0.clone());
523 let mut cur = prop.prop.clone();
524 loop {
525 let element = cur.element();
526 if element.borrow().binding(cur.name()).is_some() {
527 return Vec::new();
528 }
529 let next = match &element.borrow().base_type {
530 ElementType::Component(base) => {
531 if element.borrow().property_declarations.contains_key(cur.name()) {
532 return Vec::new();
533 }
534 base.root_element.clone()
535 }
536 _ => return Vec::new(),
537 };
538 cur = NamedReference::new(&next, cur.name().clone());
539 if let Some(v) = reverse_aliases.get(&cur) {
540 return v
541 .iter()
542 .map(|x| PropertyPath::from(NamedReference::new(&start_element, x.name().clone())))
543 .collect();
544 }
545 }
546}
547
548#[derive(Copy, Clone, Eq, PartialEq)]
549enum ReadType {
550 NativeRead,
552 PropertyRead,
554}
555
556fn process_property(
560 prop: &PropertyPath,
561 read_type: ReadType,
562 context: &mut AnalysisContext,
563 reverse_aliases: &ReverseAliases,
564 diag: &mut BuildDiagnostics,
565) -> DependsOnExternal {
566 #[allow(clippy::match_single_binding)]
567 let depends_on_external = match prop
568 .prop
569 .element()
570 .borrow()
571 .property_analysis
572 .borrow_mut()
573 .entry(prop.prop.name().clone())
574 .or_default()
575 {
576 a => {
577 if read_type == ReadType::PropertyRead {
578 a.is_read = true;
579 }
580 DependsOnExternal(prop.elements.is_empty() && a.is_set_externally)
581 }
582 };
583
584 let mut prop = prop.clone();
585
586 loop {
587 let element = prop.prop.element();
588 if element.borrow().binding(prop.prop.name()).is_some() {
589 analyze_binding(&prop, context, reverse_aliases, diag);
590 break;
591 }
592 let next = match &element.borrow().base_type {
593 ElementType::Component(base) => {
594 if element.borrow().property_declarations.contains_key(prop.prop.name()) {
595 break;
596 }
597 base.root_element.clone()
598 }
599 ElementType::Builtin(builtin) => {
600 if builtin.properties.contains_key(prop.prop.name()) {
601 visit_builtin_property(builtin, &prop, context, reverse_aliases, diag);
602 }
603 break;
604 }
605 _ => break,
606 };
607 next.borrow()
608 .property_analysis
609 .borrow_mut()
610 .entry(prop.prop.name().clone())
611 .or_default()
612 .is_read_externally = true;
613 prop.elements.push(element.into());
614 prop.prop = NamedReference::new(&next, prop.prop.name().clone());
615 }
616 depends_on_external
617}
618
619fn recurse_expression(
621 elem: &ElementRc,
622 expr: &Expression,
623 vis: &mut impl FnMut(&PropertyPath, ReadType),
624) {
625 const P: ReadType = ReadType::PropertyRead;
626 expr.visit(|sub| recurse_expression(elem, sub, vis));
627 match expr {
628 Expression::PropertyReference(r) => vis(&r.clone().into(), P),
629 Expression::LayoutCacheAccess { layout_cache_prop, .. } => {
630 vis(&layout_cache_prop.clone().into(), P)
631 }
632 Expression::GridRepeaterCacheAccess { layout_cache_prop, .. } => {
633 vis(&layout_cache_prop.clone().into(), P)
634 }
635 Expression::SolveBoxLayout(l, o)
636 | Expression::ComputeBoxLayoutInfo { layout: l, orientation: o, .. } => {
637 if matches!(expr, Expression::SolveBoxLayout(..))
639 && let Some(nr) = l.geometry.rect.size_reference(*o)
640 {
641 vis(&nr.clone().into(), P);
642 }
643 visit_layout_items_dependencies(l.elems.iter(), *o, vis);
644
645 if matches!(expr, Expression::SolveBoxLayout(..)) && *o != l.orientation {
648 if let Some(nr) = l.cross_alignment.as_ref() {
649 vis(&nr.clone().into(), P);
650 }
651 for cell in l.elems.iter() {
652 if let Some(nr) = cell.cross_axis_self_alignment.as_ref() {
653 vis(&nr.clone().into(), P);
654 }
655 }
656 }
657
658 let mut g = l.geometry.clone();
659 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
661 }
662 Expression::SolveFlexboxLayout(layout)
663 | Expression::ComputeFlexboxLayoutInfo { layout, .. } => {
664 if let Some(nr) = layout.direction.as_ref() {
665 vis(&nr.clone().into(), P);
666 }
667 if matches!(expr, Expression::SolveFlexboxLayout(..)) {
668 for it in layout.elems.iter() {
671 let elem = if it.element.borrow().repeated.is_some() {
674 it.element.borrow().base_type.as_component().root_element.clone()
675 } else {
676 it.element.clone()
677 };
678 for orientation in [Orientation::Horizontal, Orientation::Vertical] {
679 let kept = it.constraints.to_apply(&elem, orientation);
680 for (nr, _) in kept.for_each_restrictions(orientation) {
681 vis(&nr.clone().into(), P);
682 }
683 }
684 }
685 use crate::layout::FlexboxAxisRelation;
697 match layout.axis_relation(Orientation::Horizontal) {
698 FlexboxAxisRelation::MainAxis => {
699 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
700 vis(&nr.clone().into(), P);
701 }
702 visit_layout_items_layoutinfo_cross_axis_dependencies(
703 layout.elems.iter(),
704 Orientation::Vertical,
705 vis,
706 );
707 }
708 FlexboxAxisRelation::CrossAxis => {
709 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
710 vis(&nr.clone().into(), P);
711 }
712 visit_layout_items_layoutinfo_cross_axis_dependencies(
713 layout.elems.iter(),
714 Orientation::Horizontal,
715 vis,
716 );
717 }
718 FlexboxAxisRelation::Unknown => {
719 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
721 vis(&nr.clone().into(), P);
722 }
723 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
724 vis(&nr.clone().into(), P);
725 }
726 visit_layout_items_layoutinfo_cross_axis_dependencies(
727 layout.elems.iter(),
728 Orientation::Horizontal,
729 vis,
730 );
731 visit_layout_items_layoutinfo_cross_axis_dependencies(
732 layout.elems.iter(),
733 Orientation::Vertical,
734 vis,
735 );
736 }
737 }
738 } else if let Expression::ComputeFlexboxLayoutInfo { orientation, .. } = expr {
739 let orientation = *orientation;
740 use crate::layout::FlexboxAxisRelation;
741 match layout.axis_relation(orientation) {
742 FlexboxAxisRelation::MainAxis => {
743 visit_layout_items_dependencies(layout.elems.iter(), orientation, vis);
745 }
746 FlexboxAxisRelation::CrossAxis => {
747 if orientation == Orientation::Vertical
754 && let Some(nr) = layout.geometry.rect.width_reference.as_ref()
755 && nr.element().borrow().layout_info_v_with_constraint.is_none()
756 {
757 vis(&nr.clone().into(), P);
758 }
759 visit_layout_items_dependencies(
760 layout.elems.iter(),
761 Orientation::Horizontal,
762 vis,
763 );
764 visit_layout_items_dependencies(
765 layout.elems.iter(),
766 Orientation::Vertical,
767 vis,
768 );
769 }
770 FlexboxAxisRelation::Unknown => {
771 visit_layout_items_dependencies(
775 layout.elems.iter(),
776 Orientation::Horizontal,
777 vis,
778 );
779 visit_layout_items_dependencies(
780 layout.elems.iter(),
781 Orientation::Vertical,
782 vis,
783 );
784 }
785 }
786 }
787 let mut g = layout.geometry.clone();
788 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
790 }
791 Expression::OrganizeGridLayout(layout) => {
792 let mut layout = layout.clone();
793 layout.visit_rowcol_named_references(&mut |nr: &mut NamedReference| {
794 vis(&nr.clone().into(), P)
795 });
796 }
797 Expression::SolveGridLayout { layout_organized_data_prop, layout, orientation }
798 | Expression::ComputeGridLayoutInfo {
799 layout_organized_data_prop,
800 layout,
801 orientation,
802 ..
803 } => {
804 if matches!(expr, Expression::SolveGridLayout { .. })
806 && let Some(nr) = layout.geometry.rect.size_reference(*orientation)
807 {
808 vis(&nr.clone().into(), P);
809 }
810 vis(&layout_organized_data_prop.clone().into(), P);
811 visit_layout_items_dependencies(
812 layout.elems.iter().map(|it| &it.item),
813 *orientation,
814 vis,
815 );
816 let mut g = layout.geometry.clone();
817 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
819 }
820 Expression::FunctionCall {
821 function: Callable::Callback(nr) | Callable::Function(nr),
822 ..
823 } => vis(&nr.clone().into(), P),
824 Expression::FunctionCall { function: Callable::Builtin(b), arguments, .. } => match b {
825 BuiltinFunction::ImplicitLayoutInfo(orientation) => {
826 if let [Expression::ElementReference(item), ..] = arguments.as_slice() {
827 visit_implicit_layout_info_dependencies(
828 *orientation,
829 &item.upgrade().unwrap(),
830 vis,
831 );
832 }
833 }
834 BuiltinFunction::ItemAbsolutePosition => {
835 if let Some(Expression::ElementReference(item)) = arguments.first() {
836 let mut item = item.upgrade().unwrap();
839 loop {
840 vis(
841 &NamedReference::new(&item, SmolStr::new_static("x")).into(),
842 ReadType::NativeRead,
843 );
844 vis(
845 &NamedReference::new(&item, SmolStr::new_static("y")).into(),
846 ReadType::NativeRead,
847 );
848 let Some(parent) = find_parent_element(&item) else { break };
849 item = parent;
850 }
851 }
852 }
853 BuiltinFunction::ItemFontMetrics => {
854 if let Some(Expression::ElementReference(item)) = arguments.first() {
855 let item = item.upgrade().unwrap();
856 vis(
857 &NamedReference::new(&item, SmolStr::new_static("font-size")).into(),
858 ReadType::NativeRead,
859 );
860 vis(
861 &NamedReference::new(&item, SmolStr::new_static("font-weight")).into(),
862 ReadType::NativeRead,
863 );
864 vis(
865 &NamedReference::new(&item, SmolStr::new_static("font-family")).into(),
866 ReadType::NativeRead,
867 );
868 vis(
869 &NamedReference::new(&item, SmolStr::new_static("font-italic")).into(),
870 ReadType::NativeRead,
871 );
872 }
873 }
874 BuiltinFunction::GetWindowDefaultFontSize => {
875 let root =
876 elem.borrow().enclosing_component.upgrade().unwrap().root_element.clone();
877 if root.borrow().builtin_type().is_some_and(|bt| bt.name == "Window") {
878 vis(
879 &NamedReference::new(&root, SmolStr::new_static("default-font-size"))
880 .into(),
881 ReadType::PropertyRead,
882 );
883 }
884 }
885 _ => {}
886 },
887 _ => {}
888 }
889}
890
891fn visit_layout_items_dependencies<'a>(
892 items: impl Iterator<Item = &'a LayoutItem>,
893 orientation: Orientation,
894 vis: &mut impl FnMut(&PropertyPath, ReadType),
895) {
896 for it in items {
897 let mut element = it.element.clone();
898 if element
899 .borrow()
900 .repeated
901 .as_ref()
902 .map(|r| recurse_expression(&element, &r.model, vis))
903 .is_some()
904 {
905 element = it.element.borrow().base_type.as_component().root_element.clone();
906 }
907
908 if let Some(nr) = element.borrow().effective_layout_info_prop(orientation) {
909 vis(&nr.clone().into(), ReadType::PropertyRead);
910 } else {
911 let height_settled = element.borrow().height_is_literal;
912 if let Some(nr) = element.borrow().base_layout_info_prop(orientation, height_settled) {
913 vis(
914 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr },
915 ReadType::PropertyRead,
916 );
917 }
918 visit_implicit_layout_info_dependencies(orientation, &element, vis);
919 }
920
921 for (nr, _) in it.constraints.for_each_restrictions(orientation) {
922 vis(&nr.clone().into(), ReadType::PropertyRead)
923 }
924 }
925}
926
927fn visit_layout_items_layoutinfo_cross_axis_dependencies<'a>(
942 items: impl Iterator<Item = &'a LayoutItem>,
943 cross_axis: Orientation,
944 vis: &mut impl FnMut(&PropertyPath, ReadType),
945) {
946 for it in items {
947 let element = it.element.clone();
948 if cross_axis == Orientation::Vertical
950 && element.borrow().inherited_layout_info_v_with_constraint().is_some()
951 {
952 continue;
953 }
954 if let Some(nr) = element.borrow().effective_layout_info_prop(cross_axis) {
955 vis(&nr.clone().into(), ReadType::PropertyRead);
956 } else if let Some(nr) = {
957 let height_settled = element.borrow().height_is_literal;
958 element.borrow().base_layout_info_prop(cross_axis, height_settled)
959 } {
960 vis(
961 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr },
962 ReadType::PropertyRead,
963 );
964 } else {
965 visit_cell_cross_axis_implicit_dependency(cross_axis, &element, vis);
966 }
967 }
968}
969
970fn visit_cell_cross_axis_implicit_dependency(
980 cross_axis: Orientation,
981 item: &ElementRc,
982 vis: &mut impl FnMut(&PropertyPath, ReadType),
983) {
984 let base_type = item.borrow().base_type.to_smolstr();
985 if matches!(base_type.as_str(), "Image" | "ClippedImage" | "Text" | "TextInput" | "StyledText")
986 {
987 return;
988 }
989 let (prop, opposite_dim) = match cross_axis {
990 Orientation::Horizontal => ("preferred-width", "height"),
991 Orientation::Vertical => ("preferred-height", "width"),
992 };
993 if !item.borrow().is_binding_set(prop, false) {
994 return;
995 }
996 let reads_opposite = item
997 .borrow()
998 .binding(prop)
999 .map(|b| {
1000 let mut seen = false;
1001 b.expression.visit_recursive(&mut |sub| {
1002 if let Expression::PropertyReference(nr) = sub
1003 && nr.name() == opposite_dim
1004 && Rc::ptr_eq(&nr.element(), item)
1005 {
1006 seen = true;
1007 }
1008 });
1009 seen
1010 })
1011 .unwrap_or(false);
1012 if reads_opposite {
1013 vis(&NamedReference::new(item, SmolStr::new_static(prop)).into(), ReadType::NativeRead);
1014 }
1015}
1016
1017fn visit_implicit_layout_info_dependencies(
1019 orientation: crate::layout::Orientation,
1020 item: &ElementRc,
1021 vis: &mut impl FnMut(&PropertyPath, ReadType),
1022) {
1023 let base_type = item.borrow().base_type.to_smolstr();
1024 const N: ReadType = ReadType::NativeRead;
1025 match base_type.as_str() {
1026 "Image" => {
1027 vis(&NamedReference::new(item, SmolStr::new_static("source")).into(), N);
1028 vis(&NamedReference::new(item, SmolStr::new_static("source-clip-width")).into(), N);
1029 if orientation == Orientation::Vertical {
1030 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
1031 vis(
1032 &NamedReference::new(item, SmolStr::new_static("source-clip-height")).into(),
1033 N,
1034 );
1035 }
1036 }
1037 "Text" | "TextInput" => {
1038 vis(&NamedReference::new(item, SmolStr::new_static("text")).into(), N);
1039 vis(&NamedReference::new(item, SmolStr::new_static("font-family")).into(), N);
1040 vis(&NamedReference::new(item, SmolStr::new_static("font-size")).into(), N);
1041 vis(&NamedReference::new(item, SmolStr::new_static("font-weight")).into(), N);
1042 vis(&NamedReference::new(item, SmolStr::new_static("letter-spacing")).into(), N);
1043 if orientation == Orientation::Vertical {
1046 vis(
1047 &NamedReference::new(item, SmolStr::new_static("line-height-factor")).into(),
1048 N,
1049 );
1050 }
1051 vis(&NamedReference::new(item, SmolStr::new_static("wrap")).into(), N);
1052 let wrap_set = item.borrow().is_binding_set("wrap", false)
1053 || item
1054 .borrow()
1055 .property_analysis
1056 .borrow()
1057 .get("wrap")
1058 .is_some_and(|a| a.is_set || a.is_set_externally);
1059 if wrap_set && orientation == Orientation::Vertical {
1060 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
1061 }
1062 if base_type.as_str() == "TextInput" {
1063 vis(&NamedReference::new(item, SmolStr::new_static("single-line")).into(), N);
1064 } else {
1065 vis(&NamedReference::new(item, SmolStr::new_static("overflow")).into(), N);
1066 vis(&NamedReference::new(item, SmolStr::new_static("max-lines")).into(), N);
1069 }
1070 }
1071 "StyledText" => {
1072 vis(&NamedReference::new(item, SmolStr::new_static("text")).into(), N);
1073 vis(&NamedReference::new(item, SmolStr::new_static("default-font-family")).into(), N);
1074 vis(&NamedReference::new(item, SmolStr::new_static("default-font-size")).into(), N);
1075 vis(&NamedReference::new(item, SmolStr::new_static("max-lines")).into(), N);
1078 if orientation == Orientation::Vertical {
1079 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
1081 }
1082 }
1083
1084 _ => (),
1085 }
1086}
1087
1088fn visit_builtin_property(
1089 builtin: &crate::langtype::BuiltinElement,
1090 prop: &PropertyPath,
1091 context: &mut AnalysisContext,
1092 reverse_aliases: &ReverseAliases,
1093 diag: &mut BuildDiagnostics,
1094) {
1095 let name = prop.prop.name();
1096 if builtin.name == "Window" {
1097 for (p, orientation) in
1098 [("width", Orientation::Horizontal), ("height", Orientation::Vertical)]
1099 {
1100 if name == p {
1101 let is_root = |e: &ElementRc| -> bool {
1103 ElementRc::ptr_eq(
1104 e,
1105 &e.borrow().enclosing_component.upgrade().unwrap().root_element,
1106 )
1107 };
1108 let mut root = prop.prop.element();
1109 if !is_root(&root) {
1110 return;
1111 };
1112 for e in prop.elements.iter().rev() {
1113 if !is_root(&e.0) {
1114 return;
1115 }
1116 root = e.0.clone();
1117 }
1118 if let Some(p) = root.borrow().effective_layout_info_prop(orientation) {
1119 let path = PropertyPath::from(p.clone());
1120 let old_layout = context.window_layout_property.replace(path.clone());
1121 process_property(&path, ReadType::NativeRead, context, reverse_aliases, diag);
1122 context.window_layout_property = old_layout;
1123 };
1124 }
1125 }
1126 }
1127}
1128
1129fn check_window_properties(doc: &Document, global_analysis: &mut GlobalAnalysis) {
1131 doc.visit_all_used_components(|component| {
1132 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1133 component,
1134 &(),
1135 &mut |elem, _| {
1136 if elem.borrow().builtin_type().as_ref().is_some_and(|b| b.name == "Window") {
1137 const DEFAULT_FONT_SIZE: &str = "default-font-size";
1138 if elem.borrow().is_binding_set(DEFAULT_FONT_SIZE, false)
1139 || elem
1140 .borrow()
1141 .property_analysis
1142 .borrow()
1143 .get(DEFAULT_FONT_SIZE)
1144 .is_some_and(|a| a.is_set)
1145 {
1146 let value = elem.borrow().binding(DEFAULT_FONT_SIZE).and_then(|e| match e
1150 .expression
1151 {
1152 Expression::NumberLiteral(v, crate::expression_tree::Unit::Px) => {
1153 Some(v as f32)
1154 }
1155 _ => None,
1156 });
1157 let is_const = value.is_some()
1158 || NamedReference::new(elem, SmolStr::new_static(DEFAULT_FONT_SIZE))
1159 .is_constant();
1160 global_analysis.default_font_size = match global_analysis.default_font_size
1161 {
1162 DefaultFontSize::Unknown => match value {
1163 Some(v) => DefaultFontSize::LogicalValue(v),
1164 None if is_const => DefaultFontSize::Const,
1165 None => DefaultFontSize::Variable,
1166 },
1167 DefaultFontSize::NotSet if is_const => DefaultFontSize::NotSet,
1168 DefaultFontSize::LogicalValue(val) => match value {
1169 Some(v) if v == val => DefaultFontSize::LogicalValue(val),
1170 _ if is_const => DefaultFontSize::Const,
1171 _ => DefaultFontSize::Variable,
1172 },
1173 DefaultFontSize::Const if is_const => DefaultFontSize::Const,
1174 _ => DefaultFontSize::Variable,
1175 }
1176 } else {
1177 global_analysis.default_font_size = match global_analysis.default_font_size
1178 {
1179 DefaultFontSize::Unknown => DefaultFontSize::NotSet,
1180 DefaultFontSize::NotSet => DefaultFontSize::NotSet,
1181 DefaultFontSize::LogicalValue(_) => DefaultFontSize::NotSet,
1182 DefaultFontSize::Const => DefaultFontSize::NotSet,
1183 DefaultFontSize::Variable => DefaultFontSize::Variable,
1184 }
1185 }
1186 }
1187 },
1188 );
1189 });
1190}
1191
1192fn propagate_is_set_on_aliases(doc: &Document, reverse_aliases: &mut ReverseAliases) {
1204 doc.visit_all_used_components(|component| {
1205 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1206 component,
1207 &(),
1208 &mut |e, _| visit_element(e, reverse_aliases),
1209 );
1210 });
1211
1212 fn visit_element(e: &ElementRc, reverse_aliases: &mut ReverseAliases) {
1213 for (name, binding) in e.borrow().real_bindings() {
1214 if !binding.borrow().two_way_bindings.is_empty() {
1215 check_alias(e, name, &binding.borrow());
1216
1217 let nr = NamedReference::new(e, name.clone());
1218 for a in &binding.borrow().two_way_bindings {
1219 if let Some(a) = a.property()
1220 && a != &nr
1221 && !a.element().borrow().enclosing_component.upgrade().unwrap().is_global()
1222 {
1223 reverse_aliases.entry(a.clone()).or_default().push(nr.clone())
1224 }
1225 }
1226 }
1227 }
1228 for decl in e.borrow().property_declarations.values() {
1229 if let Some(alias) = &decl.is_alias {
1230 mark_alias(alias)
1231 }
1232 }
1233 }
1234
1235 fn check_alias(e: &ElementRc, name: &SmolStr, binding: &BindingExpression) {
1236 let is_binding_constant =
1238 binding.is_constant(None) && binding.two_way_bindings.iter().all(|n| n.is_constant());
1239 if is_binding_constant && !NamedReference::new(e, name.clone()).is_externally_modified() {
1240 for alias in binding.two_way_bindings.iter().filter_map(|x| x.property()) {
1241 crate::namedreference::mark_property_set_derived_in_base(
1242 alias.element(),
1243 alias.name(),
1244 );
1245 }
1246 return;
1247 }
1248
1249 propagate_alias(binding);
1250 }
1251
1252 fn propagate_alias(binding: &BindingExpression) {
1253 for alias in binding.two_way_bindings.iter().filter_map(|x| x.property()) {
1254 mark_alias(alias);
1255 }
1256 }
1257
1258 fn mark_alias(alias: &NamedReference) {
1259 alias.mark_as_set();
1260 if !alias.is_externally_modified()
1261 && let Some(bind) = alias.element().borrow().binding(alias.name())
1262 {
1263 propagate_alias(&bind)
1264 }
1265 }
1266}
1267
1268fn mark_used_base_properties(doc: &Document) {
1271 doc.visit_all_used_components(|component| {
1272 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1273 component,
1274 &(),
1275 &mut |element, _| {
1276 if !matches!(element.borrow().base_type, ElementType::Component(_)) {
1277 return;
1278 }
1279 for (name, binding) in element.borrow().real_bindings() {
1280 if binding.borrow().has_binding() {
1281 crate::namedreference::mark_property_set_derived_in_base(
1282 element.clone(),
1283 name,
1284 );
1285 }
1286 }
1287 for name in element.borrow().change_callbacks.keys() {
1288 crate::namedreference::mark_property_read_derived_in_base(
1289 element.clone(),
1290 name,
1291 );
1292 }
1293 },
1294 );
1295 });
1296}