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(..)) {
669 use crate::layout::FlexboxAxisRelation;
681 match layout.axis_relation(Orientation::Horizontal) {
682 FlexboxAxisRelation::MainAxis => {
683 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
684 vis(&nr.clone().into(), P);
685 }
686 visit_layout_items_layoutinfo_cross_axis_dependencies(
687 layout.elems.iter(),
688 Orientation::Vertical,
689 vis,
690 );
691 }
692 FlexboxAxisRelation::CrossAxis => {
693 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
694 vis(&nr.clone().into(), P);
695 }
696 visit_layout_items_layoutinfo_cross_axis_dependencies(
697 layout.elems.iter(),
698 Orientation::Horizontal,
699 vis,
700 );
701 }
702 FlexboxAxisRelation::Unknown => {
703 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
705 vis(&nr.clone().into(), P);
706 }
707 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
708 vis(&nr.clone().into(), P);
709 }
710 visit_layout_items_layoutinfo_cross_axis_dependencies(
711 layout.elems.iter(),
712 Orientation::Horizontal,
713 vis,
714 );
715 visit_layout_items_layoutinfo_cross_axis_dependencies(
716 layout.elems.iter(),
717 Orientation::Vertical,
718 vis,
719 );
720 }
721 }
722 } else if let Expression::ComputeFlexboxLayoutInfo { orientation, .. } = expr {
723 let orientation = *orientation;
724 use crate::layout::FlexboxAxisRelation;
725 match layout.axis_relation(orientation) {
726 FlexboxAxisRelation::MainAxis => {
727 visit_layout_items_dependencies(layout.elems.iter(), orientation, vis);
729 }
730 FlexboxAxisRelation::CrossAxis => {
731 if orientation == Orientation::Vertical
738 && let Some(nr) = layout.geometry.rect.width_reference.as_ref()
739 && nr.element().borrow().layout_info_v_with_constraint.is_none()
740 {
741 vis(&nr.clone().into(), P);
742 }
743 visit_layout_items_dependencies(
744 layout.elems.iter(),
745 Orientation::Horizontal,
746 vis,
747 );
748 visit_layout_items_dependencies(
749 layout.elems.iter(),
750 Orientation::Vertical,
751 vis,
752 );
753 }
754 FlexboxAxisRelation::Unknown => {
755 visit_layout_items_dependencies(
759 layout.elems.iter(),
760 Orientation::Horizontal,
761 vis,
762 );
763 visit_layout_items_dependencies(
764 layout.elems.iter(),
765 Orientation::Vertical,
766 vis,
767 );
768 }
769 }
770 }
771 let mut g = layout.geometry.clone();
772 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
774 }
775 Expression::OrganizeGridLayout(layout) => {
776 let mut layout = layout.clone();
777 layout.visit_rowcol_named_references(&mut |nr: &mut NamedReference| {
778 vis(&nr.clone().into(), P)
779 });
780 }
781 Expression::SolveGridLayout { layout_organized_data_prop, layout, orientation }
782 | Expression::ComputeGridLayoutInfo {
783 layout_organized_data_prop,
784 layout,
785 orientation,
786 ..
787 } => {
788 if matches!(expr, Expression::SolveGridLayout { .. })
790 && let Some(nr) = layout.geometry.rect.size_reference(*orientation)
791 {
792 vis(&nr.clone().into(), P);
793 }
794 vis(&layout_organized_data_prop.clone().into(), P);
795 visit_layout_items_dependencies(
796 layout.elems.iter().map(|it| &it.item),
797 *orientation,
798 vis,
799 );
800 let mut g = layout.geometry.clone();
801 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
803 }
804 Expression::FunctionCall {
805 function: Callable::Callback(nr) | Callable::Function(nr),
806 ..
807 } => vis(&nr.clone().into(), P),
808 Expression::FunctionCall { function: Callable::Builtin(b), arguments, .. } => match b {
809 BuiltinFunction::ImplicitLayoutInfo(orientation) => {
810 if let [Expression::ElementReference(item), ..] = arguments.as_slice() {
811 visit_implicit_layout_info_dependencies(
812 *orientation,
813 &item.upgrade().unwrap(),
814 vis,
815 );
816 }
817 }
818 BuiltinFunction::ItemAbsolutePosition => {
819 if let Some(Expression::ElementReference(item)) = arguments.first() {
820 let mut item = item.upgrade().unwrap();
823 loop {
824 vis(
825 &NamedReference::new(&item, SmolStr::new_static("x")).into(),
826 ReadType::NativeRead,
827 );
828 vis(
829 &NamedReference::new(&item, SmolStr::new_static("y")).into(),
830 ReadType::NativeRead,
831 );
832 let Some(parent) = find_parent_element(&item) else { break };
833 item = parent;
834 }
835 }
836 }
837 BuiltinFunction::ItemFontMetrics => {
838 if let Some(Expression::ElementReference(item)) = arguments.first() {
839 let item = item.upgrade().unwrap();
840 vis(
841 &NamedReference::new(&item, SmolStr::new_static("font-size")).into(),
842 ReadType::NativeRead,
843 );
844 vis(
845 &NamedReference::new(&item, SmolStr::new_static("font-weight")).into(),
846 ReadType::NativeRead,
847 );
848 vis(
849 &NamedReference::new(&item, SmolStr::new_static("font-family")).into(),
850 ReadType::NativeRead,
851 );
852 vis(
853 &NamedReference::new(&item, SmolStr::new_static("font-italic")).into(),
854 ReadType::NativeRead,
855 );
856 }
857 }
858 BuiltinFunction::GetWindowDefaultFontSize => {
859 let root =
860 elem.borrow().enclosing_component.upgrade().unwrap().root_element.clone();
861 if root.borrow().builtin_type().is_some_and(|bt| bt.name == "Window") {
862 vis(
863 &NamedReference::new(&root, SmolStr::new_static("default-font-size"))
864 .into(),
865 ReadType::PropertyRead,
866 );
867 }
868 }
869 _ => {}
870 },
871 _ => {}
872 }
873}
874
875fn visit_layout_items_dependencies<'a>(
876 items: impl Iterator<Item = &'a LayoutItem>,
877 orientation: Orientation,
878 vis: &mut impl FnMut(&PropertyPath, ReadType),
879) {
880 for it in items {
881 let mut element = it.element.clone();
882 if element
883 .borrow()
884 .repeated
885 .as_ref()
886 .map(|r| recurse_expression(&element, &r.model, vis))
887 .is_some()
888 {
889 element = it.element.borrow().base_type.as_component().root_element.clone();
890 }
891
892 if let Some(nr) = element.borrow().effective_layout_info_prop(orientation) {
893 vis(&nr.clone().into(), ReadType::PropertyRead);
894 } else {
895 let height_settled = element.borrow().height_is_literal;
896 if let Some(nr) = element.borrow().base_layout_info_prop(orientation, height_settled) {
897 vis(
898 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr },
899 ReadType::PropertyRead,
900 );
901 }
902 visit_implicit_layout_info_dependencies(orientation, &element, vis);
903 }
904
905 for (nr, _) in it.constraints.for_each_restrictions(orientation) {
906 vis(&nr.clone().into(), ReadType::PropertyRead)
907 }
908 }
909}
910
911fn visit_layout_items_layoutinfo_cross_axis_dependencies<'a>(
926 items: impl Iterator<Item = &'a LayoutItem>,
927 cross_axis: Orientation,
928 vis: &mut impl FnMut(&PropertyPath, ReadType),
929) {
930 for it in items {
931 let element = it.element.clone();
932 if cross_axis == Orientation::Vertical
934 && element.borrow().inherited_layout_info_v_with_constraint().is_some()
935 {
936 continue;
937 }
938 if let Some(nr) = element.borrow().effective_layout_info_prop(cross_axis) {
939 vis(&nr.clone().into(), ReadType::PropertyRead);
940 } else if let Some(nr) = {
941 let height_settled = element.borrow().height_is_literal;
942 element.borrow().base_layout_info_prop(cross_axis, height_settled)
943 } {
944 vis(
945 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr },
946 ReadType::PropertyRead,
947 );
948 } else {
949 visit_cell_cross_axis_implicit_dependency(cross_axis, &element, vis);
950 }
951 }
952}
953
954fn visit_cell_cross_axis_implicit_dependency(
964 cross_axis: Orientation,
965 item: &ElementRc,
966 vis: &mut impl FnMut(&PropertyPath, ReadType),
967) {
968 let base_type = item.borrow().base_type.to_smolstr();
969 if matches!(base_type.as_str(), "Image" | "ClippedImage" | "Text" | "TextInput" | "StyledText")
970 {
971 return;
972 }
973 let (prop, opposite_dim) = match cross_axis {
974 Orientation::Horizontal => ("preferred-width", "height"),
975 Orientation::Vertical => ("preferred-height", "width"),
976 };
977 if !item.borrow().is_binding_set(prop, false) {
978 return;
979 }
980 let reads_opposite = item
981 .borrow()
982 .binding(prop)
983 .map(|b| {
984 let mut seen = false;
985 b.expression.visit_recursive(&mut |sub| {
986 if let Expression::PropertyReference(nr) = sub
987 && nr.name() == opposite_dim
988 && Rc::ptr_eq(&nr.element(), item)
989 {
990 seen = true;
991 }
992 });
993 seen
994 })
995 .unwrap_or(false);
996 if reads_opposite {
997 vis(&NamedReference::new(item, SmolStr::new_static(prop)).into(), ReadType::NativeRead);
998 }
999}
1000
1001fn visit_implicit_layout_info_dependencies(
1003 orientation: crate::layout::Orientation,
1004 item: &ElementRc,
1005 vis: &mut impl FnMut(&PropertyPath, ReadType),
1006) {
1007 let base_type = item.borrow().base_type.to_smolstr();
1008 const N: ReadType = ReadType::NativeRead;
1009 match base_type.as_str() {
1010 "Image" => {
1011 vis(&NamedReference::new(item, SmolStr::new_static("source")).into(), N);
1012 vis(&NamedReference::new(item, SmolStr::new_static("source-clip-width")).into(), N);
1013 if orientation == Orientation::Vertical {
1014 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
1015 vis(
1016 &NamedReference::new(item, SmolStr::new_static("source-clip-height")).into(),
1017 N,
1018 );
1019 }
1020 }
1021 "Text" | "TextInput" => {
1022 vis(&NamedReference::new(item, SmolStr::new_static("text")).into(), N);
1023 vis(&NamedReference::new(item, SmolStr::new_static("font-family")).into(), N);
1024 vis(&NamedReference::new(item, SmolStr::new_static("font-size")).into(), N);
1025 vis(&NamedReference::new(item, SmolStr::new_static("font-weight")).into(), N);
1026 vis(&NamedReference::new(item, SmolStr::new_static("letter-spacing")).into(), N);
1027 if orientation == Orientation::Vertical {
1030 vis(
1031 &NamedReference::new(item, SmolStr::new_static("line-height-factor")).into(),
1032 N,
1033 );
1034 }
1035 vis(&NamedReference::new(item, SmolStr::new_static("wrap")).into(), N);
1036 let wrap_set = item.borrow().is_binding_set("wrap", false)
1037 || item
1038 .borrow()
1039 .property_analysis
1040 .borrow()
1041 .get("wrap")
1042 .is_some_and(|a| a.is_set || a.is_set_externally);
1043 if wrap_set && orientation == Orientation::Vertical {
1044 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
1045 }
1046 if base_type.as_str() == "TextInput" {
1047 vis(&NamedReference::new(item, SmolStr::new_static("single-line")).into(), N);
1048 } else {
1049 vis(&NamedReference::new(item, SmolStr::new_static("overflow")).into(), N);
1050 vis(&NamedReference::new(item, SmolStr::new_static("max-lines")).into(), N);
1053 }
1054 }
1055 "StyledText" => {
1056 vis(&NamedReference::new(item, SmolStr::new_static("text")).into(), N);
1057 vis(&NamedReference::new(item, SmolStr::new_static("default-font-family")).into(), N);
1058 vis(&NamedReference::new(item, SmolStr::new_static("default-font-size")).into(), N);
1059 vis(&NamedReference::new(item, SmolStr::new_static("max-lines")).into(), N);
1062 if orientation == Orientation::Vertical {
1063 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
1065 }
1066 }
1067
1068 _ => (),
1069 }
1070}
1071
1072fn visit_builtin_property(
1073 builtin: &crate::langtype::BuiltinElement,
1074 prop: &PropertyPath,
1075 context: &mut AnalysisContext,
1076 reverse_aliases: &ReverseAliases,
1077 diag: &mut BuildDiagnostics,
1078) {
1079 let name = prop.prop.name();
1080 if builtin.name == "Window" {
1081 for (p, orientation) in
1082 [("width", Orientation::Horizontal), ("height", Orientation::Vertical)]
1083 {
1084 if name == p {
1085 let is_root = |e: &ElementRc| -> bool {
1087 ElementRc::ptr_eq(
1088 e,
1089 &e.borrow().enclosing_component.upgrade().unwrap().root_element,
1090 )
1091 };
1092 let mut root = prop.prop.element();
1093 if !is_root(&root) {
1094 return;
1095 };
1096 for e in prop.elements.iter().rev() {
1097 if !is_root(&e.0) {
1098 return;
1099 }
1100 root = e.0.clone();
1101 }
1102 if let Some(p) = root.borrow().effective_layout_info_prop(orientation) {
1103 let path = PropertyPath::from(p.clone());
1104 let old_layout = context.window_layout_property.replace(path.clone());
1105 process_property(&path, ReadType::NativeRead, context, reverse_aliases, diag);
1106 context.window_layout_property = old_layout;
1107 };
1108 }
1109 }
1110 }
1111}
1112
1113fn check_window_properties(doc: &Document, global_analysis: &mut GlobalAnalysis) {
1115 doc.visit_all_used_components(|component| {
1116 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1117 component,
1118 &(),
1119 &mut |elem, _| {
1120 if elem.borrow().builtin_type().as_ref().is_some_and(|b| b.name == "Window") {
1121 const DEFAULT_FONT_SIZE: &str = "default-font-size";
1122 if elem.borrow().is_binding_set(DEFAULT_FONT_SIZE, false)
1123 || elem
1124 .borrow()
1125 .property_analysis
1126 .borrow()
1127 .get(DEFAULT_FONT_SIZE)
1128 .is_some_and(|a| a.is_set)
1129 {
1130 let value = elem.borrow().binding(DEFAULT_FONT_SIZE).and_then(|e| match e
1134 .expression
1135 {
1136 Expression::NumberLiteral(v, crate::expression_tree::Unit::Px) => {
1137 Some(v as f32)
1138 }
1139 _ => None,
1140 });
1141 let is_const = value.is_some()
1142 || NamedReference::new(elem, SmolStr::new_static(DEFAULT_FONT_SIZE))
1143 .is_constant();
1144 global_analysis.default_font_size = match global_analysis.default_font_size
1145 {
1146 DefaultFontSize::Unknown => match value {
1147 Some(v) => DefaultFontSize::LogicalValue(v),
1148 None if is_const => DefaultFontSize::Const,
1149 None => DefaultFontSize::Variable,
1150 },
1151 DefaultFontSize::NotSet if is_const => DefaultFontSize::NotSet,
1152 DefaultFontSize::LogicalValue(val) => match value {
1153 Some(v) if v == val => DefaultFontSize::LogicalValue(val),
1154 _ if is_const => DefaultFontSize::Const,
1155 _ => DefaultFontSize::Variable,
1156 },
1157 DefaultFontSize::Const if is_const => DefaultFontSize::Const,
1158 _ => DefaultFontSize::Variable,
1159 }
1160 } else {
1161 global_analysis.default_font_size = match global_analysis.default_font_size
1162 {
1163 DefaultFontSize::Unknown => DefaultFontSize::NotSet,
1164 DefaultFontSize::NotSet => DefaultFontSize::NotSet,
1165 DefaultFontSize::LogicalValue(_) => DefaultFontSize::NotSet,
1166 DefaultFontSize::Const => DefaultFontSize::NotSet,
1167 DefaultFontSize::Variable => DefaultFontSize::Variable,
1168 }
1169 }
1170 }
1171 },
1172 );
1173 });
1174}
1175
1176fn propagate_is_set_on_aliases(doc: &Document, reverse_aliases: &mut ReverseAliases) {
1188 doc.visit_all_used_components(|component| {
1189 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1190 component,
1191 &(),
1192 &mut |e, _| visit_element(e, reverse_aliases),
1193 );
1194 });
1195
1196 fn visit_element(e: &ElementRc, reverse_aliases: &mut ReverseAliases) {
1197 for (name, binding) in e.borrow().real_bindings() {
1198 if !binding.borrow().two_way_bindings.is_empty() {
1199 check_alias(e, name, &binding.borrow());
1200
1201 let nr = NamedReference::new(e, name.clone());
1202 for a in &binding.borrow().two_way_bindings {
1203 if let Some(a) = a.property()
1204 && a != &nr
1205 && !a.element().borrow().enclosing_component.upgrade().unwrap().is_global()
1206 {
1207 reverse_aliases.entry(a.clone()).or_default().push(nr.clone())
1208 }
1209 }
1210 }
1211 }
1212 for decl in e.borrow().property_declarations.values() {
1213 if let Some(alias) = &decl.is_alias {
1214 mark_alias(alias)
1215 }
1216 }
1217 }
1218
1219 fn check_alias(e: &ElementRc, name: &SmolStr, binding: &BindingExpression) {
1220 let is_binding_constant =
1222 binding.is_constant(None) && binding.two_way_bindings.iter().all(|n| n.is_constant());
1223 if is_binding_constant && !NamedReference::new(e, name.clone()).is_externally_modified() {
1224 for alias in binding.two_way_bindings.iter().filter_map(|x| x.property()) {
1225 crate::namedreference::mark_property_set_derived_in_base(
1226 alias.element(),
1227 alias.name(),
1228 );
1229 }
1230 return;
1231 }
1232
1233 propagate_alias(binding);
1234 }
1235
1236 fn propagate_alias(binding: &BindingExpression) {
1237 for alias in binding.two_way_bindings.iter().filter_map(|x| x.property()) {
1238 mark_alias(alias);
1239 }
1240 }
1241
1242 fn mark_alias(alias: &NamedReference) {
1243 alias.mark_as_set();
1244 if !alias.is_externally_modified()
1245 && let Some(bind) = alias.element().borrow().binding(alias.name())
1246 {
1247 propagate_alias(&bind)
1248 }
1249 }
1250}
1251
1252fn mark_used_base_properties(doc: &Document) {
1255 doc.visit_all_used_components(|component| {
1256 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1257 component,
1258 &(),
1259 &mut |element, _| {
1260 if !matches!(element.borrow().base_type, ElementType::Component(_)) {
1261 return;
1262 }
1263 for (name, binding) in element.borrow().real_bindings() {
1264 if binding.borrow().has_binding() {
1265 crate::namedreference::mark_property_set_derived_in_base(
1266 element.clone(),
1267 name,
1268 );
1269 }
1270 }
1271 for name in element.borrow().change_callbacks.keys() {
1272 crate::namedreference::mark_property_read_derived_in_base(
1273 element.clone(),
1274 name,
1275 );
1276 }
1277 },
1278 );
1279 });
1280}