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}
52
53type ReverseAliases = HashMap<NamedReference, Vec<NamedReference>>;
57
58pub fn binding_analysis(
59 doc: &Document,
60 compiler_config: &CompilerConfiguration,
61 diag: &mut BuildDiagnostics,
62) -> GlobalAnalysis {
63 let mut global_analysis = GlobalAnalysis {
64 const_scale_factor: compiler_config.const_scale_factor,
65 ..Default::default()
66 };
67 let mut reverse_aliases = Default::default();
68 mark_used_base_properties(doc);
69 propagate_is_set_on_aliases(doc, &mut reverse_aliases);
70 check_window_properties(doc, &mut global_analysis);
71 perform_binding_analysis(
72 doc,
73 &reverse_aliases,
74 &mut global_analysis,
75 compiler_config.error_on_binding_loop_with_window_layout,
76 diag,
77 );
78 global_analysis
79}
80#[derive(Hash, PartialEq, Eq, Clone)]
83struct PropertyPath {
84 elements: Vec<ByAddress<ElementRc>>,
85 prop: NamedReference,
86}
87
88impl std::fmt::Debug for PropertyPath {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 for e in &self.elements {
91 write!(f, "{}.", e.borrow().id)?;
92 }
93 self.prop.fmt(f)
94 }
95}
96
97impl PropertyPath {
98 fn relative(&self, second: &PropertyPath) -> Self {
102 let mut element =
103 second.elements.first().map_or_else(|| second.prop.element(), |f| f.0.clone());
104 if element.borrow().enclosing_component.upgrade().unwrap().is_global() {
105 return second.clone();
106 }
107 let mut elements = self.elements.clone();
108 loop {
109 let enclosing = element.borrow().enclosing_component.upgrade().unwrap();
110 if enclosing.parent_element().is_some()
111 || !Rc::ptr_eq(&element, &enclosing.root_element)
112 {
113 break;
114 }
115
116 if let Some(last) = elements.pop() {
117 #[cfg(debug_assertions)]
118 fn check_that_element_is_in_the_component(
119 e: &ElementRc,
120 c: &Rc<crate::object_tree::Component>,
121 ) -> bool {
122 let enclosing = e.borrow().enclosing_component.upgrade().unwrap();
123 Rc::ptr_eq(c, &enclosing)
124 || enclosing
125 .parent_element
126 .borrow()
127 .upgrade()
128 .is_some_and(|e| check_that_element_is_in_the_component(&e, c))
129 }
130 #[cfg(debug_assertions)]
131 debug_assert!(
132 check_that_element_is_in_the_component(
133 &element,
134 last.borrow().base_type.as_component()
135 ),
136 "The element is not in the component pointed at by the path ({self:?} / {second:?})"
137 );
138 element = last.0;
139 } else {
140 break;
141 }
142 }
143 if second.elements.is_empty() {
144 debug_assert!(elements.last().is_none_or(|x| *x != ByAddress(second.prop.element())));
145 Self { elements, prop: NamedReference::new(&element, second.prop.name().clone()) }
146 } else {
147 elements.push(ByAddress(element));
148 elements.extend(second.elements.iter().skip(1).cloned());
149 Self { elements, prop: second.prop.clone() }
150 }
151 }
152}
153
154impl From<NamedReference> for PropertyPath {
155 fn from(prop: NamedReference) -> Self {
156 Self { elements: Vec::new(), prop }
157 }
158}
159
160struct AnalysisContext<'a> {
161 visited: HashSet<PropertyPath>,
162 currently_analyzing: linked_hash_set::LinkedHashSet<PropertyPath>,
164 window_layout_property: Option<PropertyPath>,
167 error_on_binding_loop_with_window_layout: bool,
168 global_analysis: &'a mut GlobalAnalysis,
169}
170
171fn perform_binding_analysis(
172 doc: &Document,
173 reverse_aliases: &ReverseAliases,
174 global_analysis: &mut GlobalAnalysis,
175 error_on_binding_loop_with_window_layout: bool,
176 diag: &mut BuildDiagnostics,
177) {
178 let mut context = AnalysisContext {
179 error_on_binding_loop_with_window_layout,
180 visited: HashSet::new(),
181 currently_analyzing: Default::default(),
182 window_layout_property: None,
183 global_analysis,
184 };
185 doc.visit_all_used_components(|component| {
186 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
187 component,
188 &(),
189 &mut |e, _| analyze_element(e, &mut context, reverse_aliases, diag),
190 )
191 });
192}
193
194fn analyze_element(
195 elem: &ElementRc,
196 context: &mut AnalysisContext,
197 reverse_aliases: &ReverseAliases,
198 diag: &mut BuildDiagnostics,
199) {
200 for (name, binding) in &elem.borrow().bindings {
201 if binding.borrow().analysis.is_some() {
202 continue;
203 }
204 analyze_binding(
205 &PropertyPath::from(NamedReference::new(elem, name.clone())),
206 context,
207 reverse_aliases,
208 diag,
209 );
210 }
211 for cb in elem.borrow().change_callbacks.values() {
212 for e in cb.borrow().iter() {
213 recurse_expression(elem, e, &mut |prop, r| {
214 process_property(prop, r, context, reverse_aliases, diag);
215 });
216 }
217 }
218 const P: ReadType = ReadType::PropertyRead;
219 for nr in elem.borrow().accessibility_props.0.values() {
220 process_property(&PropertyPath::from(nr.clone()), P, context, reverse_aliases, diag);
221 }
222 if let Some(g) = elem.borrow().geometry_props.as_ref() {
223 process_property(&g.x.clone().into(), P, context, reverse_aliases, diag);
224 process_property(&g.y.clone().into(), P, context, reverse_aliases, diag);
225 process_property(&g.width.clone().into(), P, context, reverse_aliases, diag);
226 process_property(&g.height.clone().into(), P, context, reverse_aliases, diag);
227 }
228
229 if let Some(component) = elem.borrow().enclosing_component.upgrade()
230 && Rc::ptr_eq(&component.root_element, elem)
231 {
232 for e in component.init_code.borrow().iter() {
233 recurse_expression(elem, e, &mut |prop, r| {
234 process_property(prop, r, context, reverse_aliases, diag);
235 });
236 }
237 component.root_constraints.borrow_mut().visit_named_references(&mut |nr| {
238 process_property(&nr.clone().into(), P, context, reverse_aliases, diag);
239 });
240 component.popup_windows.borrow().iter().for_each(|p| {
241 process_property(&p.x.clone().into(), P, context, reverse_aliases, diag);
242 process_property(&p.y.clone().into(), P, context, reverse_aliases, diag);
243 });
244 component.timers.borrow().iter().for_each(|t| {
245 process_property(&t.interval.clone().into(), P, context, reverse_aliases, diag);
246 process_property(&t.running.clone().into(), P, context, reverse_aliases, diag);
247 process_property(&t.triggered.clone().into(), P, context, reverse_aliases, diag);
248 });
249 }
250
251 if let Some(repeated) = &elem.borrow().repeated {
252 recurse_expression(elem, &repeated.model, &mut |prop, r| {
253 process_property(prop, r, context, reverse_aliases, diag);
254 });
255 if let Some(lv) = &repeated.is_listview {
256 process_property(&lv.viewport_y.clone().into(), P, context, reverse_aliases, diag);
257 process_property(&lv.viewport_height.clone().into(), P, context, reverse_aliases, diag);
258 process_property(&lv.viewport_width.clone().into(), P, context, reverse_aliases, diag);
259 process_property(&lv.listview_height.clone().into(), P, context, reverse_aliases, diag);
260 process_property(&lv.listview_width.clone().into(), P, context, reverse_aliases, diag);
261 }
262 }
263 if let Some((h, v)) = &elem.borrow().layout_info_prop {
264 process_property(&h.clone().into(), P, context, reverse_aliases, diag);
265 process_property(&v.clone().into(), P, context, reverse_aliases, diag);
266 }
267
268 for info in elem.borrow().debug.iter() {
269 if let Some(crate::layout::Layout::GridLayout(grid)) = &info.layout
270 && grid.uses_auto
271 {
272 for rowcol_prop_name in ["row", "col"] {
273 for it in grid.elems.iter() {
274 let child = &it.item.element;
275 if child
276 .borrow()
277 .property_analysis
278 .borrow()
279 .get(rowcol_prop_name)
280 .is_some_and(|a| a.is_set || a.is_set_externally)
281 {
282 diag.push_error(
283 format!("Cannot set property '{}' on '{}' because parent GridLayout uses auto-numbering",
284 rowcol_prop_name, child.borrow().id),
285 &child.borrow().to_source_location(), );
287 }
288 }
289 }
290 }
291 }
292}
293
294#[derive(Copy, Clone, dm::BitAnd, dm::BitOr, dm::BitAndAssign, dm::BitOrAssign)]
295struct DependsOnExternal(bool);
296
297fn analyze_binding(
298 current: &PropertyPath,
299 context: &mut AnalysisContext,
300 reverse_aliases: &ReverseAliases,
301 diag: &mut BuildDiagnostics,
302) -> DependsOnExternal {
303 let mut depends_on_external = DependsOnExternal(false);
304 let element = current.prop.element();
305 let name = current.prop.name();
306 if (context.currently_analyzing.back() == Some(current))
307 && !element.borrow().bindings[name].borrow().two_way_bindings.is_empty()
308 {
309 let span = element.borrow().bindings[name]
310 .borrow()
311 .span
312 .clone()
313 .unwrap_or_else(|| element.borrow().to_source_location());
314 diag.push_error(format!("Property '{name}' cannot refer to itself"), &span);
315 return depends_on_external;
316 }
317
318 if context.currently_analyzing.contains(current) {
319 let mut loop_description = String::new();
320 let mut has_window_layout = false;
321
322 fn push_prop(prop: &PropertyPath, out: &mut String) {
323 if !out.is_empty() {
324 out.push_str(" -> ");
325 }
326 match prop.prop.element().borrow().id.as_str() {
327 "" => out.push_str(prop.prop.name()),
328 id => {
329 out.push_str(id);
330 out.push('.');
331 out.push_str(prop.prop.name());
332 }
333 }
334 }
335
336 push_prop(current, &mut loop_description);
339 for it in context.currently_analyzing.iter().rev() {
340 if context.window_layout_property.as_ref().is_some_and(|p| p == it) {
341 has_window_layout = true;
342 }
343 push_prop(it, &mut loop_description);
344 if it == current {
345 break;
346 }
347 }
348
349 for it in context.currently_analyzing.iter().rev() {
350 let p = &it.prop;
351 let elem = p.element();
352 let elem = elem.borrow();
353 let binding = elem.bindings[p.name()].borrow();
354 if binding.analysis.as_ref().unwrap().is_in_binding_loop.replace(true) {
355 break;
356 }
357
358 let span = binding.span.clone().unwrap_or_else(|| elem.to_source_location());
359 if !context.error_on_binding_loop_with_window_layout && has_window_layout {
360 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);
361 } else {
362 diag.push_error(format!("The binding for the property '{}' is part of a binding loop ({loop_description})", p.name()), &span);
363 }
364 if it == current {
365 break;
366 }
367 }
368 return depends_on_external;
369 }
370
371 let binding = &element.borrow().bindings[name];
372 if binding.borrow().analysis.as_ref().is_some_and(|a| a.no_external_dependencies) {
373 return depends_on_external;
374 } else if !context.visited.insert(current.clone()) {
375 return DependsOnExternal(true);
376 }
377
378 if let Ok(mut b) = binding.try_borrow_mut() {
379 b.analysis = Some(Default::default());
380 };
381 context.currently_analyzing.insert(current.clone());
382
383 let b = binding.borrow();
384 for twb in &b.two_way_bindings {
385 if let Some(p) = twb.property()
386 && p != ¤t.prop
387 {
388 depends_on_external |= process_property(
389 ¤t.relative(&p.clone().into()),
390 ReadType::PropertyRead,
391 context,
392 reverse_aliases,
393 diag,
394 );
395 }
396 }
397
398 let mut process_prop = |prop: &PropertyPath, r, context: &mut AnalysisContext| {
399 depends_on_external |=
400 process_property(¤t.relative(prop), r, context, reverse_aliases, diag);
401 for x in reverse_aliases.get(&prop.prop).unwrap_or(&Default::default()) {
402 if x != ¤t.prop && x != &prop.prop {
403 depends_on_external |= process_property(
404 ¤t.relative(&x.clone().into()),
405 ReadType::PropertyRead,
406 context,
407 reverse_aliases,
408 diag,
409 );
410 }
411 }
412 };
413
414 recurse_expression(¤t.prop.element(), &b.expression, &mut |p, r| {
415 process_prop(p, r, context)
416 });
417
418 let mut is_const = b.expression.is_constant(Some(context.global_analysis))
419 && b.two_way_bindings.iter().all(|n| n.is_constant());
420
421 if is_const && matches!(b.expression, Expression::Invalid) {
422 if let Some(base) = element.borrow().sub_component() {
424 is_const = NamedReference::new(&base.root_element, name.clone()).is_constant();
425 }
426 }
427 drop(b);
428
429 if let Ok(mut b) = binding.try_borrow_mut() {
430 b.analysis.as_mut().unwrap().is_const = is_const;
432 }
433
434 match &binding.borrow().animation {
435 Some(PropertyAnimation::Static(e)) => analyze_element(e, context, reverse_aliases, diag),
436 Some(PropertyAnimation::Transition { animations, state_ref }) => {
437 recurse_expression(¤t.prop.element(), state_ref, &mut |p, r| {
438 process_prop(p, r, context)
439 });
440 for a in animations {
441 analyze_element(&a.animation, context, reverse_aliases, diag);
442 }
443 }
444 None => (),
445 }
446
447 let o = context.currently_analyzing.pop_back();
448 assert_eq!(&o.unwrap(), current);
449
450 depends_on_external
451}
452
453#[derive(Copy, Clone, Eq, PartialEq)]
454enum ReadType {
455 NativeRead,
457 PropertyRead,
459}
460
461fn process_property(
465 prop: &PropertyPath,
466 read_type: ReadType,
467 context: &mut AnalysisContext,
468 reverse_aliases: &ReverseAliases,
469 diag: &mut BuildDiagnostics,
470) -> DependsOnExternal {
471 #[allow(clippy::match_single_binding)]
472 let depends_on_external = match prop
473 .prop
474 .element()
475 .borrow()
476 .property_analysis
477 .borrow_mut()
478 .entry(prop.prop.name().clone())
479 .or_default()
480 {
481 a => {
482 if read_type == ReadType::PropertyRead {
483 a.is_read = true;
484 }
485 DependsOnExternal(prop.elements.is_empty() && a.is_set_externally)
486 }
487 };
488
489 let mut prop = prop.clone();
490
491 loop {
492 let element = prop.prop.element();
493 if element.borrow().bindings.contains_key(prop.prop.name()) {
494 analyze_binding(&prop, context, reverse_aliases, diag);
495 break;
496 }
497 let next = match &element.borrow().base_type {
498 ElementType::Component(base) => {
499 if element.borrow().property_declarations.contains_key(prop.prop.name()) {
500 break;
501 }
502 base.root_element.clone()
503 }
504 ElementType::Builtin(builtin) => {
505 if builtin.properties.contains_key(prop.prop.name()) {
506 visit_builtin_property(builtin, &prop, context, reverse_aliases, diag);
507 }
508 break;
509 }
510 _ => break,
511 };
512 next.borrow()
513 .property_analysis
514 .borrow_mut()
515 .entry(prop.prop.name().clone())
516 .or_default()
517 .is_read_externally = true;
518 prop.elements.push(element.into());
519 prop.prop = NamedReference::new(&next, prop.prop.name().clone());
520 }
521 depends_on_external
522}
523
524fn recurse_expression(
526 elem: &ElementRc,
527 expr: &Expression,
528 vis: &mut impl FnMut(&PropertyPath, ReadType),
529) {
530 const P: ReadType = ReadType::PropertyRead;
531 expr.visit(|sub| recurse_expression(elem, sub, vis));
532 match expr {
533 Expression::PropertyReference(r) => vis(&r.clone().into(), P),
534 Expression::LayoutCacheAccess { layout_cache_prop, .. } => {
535 vis(&layout_cache_prop.clone().into(), P)
536 }
537 Expression::GridRepeaterCacheAccess { layout_cache_prop, .. } => {
538 vis(&layout_cache_prop.clone().into(), P)
539 }
540 Expression::SolveBoxLayout(l, o)
541 | Expression::ComputeBoxLayoutInfo { layout: l, orientation: o, .. } => {
542 if matches!(expr, Expression::SolveBoxLayout(..))
544 && let Some(nr) = l.geometry.rect.size_reference(*o)
545 {
546 vis(&nr.clone().into(), P);
547 }
548 visit_layout_items_dependencies(l.elems.iter(), *o, vis);
549
550 if matches!(expr, Expression::SolveBoxLayout(..))
552 && *o != l.orientation
553 && let Some(nr) = l.cross_alignment.as_ref()
554 {
555 vis(&nr.clone().into(), P);
556 }
557
558 let mut g = l.geometry.clone();
559 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
561 }
562 Expression::SolveFlexboxLayout(layout)
563 | Expression::ComputeFlexboxLayoutInfo { layout, .. } => {
564 if let Some(nr) = layout.direction.as_ref() {
565 vis(&nr.clone().into(), P);
566 }
567 if matches!(expr, Expression::SolveFlexboxLayout(..)) {
569 use crate::layout::FlexboxAxisRelation;
581 match layout.axis_relation(Orientation::Horizontal) {
582 FlexboxAxisRelation::MainAxis => {
583 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
584 vis(&nr.clone().into(), P);
585 }
586 visit_layout_items_layoutinfo_cross_axis_dependencies(
587 layout.elems.iter().map(|fi| &fi.item),
588 Orientation::Vertical,
589 vis,
590 );
591 }
592 FlexboxAxisRelation::CrossAxis => {
593 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
594 vis(&nr.clone().into(), P);
595 }
596 visit_layout_items_layoutinfo_cross_axis_dependencies(
597 layout.elems.iter().map(|fi| &fi.item),
598 Orientation::Horizontal,
599 vis,
600 );
601 }
602 FlexboxAxisRelation::Unknown => {
603 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
605 vis(&nr.clone().into(), P);
606 }
607 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
608 vis(&nr.clone().into(), P);
609 }
610 visit_layout_items_layoutinfo_cross_axis_dependencies(
611 layout.elems.iter().map(|fi| &fi.item),
612 Orientation::Horizontal,
613 vis,
614 );
615 visit_layout_items_layoutinfo_cross_axis_dependencies(
616 layout.elems.iter().map(|fi| &fi.item),
617 Orientation::Vertical,
618 vis,
619 );
620 }
621 }
622 } else if let Expression::ComputeFlexboxLayoutInfo { orientation, .. } = expr {
623 let orientation = *orientation;
624 use crate::layout::FlexboxAxisRelation;
625 match layout.axis_relation(orientation) {
626 FlexboxAxisRelation::MainAxis => {
627 visit_layout_items_dependencies(
629 layout.elems.iter().map(|fi| &fi.item),
630 orientation,
631 vis,
632 );
633 }
634 FlexboxAxisRelation::CrossAxis => {
635 if orientation == Orientation::Vertical
642 && let Some(nr) = layout.geometry.rect.width_reference.as_ref()
643 && nr.element().borrow().layout_info_v_with_constraint.is_none()
644 {
645 vis(&nr.clone().into(), P);
646 }
647 if orientation == Orientation::Horizontal
648 && let Some(nr) = layout.geometry.rect.height_reference.as_ref()
649 && nr.element().borrow().layout_info_h_with_constraint.is_none()
650 {
651 vis(&nr.clone().into(), P);
652 }
653 visit_layout_items_dependencies(
654 layout.elems.iter().map(|fi| &fi.item),
655 Orientation::Horizontal,
656 vis,
657 );
658 visit_layout_items_dependencies(
659 layout.elems.iter().map(|fi| &fi.item),
660 Orientation::Vertical,
661 vis,
662 );
663 }
664 FlexboxAxisRelation::Unknown => {
665 visit_layout_items_dependencies(
669 layout.elems.iter().map(|fi| &fi.item),
670 Orientation::Horizontal,
671 vis,
672 );
673 visit_layout_items_dependencies(
674 layout.elems.iter().map(|fi| &fi.item),
675 Orientation::Vertical,
676 vis,
677 );
678 }
679 }
680 }
681 let mut g = layout.geometry.clone();
682 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
684 }
685 Expression::OrganizeGridLayout(layout) => {
686 let mut layout = layout.clone();
687 layout.visit_rowcol_named_references(&mut |nr: &mut NamedReference| {
688 vis(&nr.clone().into(), P)
689 });
690 }
691 Expression::SolveGridLayout { layout_organized_data_prop, layout, orientation }
692 | Expression::ComputeGridLayoutInfo {
693 layout_organized_data_prop,
694 layout,
695 orientation,
696 ..
697 } => {
698 if matches!(expr, Expression::SolveGridLayout { .. })
700 && let Some(nr) = layout.geometry.rect.size_reference(*orientation)
701 {
702 vis(&nr.clone().into(), P);
703 }
704 vis(&layout_organized_data_prop.clone().into(), P);
705 visit_layout_items_dependencies(
706 layout.elems.iter().map(|it| &it.item),
707 *orientation,
708 vis,
709 );
710 let mut g = layout.geometry.clone();
711 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
713 }
714 Expression::FunctionCall {
715 function: Callable::Callback(nr) | Callable::Function(nr),
716 ..
717 } => vis(&nr.clone().into(), P),
718 Expression::FunctionCall { function: Callable::Builtin(b), arguments, .. } => match b {
719 BuiltinFunction::ImplicitLayoutInfo(orientation) => {
720 if let [Expression::ElementReference(item), ..] = arguments.as_slice() {
721 visit_implicit_layout_info_dependencies(
722 *orientation,
723 &item.upgrade().unwrap(),
724 vis,
725 );
726 }
727 }
728 BuiltinFunction::ItemAbsolutePosition => {
729 if let Some(Expression::ElementReference(item)) = arguments.first() {
730 let mut item = item.upgrade().unwrap();
731 while let Some(parent) = find_parent_element(&item) {
732 item = parent;
733 vis(
734 &NamedReference::new(&item, SmolStr::new_static("x")).into(),
735 ReadType::NativeRead,
736 );
737 vis(
738 &NamedReference::new(&item, SmolStr::new_static("y")).into(),
739 ReadType::NativeRead,
740 );
741 }
742 }
743 }
744 BuiltinFunction::ItemFontMetrics => {
745 if let Some(Expression::ElementReference(item)) = arguments.first() {
746 let item = item.upgrade().unwrap();
747 vis(
748 &NamedReference::new(&item, SmolStr::new_static("font-size")).into(),
749 ReadType::NativeRead,
750 );
751 vis(
752 &NamedReference::new(&item, SmolStr::new_static("font-weight")).into(),
753 ReadType::NativeRead,
754 );
755 vis(
756 &NamedReference::new(&item, SmolStr::new_static("font-family")).into(),
757 ReadType::NativeRead,
758 );
759 vis(
760 &NamedReference::new(&item, SmolStr::new_static("font-italic")).into(),
761 ReadType::NativeRead,
762 );
763 }
764 }
765 BuiltinFunction::GetWindowDefaultFontSize => {
766 let root =
767 elem.borrow().enclosing_component.upgrade().unwrap().root_element.clone();
768 if root.borrow().builtin_type().is_some_and(|bt| bt.name == "Window") {
769 vis(
770 &NamedReference::new(&root, SmolStr::new_static("default-font-size"))
771 .into(),
772 ReadType::PropertyRead,
773 );
774 }
775 }
776 _ => {}
777 },
778 _ => {}
779 }
780}
781
782fn visit_layout_items_dependencies<'a>(
783 items: impl Iterator<Item = &'a LayoutItem>,
784 orientation: Orientation,
785 vis: &mut impl FnMut(&PropertyPath, ReadType),
786) {
787 for it in items {
788 let mut element = it.element.clone();
789 if element
790 .borrow()
791 .repeated
792 .as_ref()
793 .map(|r| recurse_expression(&element, &r.model, vis))
794 .is_some()
795 {
796 element = it.element.borrow().base_type.as_component().root_element.clone();
797 }
798
799 if let Some(nr) = element.borrow().layout_info_prop(orientation) {
800 vis(&nr.clone().into(), ReadType::PropertyRead);
801 } else {
802 if let ElementType::Component(base) = &element.borrow().base_type
803 && let Some(nr) = base.root_element.borrow().layout_info_prop(orientation)
804 {
805 vis(
806 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr.clone() },
807 ReadType::PropertyRead,
808 );
809 }
810 visit_implicit_layout_info_dependencies(orientation, &element, vis);
811 }
812
813 for (nr, _) in it.constraints.for_each_restrictions(orientation) {
814 vis(&nr.clone().into(), ReadType::PropertyRead)
815 }
816 }
817}
818
819fn visit_layout_items_layoutinfo_cross_axis_dependencies<'a>(
834 items: impl Iterator<Item = &'a LayoutItem>,
835 cross_axis: Orientation,
836 vis: &mut impl FnMut(&PropertyPath, ReadType),
837) {
838 for it in items {
839 let element = it.element.clone();
840 let bypassed = match cross_axis {
842 Orientation::Vertical => {
843 element.borrow().inherited_layout_info_v_with_constraint().is_some()
844 }
845 Orientation::Horizontal => {
846 element.borrow().inherited_layout_info_h_with_constraint().is_some()
847 }
848 };
849 if bypassed {
850 continue;
851 }
852 if let Some(nr) = element.borrow().layout_info_prop(cross_axis) {
853 vis(&nr.clone().into(), ReadType::PropertyRead);
854 } else if let ElementType::Component(base) = &element.borrow().base_type
855 && let Some(nr) = base.root_element.borrow().layout_info_prop(cross_axis)
856 {
857 vis(
858 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr.clone() },
859 ReadType::PropertyRead,
860 );
861 } else {
862 visit_cell_cross_axis_implicit_dependency(cross_axis, &element, vis);
863 }
864 }
865}
866
867fn visit_cell_cross_axis_implicit_dependency(
877 cross_axis: Orientation,
878 item: &ElementRc,
879 vis: &mut impl FnMut(&PropertyPath, ReadType),
880) {
881 let base_type = item.borrow().base_type.to_smolstr();
882 if matches!(base_type.as_str(), "Image" | "ClippedImage" | "Text" | "TextInput" | "StyledText")
883 {
884 return;
885 }
886 let (prop, opposite_dim) = match cross_axis {
887 Orientation::Horizontal => ("preferred-width", "height"),
888 Orientation::Vertical => ("preferred-height", "width"),
889 };
890 if !item.borrow().is_binding_set(prop, false) {
891 return;
892 }
893 let reads_opposite = item
894 .borrow()
895 .bindings
896 .get(prop)
897 .map(|b| {
898 let mut seen = false;
899 b.borrow().expression.visit_recursive(&mut |sub| {
900 if let Expression::PropertyReference(nr) = sub
901 && nr.name() == opposite_dim
902 && Rc::ptr_eq(&nr.element(), item)
903 {
904 seen = true;
905 }
906 });
907 seen
908 })
909 .unwrap_or(false);
910 if reads_opposite {
911 vis(&NamedReference::new(item, SmolStr::new_static(prop)).into(), ReadType::NativeRead);
912 }
913}
914
915fn visit_implicit_layout_info_dependencies(
917 orientation: crate::layout::Orientation,
918 item: &ElementRc,
919 vis: &mut impl FnMut(&PropertyPath, ReadType),
920) {
921 let base_type = item.borrow().base_type.to_smolstr();
922 const N: ReadType = ReadType::NativeRead;
923 match base_type.as_str() {
924 "Image" => {
925 vis(&NamedReference::new(item, SmolStr::new_static("source")).into(), N);
926 vis(&NamedReference::new(item, SmolStr::new_static("source-clip-width")).into(), N);
927 if orientation == Orientation::Vertical {
928 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
929 vis(
930 &NamedReference::new(item, SmolStr::new_static("source-clip-height")).into(),
931 N,
932 );
933 }
934 }
935 "Text" | "TextInput" => {
936 vis(&NamedReference::new(item, SmolStr::new_static("text")).into(), N);
937 vis(&NamedReference::new(item, SmolStr::new_static("font-family")).into(), N);
938 vis(&NamedReference::new(item, SmolStr::new_static("font-size")).into(), N);
939 vis(&NamedReference::new(item, SmolStr::new_static("font-weight")).into(), N);
940 vis(&NamedReference::new(item, SmolStr::new_static("letter-spacing")).into(), N);
941 vis(&NamedReference::new(item, SmolStr::new_static("wrap")).into(), N);
942 let wrap_set = item.borrow().is_binding_set("wrap", false)
943 || item
944 .borrow()
945 .property_analysis
946 .borrow()
947 .get("wrap")
948 .is_some_and(|a| a.is_set || a.is_set_externally);
949 if wrap_set && orientation == Orientation::Vertical {
950 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
951 }
952 if base_type.as_str() == "TextInput" {
953 vis(&NamedReference::new(item, SmolStr::new_static("single-line")).into(), N);
954 } else {
955 vis(&NamedReference::new(item, SmolStr::new_static("overflow")).into(), N);
956 }
957 }
958
959 _ => (),
960 }
961}
962
963fn visit_builtin_property(
964 builtin: &crate::langtype::BuiltinElement,
965 prop: &PropertyPath,
966 context: &mut AnalysisContext,
967 reverse_aliases: &ReverseAliases,
968 diag: &mut BuildDiagnostics,
969) {
970 let name = prop.prop.name();
971 if builtin.name == "Window" {
972 for (p, orientation) in
973 [("width", Orientation::Horizontal), ("height", Orientation::Vertical)]
974 {
975 if name == p {
976 let is_root = |e: &ElementRc| -> bool {
978 ElementRc::ptr_eq(
979 e,
980 &e.borrow().enclosing_component.upgrade().unwrap().root_element,
981 )
982 };
983 let mut root = prop.prop.element();
984 if !is_root(&root) {
985 return;
986 };
987 for e in prop.elements.iter().rev() {
988 if !is_root(&e.0) {
989 return;
990 }
991 root = e.0.clone();
992 }
993 if let Some(p) = root.borrow().layout_info_prop(orientation) {
994 let path = PropertyPath::from(p.clone());
995 let old_layout = context.window_layout_property.replace(path.clone());
996 process_property(&path, ReadType::NativeRead, context, reverse_aliases, diag);
997 context.window_layout_property = old_layout;
998 };
999 }
1000 }
1001 }
1002}
1003
1004fn check_window_properties(doc: &Document, global_analysis: &mut GlobalAnalysis) {
1006 doc.visit_all_used_components(|component| {
1007 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1008 component,
1009 &(),
1010 &mut |elem, _| {
1011 if elem.borrow().builtin_type().as_ref().is_some_and(|b| b.name == "Window") {
1012 const DEFAULT_FONT_SIZE: &str = "default-font-size";
1013 if elem.borrow().is_binding_set(DEFAULT_FONT_SIZE, false)
1014 || elem
1015 .borrow()
1016 .property_analysis
1017 .borrow()
1018 .get(DEFAULT_FONT_SIZE)
1019 .is_some_and(|a| a.is_set)
1020 {
1021 let value = elem.borrow().bindings.get(DEFAULT_FONT_SIZE).and_then(|e| {
1022 match &e.borrow().expression {
1023 Expression::NumberLiteral(v, crate::expression_tree::Unit::Px) => {
1024 Some(*v as f32)
1025 }
1026 _ => None,
1027 }
1028 });
1029 let is_const = value.is_some()
1030 || NamedReference::new(elem, SmolStr::new_static(DEFAULT_FONT_SIZE))
1031 .is_constant();
1032 global_analysis.default_font_size = match global_analysis.default_font_size
1033 {
1034 DefaultFontSize::Unknown => match value {
1035 Some(v) => DefaultFontSize::LogicalValue(v),
1036 None if is_const => DefaultFontSize::Const,
1037 None => DefaultFontSize::Variable,
1038 },
1039 DefaultFontSize::NotSet if is_const => DefaultFontSize::NotSet,
1040 DefaultFontSize::LogicalValue(val) => match value {
1041 Some(v) if v == val => DefaultFontSize::LogicalValue(val),
1042 _ if is_const => DefaultFontSize::Const,
1043 _ => DefaultFontSize::Variable,
1044 },
1045 DefaultFontSize::Const if is_const => DefaultFontSize::Const,
1046 _ => DefaultFontSize::Variable,
1047 }
1048 } else {
1049 global_analysis.default_font_size = match global_analysis.default_font_size
1050 {
1051 DefaultFontSize::Unknown => DefaultFontSize::NotSet,
1052 DefaultFontSize::NotSet => DefaultFontSize::NotSet,
1053 DefaultFontSize::LogicalValue(_) => DefaultFontSize::NotSet,
1054 DefaultFontSize::Const => DefaultFontSize::NotSet,
1055 DefaultFontSize::Variable => DefaultFontSize::Variable,
1056 }
1057 }
1058 }
1059 },
1060 );
1061 });
1062}
1063
1064fn propagate_is_set_on_aliases(doc: &Document, reverse_aliases: &mut ReverseAliases) {
1076 doc.visit_all_used_components(|component| {
1077 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1078 component,
1079 &(),
1080 &mut |e, _| visit_element(e, reverse_aliases),
1081 );
1082 });
1083
1084 fn visit_element(e: &ElementRc, reverse_aliases: &mut ReverseAliases) {
1085 for (name, binding) in &e.borrow().bindings {
1086 if !binding.borrow().two_way_bindings.is_empty() {
1087 check_alias(e, name, &binding.borrow());
1088
1089 let nr = NamedReference::new(e, name.clone());
1090 for a in &binding.borrow().two_way_bindings {
1091 if let Some(a) = a.property()
1092 && a != &nr
1093 && !a.element().borrow().enclosing_component.upgrade().unwrap().is_global()
1094 {
1095 reverse_aliases.entry(a.clone()).or_default().push(nr.clone())
1096 }
1097 }
1098 }
1099 }
1100 for decl in e.borrow().property_declarations.values() {
1101 if let Some(alias) = &decl.is_alias {
1102 mark_alias(alias)
1103 }
1104 }
1105 }
1106
1107 fn check_alias(e: &ElementRc, name: &SmolStr, binding: &BindingExpression) {
1108 let is_binding_constant =
1110 binding.is_constant(None) && binding.two_way_bindings.iter().all(|n| n.is_constant());
1111 if is_binding_constant && !NamedReference::new(e, name.clone()).is_externally_modified() {
1112 for alias in binding.two_way_bindings.iter().filter_map(|x| x.property()) {
1113 crate::namedreference::mark_property_set_derived_in_base(
1114 alias.element(),
1115 alias.name(),
1116 );
1117 }
1118 return;
1119 }
1120
1121 propagate_alias(binding);
1122 }
1123
1124 fn propagate_alias(binding: &BindingExpression) {
1125 for alias in binding.two_way_bindings.iter().filter_map(|x| x.property()) {
1126 mark_alias(alias);
1127 }
1128 }
1129
1130 fn mark_alias(alias: &NamedReference) {
1131 alias.mark_as_set();
1132 if !alias.is_externally_modified()
1133 && let Some(bind) = alias.element().borrow().bindings.get(alias.name())
1134 {
1135 propagate_alias(&bind.borrow())
1136 }
1137 }
1138}
1139
1140fn mark_used_base_properties(doc: &Document) {
1143 doc.visit_all_used_components(|component| {
1144 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1145 component,
1146 &(),
1147 &mut |element, _| {
1148 if !matches!(element.borrow().base_type, ElementType::Component(_)) {
1149 return;
1150 }
1151 for (name, binding) in &element.borrow().bindings {
1152 if binding.borrow().has_binding() {
1153 crate::namedreference::mark_property_set_derived_in_base(
1154 element.clone(),
1155 name,
1156 );
1157 }
1158 }
1159 for name in element.borrow().change_callbacks.keys() {
1160 crate::namedreference::mark_property_read_derived_in_base(
1161 element.clone(),
1162 name,
1163 );
1164 }
1165 },
1166 );
1167 });
1168}