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 twb.property != current.prop {
386 depends_on_external |= process_property(
387 ¤t.relative(&twb.property.clone().into()),
388 ReadType::PropertyRead,
389 context,
390 reverse_aliases,
391 diag,
392 );
393 }
394 }
395
396 let mut process_prop = |prop: &PropertyPath, r, context: &mut AnalysisContext| {
397 depends_on_external |=
398 process_property(¤t.relative(prop), r, context, reverse_aliases, diag);
399 for x in reverse_aliases.get(&prop.prop).unwrap_or(&Default::default()) {
400 if x != ¤t.prop && x != &prop.prop {
401 depends_on_external |= process_property(
402 ¤t.relative(&x.clone().into()),
403 ReadType::PropertyRead,
404 context,
405 reverse_aliases,
406 diag,
407 );
408 }
409 }
410 };
411
412 recurse_expression(¤t.prop.element(), &b.expression, &mut |p, r| {
413 process_prop(p, r, context)
414 });
415
416 let mut is_const = b.expression.is_constant(Some(context.global_analysis))
417 && b.two_way_bindings.iter().all(|n| n.property.is_constant());
418
419 if is_const && matches!(b.expression, Expression::Invalid) {
420 if let Some(base) = element.borrow().sub_component() {
422 is_const = NamedReference::new(&base.root_element, name.clone()).is_constant();
423 }
424 }
425 drop(b);
426
427 if let Ok(mut b) = binding.try_borrow_mut() {
428 b.analysis.as_mut().unwrap().is_const = is_const;
430 }
431
432 match &binding.borrow().animation {
433 Some(PropertyAnimation::Static(e)) => analyze_element(e, context, reverse_aliases, diag),
434 Some(PropertyAnimation::Transition { animations, state_ref }) => {
435 recurse_expression(¤t.prop.element(), state_ref, &mut |p, r| {
436 process_prop(p, r, context)
437 });
438 for a in animations {
439 analyze_element(&a.animation, context, reverse_aliases, diag);
440 }
441 }
442 None => (),
443 }
444
445 let o = context.currently_analyzing.pop_back();
446 assert_eq!(&o.unwrap(), current);
447
448 depends_on_external
449}
450
451#[derive(Copy, Clone, Eq, PartialEq)]
452enum ReadType {
453 NativeRead,
455 PropertyRead,
457}
458
459fn process_property(
463 prop: &PropertyPath,
464 read_type: ReadType,
465 context: &mut AnalysisContext,
466 reverse_aliases: &ReverseAliases,
467 diag: &mut BuildDiagnostics,
468) -> DependsOnExternal {
469 #[allow(clippy::match_single_binding)]
470 let depends_on_external = match prop
471 .prop
472 .element()
473 .borrow()
474 .property_analysis
475 .borrow_mut()
476 .entry(prop.prop.name().clone())
477 .or_default()
478 {
479 a => {
480 if read_type == ReadType::PropertyRead {
481 a.is_read = true;
482 }
483 DependsOnExternal(prop.elements.is_empty() && a.is_set_externally)
484 }
485 };
486
487 let mut prop = prop.clone();
488
489 loop {
490 let element = prop.prop.element();
491 if element.borrow().bindings.contains_key(prop.prop.name()) {
492 analyze_binding(&prop, context, reverse_aliases, diag);
493 break;
494 }
495 let next = match &element.borrow().base_type {
496 ElementType::Component(base) => {
497 if element.borrow().property_declarations.contains_key(prop.prop.name()) {
498 break;
499 }
500 base.root_element.clone()
501 }
502 ElementType::Builtin(builtin) => {
503 if builtin.properties.contains_key(prop.prop.name()) {
504 visit_builtin_property(builtin, &prop, context, reverse_aliases, diag);
505 }
506 break;
507 }
508 _ => break,
509 };
510 next.borrow()
511 .property_analysis
512 .borrow_mut()
513 .entry(prop.prop.name().clone())
514 .or_default()
515 .is_read_externally = true;
516 prop.elements.push(element.into());
517 prop.prop = NamedReference::new(&next, prop.prop.name().clone());
518 }
519 depends_on_external
520}
521
522fn recurse_expression(
524 elem: &ElementRc,
525 expr: &Expression,
526 vis: &mut impl FnMut(&PropertyPath, ReadType),
527) {
528 const P: ReadType = ReadType::PropertyRead;
529 expr.visit(|sub| recurse_expression(elem, sub, vis));
530 match expr {
531 Expression::PropertyReference(r) => vis(&r.clone().into(), P),
532 Expression::LayoutCacheAccess { layout_cache_prop, .. } => {
533 vis(&layout_cache_prop.clone().into(), P)
534 }
535 Expression::GridRepeaterCacheAccess { layout_cache_prop, .. } => {
536 vis(&layout_cache_prop.clone().into(), P)
537 }
538 Expression::SolveBoxLayout(l, o) | Expression::ComputeBoxLayoutInfo(l, o) => {
539 if matches!(expr, Expression::SolveBoxLayout(..))
541 && let Some(nr) = l.geometry.rect.size_reference(*o)
542 {
543 vis(&nr.clone().into(), P);
544 }
545 visit_layout_items_dependencies(l.elems.iter(), *o, vis);
546
547 let mut g = l.geometry.clone();
548 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
550 }
551 Expression::SolveFlexboxLayout(layout)
552 | Expression::ComputeFlexboxLayoutInfo(layout, _) => {
553 if let Some(nr) = layout.direction.as_ref() {
554 vis(&nr.clone().into(), P);
555 }
556 if matches!(expr, Expression::SolveFlexboxLayout(..)) {
558 use crate::layout::FlexboxAxisRelation;
570 match layout.axis_relation(Orientation::Horizontal) {
571 FlexboxAxisRelation::MainAxis => {
572 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
573 vis(&nr.clone().into(), P);
574 }
575 visit_layout_items_layoutinfo_cross_axis_dependencies(
576 layout.elems.iter().map(|fi| &fi.item),
577 Orientation::Vertical,
578 vis,
579 );
580 }
581 FlexboxAxisRelation::CrossAxis => {
582 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
583 vis(&nr.clone().into(), P);
584 }
585 visit_layout_items_layoutinfo_cross_axis_dependencies(
586 layout.elems.iter().map(|fi| &fi.item),
587 Orientation::Horizontal,
588 vis,
589 );
590 }
591 FlexboxAxisRelation::Unknown => {
592 if let Some(nr) = layout.geometry.rect.width_reference.as_ref() {
594 vis(&nr.clone().into(), P);
595 }
596 if let Some(nr) = layout.geometry.rect.height_reference.as_ref() {
597 vis(&nr.clone().into(), P);
598 }
599 visit_layout_items_layoutinfo_cross_axis_dependencies(
600 layout.elems.iter().map(|fi| &fi.item),
601 Orientation::Horizontal,
602 vis,
603 );
604 visit_layout_items_layoutinfo_cross_axis_dependencies(
605 layout.elems.iter().map(|fi| &fi.item),
606 Orientation::Vertical,
607 vis,
608 );
609 }
610 }
611 } else if let Expression::ComputeFlexboxLayoutInfo(_, orientation) = expr {
612 use crate::layout::FlexboxAxisRelation;
613 match layout.axis_relation(*orientation) {
614 FlexboxAxisRelation::MainAxis => {
615 visit_layout_items_dependencies(
617 layout.elems.iter().map(|fi| &fi.item),
618 *orientation,
619 vis,
620 );
621 }
622 FlexboxAxisRelation::CrossAxis => {
623 if *orientation == Orientation::Vertical
626 && let Some(nr) = layout.geometry.rect.width_reference.as_ref()
627 {
628 vis(&nr.clone().into(), P);
629 }
630 if *orientation == Orientation::Horizontal
631 && let Some(nr) = layout.geometry.rect.height_reference.as_ref()
632 {
633 vis(&nr.clone().into(), P);
634 }
635 visit_layout_items_dependencies(
636 layout.elems.iter().map(|fi| &fi.item),
637 Orientation::Horizontal,
638 vis,
639 );
640 visit_layout_items_dependencies(
641 layout.elems.iter().map(|fi| &fi.item),
642 Orientation::Vertical,
643 vis,
644 );
645 }
646 FlexboxAxisRelation::Unknown => {
647 visit_layout_items_dependencies(
651 layout.elems.iter().map(|fi| &fi.item),
652 Orientation::Horizontal,
653 vis,
654 );
655 visit_layout_items_dependencies(
656 layout.elems.iter().map(|fi| &fi.item),
657 Orientation::Vertical,
658 vis,
659 );
660 }
661 }
662 }
663 let mut g = layout.geometry.clone();
664 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
666 }
667 Expression::OrganizeGridLayout(layout) => {
668 let mut layout = layout.clone();
669 layout.visit_rowcol_named_references(&mut |nr: &mut NamedReference| {
670 vis(&nr.clone().into(), P)
671 });
672 }
673 Expression::SolveGridLayout { layout_organized_data_prop, layout, orientation }
674 | Expression::ComputeGridLayoutInfo { layout_organized_data_prop, layout, orientation } => {
675 if matches!(expr, Expression::SolveGridLayout { .. })
677 && let Some(nr) = layout.geometry.rect.size_reference(*orientation)
678 {
679 vis(&nr.clone().into(), P);
680 }
681 vis(&layout_organized_data_prop.clone().into(), P);
682 visit_layout_items_dependencies(
683 layout.elems.iter().map(|it| &it.item),
684 *orientation,
685 vis,
686 );
687 let mut g = layout.geometry.clone();
688 g.rect = Default::default(); g.visit_named_references(&mut |nr| vis(&nr.clone().into(), P))
690 }
691 Expression::FunctionCall {
692 function: Callable::Callback(nr) | Callable::Function(nr),
693 ..
694 } => vis(&nr.clone().into(), P),
695 Expression::FunctionCall { function: Callable::Builtin(b), arguments, .. } => match b {
696 BuiltinFunction::ImplicitLayoutInfo(orientation) => {
697 if let [Expression::ElementReference(item), ..] = arguments.as_slice() {
698 visit_implicit_layout_info_dependencies(
699 *orientation,
700 &item.upgrade().unwrap(),
701 vis,
702 );
703 }
704 }
705 BuiltinFunction::ItemAbsolutePosition => {
706 if let Some(Expression::ElementReference(item)) = arguments.first() {
707 let mut item = item.upgrade().unwrap();
708 while let Some(parent) = find_parent_element(&item) {
709 item = parent;
710 vis(
711 &NamedReference::new(&item, SmolStr::new_static("x")).into(),
712 ReadType::NativeRead,
713 );
714 vis(
715 &NamedReference::new(&item, SmolStr::new_static("y")).into(),
716 ReadType::NativeRead,
717 );
718 }
719 }
720 }
721 BuiltinFunction::ItemFontMetrics => {
722 if let Some(Expression::ElementReference(item)) = arguments.first() {
723 let item = item.upgrade().unwrap();
724 vis(
725 &NamedReference::new(&item, SmolStr::new_static("font-size")).into(),
726 ReadType::NativeRead,
727 );
728 vis(
729 &NamedReference::new(&item, SmolStr::new_static("font-weight")).into(),
730 ReadType::NativeRead,
731 );
732 vis(
733 &NamedReference::new(&item, SmolStr::new_static("font-family")).into(),
734 ReadType::NativeRead,
735 );
736 vis(
737 &NamedReference::new(&item, SmolStr::new_static("font-italic")).into(),
738 ReadType::NativeRead,
739 );
740 }
741 }
742 BuiltinFunction::GetWindowDefaultFontSize => {
743 let root =
744 elem.borrow().enclosing_component.upgrade().unwrap().root_element.clone();
745 if root.borrow().builtin_type().is_some_and(|bt| bt.name == "Window") {
746 vis(
747 &NamedReference::new(&root, SmolStr::new_static("default-font-size"))
748 .into(),
749 ReadType::PropertyRead,
750 );
751 }
752 }
753 _ => {}
754 },
755 _ => {}
756 }
757}
758
759fn visit_layout_items_dependencies<'a>(
760 items: impl Iterator<Item = &'a LayoutItem>,
761 orientation: Orientation,
762 vis: &mut impl FnMut(&PropertyPath, ReadType),
763) {
764 for it in items {
765 let mut element = it.element.clone();
766 if element
767 .borrow()
768 .repeated
769 .as_ref()
770 .map(|r| recurse_expression(&element, &r.model, vis))
771 .is_some()
772 {
773 element = it.element.borrow().base_type.as_component().root_element.clone();
774 }
775
776 if let Some(nr) = element.borrow().layout_info_prop(orientation) {
777 vis(&nr.clone().into(), ReadType::PropertyRead);
778 } else {
779 if let ElementType::Component(base) = &element.borrow().base_type
780 && let Some(nr) = base.root_element.borrow().layout_info_prop(orientation)
781 {
782 vis(
783 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr.clone() },
784 ReadType::PropertyRead,
785 );
786 }
787 visit_implicit_layout_info_dependencies(orientation, &element, vis);
788 }
789
790 for (nr, _) in it.constraints.for_each_restrictions(orientation) {
791 vis(&nr.clone().into(), ReadType::PropertyRead)
792 }
793 }
794}
795
796fn visit_layout_items_layoutinfo_cross_axis_dependencies<'a>(
810 items: impl Iterator<Item = &'a LayoutItem>,
811 cross_axis: Orientation,
812 vis: &mut impl FnMut(&PropertyPath, ReadType),
813) {
814 for it in items {
815 let element = it.element.clone();
816 if let Some(nr) = element.borrow().layout_info_prop(cross_axis) {
817 vis(&nr.clone().into(), ReadType::PropertyRead);
818 } else if let ElementType::Component(base) = &element.borrow().base_type
819 && let Some(nr) = base.root_element.borrow().layout_info_prop(cross_axis)
820 {
821 vis(
822 &PropertyPath { elements: vec![ByAddress(element.clone())], prop: nr.clone() },
823 ReadType::PropertyRead,
824 );
825 }
826 }
827}
828
829fn visit_implicit_layout_info_dependencies(
831 orientation: crate::layout::Orientation,
832 item: &ElementRc,
833 vis: &mut impl FnMut(&PropertyPath, ReadType),
834) {
835 let base_type = item.borrow().base_type.to_smolstr();
836 const N: ReadType = ReadType::NativeRead;
837 match base_type.as_str() {
838 "Image" => {
839 vis(&NamedReference::new(item, SmolStr::new_static("source")).into(), N);
840 vis(&NamedReference::new(item, SmolStr::new_static("source-clip-width")).into(), N);
841 if orientation == Orientation::Vertical {
842 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
843 vis(
844 &NamedReference::new(item, SmolStr::new_static("source-clip-height")).into(),
845 N,
846 );
847 }
848 }
849 "Text" | "TextInput" => {
850 vis(&NamedReference::new(item, SmolStr::new_static("text")).into(), N);
851 vis(&NamedReference::new(item, SmolStr::new_static("font-family")).into(), N);
852 vis(&NamedReference::new(item, SmolStr::new_static("font-size")).into(), N);
853 vis(&NamedReference::new(item, SmolStr::new_static("font-weight")).into(), N);
854 vis(&NamedReference::new(item, SmolStr::new_static("letter-spacing")).into(), N);
855 vis(&NamedReference::new(item, SmolStr::new_static("wrap")).into(), N);
856 let wrap_set = item.borrow().is_binding_set("wrap", false)
857 || item
858 .borrow()
859 .property_analysis
860 .borrow()
861 .get("wrap")
862 .is_some_and(|a| a.is_set || a.is_set_externally);
863 if wrap_set && orientation == Orientation::Vertical {
864 vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N);
865 }
866 if base_type.as_str() == "TextInput" {
867 vis(&NamedReference::new(item, SmolStr::new_static("single-line")).into(), N);
868 } else {
869 vis(&NamedReference::new(item, SmolStr::new_static("overflow")).into(), N);
870 }
871 }
872
873 _ => (),
874 }
875}
876
877fn visit_builtin_property(
878 builtin: &crate::langtype::BuiltinElement,
879 prop: &PropertyPath,
880 context: &mut AnalysisContext,
881 reverse_aliases: &ReverseAliases,
882 diag: &mut BuildDiagnostics,
883) {
884 let name = prop.prop.name();
885 if builtin.name == "Window" {
886 for (p, orientation) in
887 [("width", Orientation::Horizontal), ("height", Orientation::Vertical)]
888 {
889 if name == p {
890 let is_root = |e: &ElementRc| -> bool {
892 ElementRc::ptr_eq(
893 e,
894 &e.borrow().enclosing_component.upgrade().unwrap().root_element,
895 )
896 };
897 let mut root = prop.prop.element();
898 if !is_root(&root) {
899 return;
900 };
901 for e in prop.elements.iter().rev() {
902 if !is_root(&e.0) {
903 return;
904 }
905 root = e.0.clone();
906 }
907 if let Some(p) = root.borrow().layout_info_prop(orientation) {
908 let path = PropertyPath::from(p.clone());
909 let old_layout = context.window_layout_property.replace(path.clone());
910 process_property(&path, ReadType::NativeRead, context, reverse_aliases, diag);
911 context.window_layout_property = old_layout;
912 };
913 }
914 }
915 }
916}
917
918fn check_window_properties(doc: &Document, global_analysis: &mut GlobalAnalysis) {
920 doc.visit_all_used_components(|component| {
921 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
922 component,
923 &(),
924 &mut |elem, _| {
925 if elem.borrow().builtin_type().as_ref().is_some_and(|b| b.name == "Window") {
926 const DEFAULT_FONT_SIZE: &str = "default-font-size";
927 if elem.borrow().is_binding_set(DEFAULT_FONT_SIZE, false)
928 || elem
929 .borrow()
930 .property_analysis
931 .borrow()
932 .get(DEFAULT_FONT_SIZE)
933 .is_some_and(|a| a.is_set)
934 {
935 let value = elem.borrow().bindings.get(DEFAULT_FONT_SIZE).and_then(|e| {
936 match &e.borrow().expression {
937 Expression::NumberLiteral(v, crate::expression_tree::Unit::Px) => {
938 Some(*v as f32)
939 }
940 _ => None,
941 }
942 });
943 let is_const = value.is_some()
944 || NamedReference::new(elem, SmolStr::new_static(DEFAULT_FONT_SIZE))
945 .is_constant();
946 global_analysis.default_font_size = match global_analysis.default_font_size
947 {
948 DefaultFontSize::Unknown => match value {
949 Some(v) => DefaultFontSize::LogicalValue(v),
950 None if is_const => DefaultFontSize::Const,
951 None => DefaultFontSize::Variable,
952 },
953 DefaultFontSize::NotSet if is_const => DefaultFontSize::NotSet,
954 DefaultFontSize::LogicalValue(val) => match value {
955 Some(v) if v == val => DefaultFontSize::LogicalValue(val),
956 _ if is_const => DefaultFontSize::Const,
957 _ => DefaultFontSize::Variable,
958 },
959 DefaultFontSize::Const if is_const => DefaultFontSize::Const,
960 _ => DefaultFontSize::Variable,
961 }
962 } else {
963 global_analysis.default_font_size = match global_analysis.default_font_size
964 {
965 DefaultFontSize::Unknown => DefaultFontSize::NotSet,
966 DefaultFontSize::NotSet => DefaultFontSize::NotSet,
967 DefaultFontSize::LogicalValue(_) => DefaultFontSize::NotSet,
968 DefaultFontSize::Const => DefaultFontSize::NotSet,
969 DefaultFontSize::Variable => DefaultFontSize::Variable,
970 }
971 }
972 }
973 },
974 );
975 });
976}
977
978fn propagate_is_set_on_aliases(doc: &Document, reverse_aliases: &mut ReverseAliases) {
990 doc.visit_all_used_components(|component| {
991 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
992 component,
993 &(),
994 &mut |e, _| visit_element(e, reverse_aliases),
995 );
996 });
997
998 fn visit_element(e: &ElementRc, reverse_aliases: &mut ReverseAliases) {
999 for (name, binding) in &e.borrow().bindings {
1000 if !binding.borrow().two_way_bindings.is_empty() {
1001 check_alias(e, name, &binding.borrow());
1002
1003 let nr = NamedReference::new(e, name.clone());
1004 for a in &binding.borrow().two_way_bindings {
1005 if a.property != nr
1006 && !a
1007 .property
1008 .element()
1009 .borrow()
1010 .enclosing_component
1011 .upgrade()
1012 .unwrap()
1013 .is_global()
1014 {
1015 reverse_aliases.entry(a.property.clone()).or_default().push(nr.clone())
1016 }
1017 }
1018 }
1019 }
1020 for decl in e.borrow().property_declarations.values() {
1021 if let Some(alias) = &decl.is_alias {
1022 mark_alias(alias)
1023 }
1024 }
1025 }
1026
1027 fn check_alias(e: &ElementRc, name: &SmolStr, binding: &BindingExpression) {
1028 let is_binding_constant = binding.is_constant(None)
1030 && binding.two_way_bindings.iter().all(|n| n.property.is_constant());
1031 if is_binding_constant && !NamedReference::new(e, name.clone()).is_externally_modified() {
1032 for alias in &binding.two_way_bindings {
1033 crate::namedreference::mark_property_set_derived_in_base(
1034 alias.property.element(),
1035 alias.property.name(),
1036 );
1037 }
1038 return;
1039 }
1040
1041 propagate_alias(binding);
1042 }
1043
1044 fn propagate_alias(binding: &BindingExpression) {
1045 for alias in &binding.two_way_bindings {
1046 mark_alias(&alias.property);
1047 }
1048 }
1049
1050 fn mark_alias(alias: &NamedReference) {
1051 alias.mark_as_set();
1052 if !alias.is_externally_modified()
1053 && let Some(bind) = alias.element().borrow().bindings.get(alias.name())
1054 {
1055 propagate_alias(&bind.borrow())
1056 }
1057 }
1058}
1059
1060fn mark_used_base_properties(doc: &Document) {
1063 doc.visit_all_used_components(|component| {
1064 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
1065 component,
1066 &(),
1067 &mut |element, _| {
1068 if !matches!(element.borrow().base_type, ElementType::Component(_)) {
1069 return;
1070 }
1071 for (name, binding) in &element.borrow().bindings {
1072 if binding.borrow().has_binding() {
1073 crate::namedreference::mark_property_set_derived_in_base(
1074 element.clone(),
1075 name,
1076 );
1077 }
1078 }
1079 for name in element.borrow().change_callbacks.keys() {
1080 crate::namedreference::mark_property_read_derived_in_base(
1081 element.clone(),
1082 name,
1083 );
1084 }
1085 },
1086 );
1087 });
1088}