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