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