Skip to main content

i_slint_compiler/
object_tree.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5 This module contains the intermediate representation of the code in the form of an object tree
6*/
7
8// cSpell: ignore qualname
9
10use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
11use crate::expression_tree::{
12    self, BindingExpression, Callable, ConditionLocation, Expression, Unit,
13};
14use crate::langtype::{
15    BuiltinElement, Enumeration, EnumerationValue, Function, NativeClass, Struct, StructName, Type,
16};
17use crate::langtype::{ElementType, PropertyLookupMode, PropertyLookupResult};
18use crate::layout::{LayoutConstraints, Orientation};
19use crate::namedreference::NamedReference;
20use crate::parser::{SyntaxKind, SyntaxNode, syntax_nodes};
21use crate::typeloader::{ImportKind, ImportedTypes, LibraryInfo};
22use crate::typeregister::TypeRegister;
23use crate::{parser, reject_experimental_feature};
24use itertools::Either;
25use smol_str::{SmolStr, ToSmolStr, format_smolstr};
26use std::cell::{Cell, OnceCell, Ref, RefCell, RefMut};
27use std::collections::btree_map::Entry;
28use std::collections::{BTreeMap, HashMap, HashSet};
29use std::fmt::Display;
30use std::path::PathBuf;
31use std::rc::{Rc, Weak};
32use std::sync::Arc;
33
34pub(crate) mod forward_inherited_expression;
35mod interfaces;
36
37macro_rules! unwrap_or_continue {
38    ($e:expr ; $diag:expr) => {
39        match $e {
40            Some(x) => x,
41            None => {
42                debug_assert!($diag.has_errors()); // error should have been reported at parsing time
43                continue;
44            }
45        }
46    };
47}
48
49/// The full document (a complete file)
50#[derive(Default)]
51pub struct Document {
52    pub node: Option<syntax_nodes::Document>,
53    pub inner_components: Vec<Rc<Component>>,
54    pub inner_types: Vec<Type>,
55    pub local_registry: TypeRegister,
56    /// A list of paths to .ttf/.ttc files that are supposed to be registered on
57    /// startup for custom font use.
58    pub custom_fonts: Vec<(SmolStr, crate::parser::SyntaxToken)>,
59    pub exports: Exports,
60    pub imports: Vec<ImportedTypes>,
61    pub library_exports: HashMap<String, LibraryInfo>,
62
63    /// Resources to embed in the generated code.
64    ///
65    /// The [`crate::embedded_resources::EmbeddedResourcesIdx`] is the identifier used by code generators.
66    /// Each entry's `path` is the absolute path on disk, or `None` for in-memory data URI payloads.
67    pub embedded_file_resources: RefCell<
68        typed_index_collections::TiVec<
69            crate::embedded_resources::EmbeddedResourcesIdx,
70            crate::embedded_resources::EmbeddedResources,
71        >,
72    >,
73
74    #[cfg(feature = "bundle-translations")]
75    pub translation_builder: Option<crate::translations::TranslationsBuilder>,
76
77    /// The list of used extra types used recursively.
78    pub used_types: RefCell<UsedSubTypes>,
79
80    /// The popup_menu_impl
81    pub popup_menu_impl: Option<Rc<Component>>,
82}
83
84impl Document {
85    pub fn from_node(
86        node: syntax_nodes::Document,
87        imports: Vec<ImportedTypes>,
88        reexports: Exports,
89        diag: &mut BuildDiagnostics,
90        parent_registry: &Rc<RefCell<TypeRegister>>,
91        ignore_missing_font_files: bool,
92        symbol_counters: &Rc<crate::symbol_counters::SymbolCounters>,
93    ) -> Self {
94        debug_assert_eq!(node.kind(), SyntaxKind::Document);
95
96        let mut local_registry = TypeRegister::new(parent_registry);
97        let mut inner_components = Vec::new();
98        let mut inner_types = Vec::new();
99
100        // Named imports are part of the subset, the other two forms aren't.
101        // The path rules are enforced in the type loader, which is where a
102        // path is resolved.
103        #[cfg(feature = "slint-sc")]
104        for import in &imports {
105            match import.import_kind {
106                ImportKind::ImportList(_) => {}
107                ImportKind::FileImport => {
108                    diag.slint_sc_error("File imports are", &import.import_uri_token)
109                }
110                ImportKind::ModuleReexport(_) => {
111                    diag.slint_sc_error("Re-exports are", &import.import_uri_token)
112                }
113            }
114        }
115
116        let mut process_component =
117            |n: syntax_nodes::Component,
118             diag: &mut BuildDiagnostics,
119             local_registry: &mut TypeRegister| {
120                let compo = Component::from_node(n, diag, local_registry);
121                if !local_registry.add(compo.clone()) {
122                    diag.push_warning(format!("Component '{}' is replacing a previously defined component with the same name", compo.id), &compo.node.clone().unwrap().DeclaredIdentifier());
123                }
124                inner_components.push(compo);
125            };
126        let process_struct = |n: syntax_nodes::StructDeclaration,
127                              diag: &mut BuildDiagnostics,
128                              local_registry: &mut TypeRegister,
129                              inner_types: &mut Vec<Type>| {
130            let ty = type_struct_from_node(
131                n.ObjectType(),
132                diag,
133                local_registry,
134                parser::identifier_text(&n.DeclaredIdentifier()),
135                Some(symbol_counters),
136            );
137            assert!(matches!(ty, Type::Struct(_)));
138            if !local_registry.insert_type(ty.clone()) {
139                diag.push_warning(
140                    format!(
141                        "Struct '{ty}' is replacing a previously defined type with the same name"
142                    ),
143                    &n.DeclaredIdentifier(),
144                );
145            }
146            inner_types.push(ty);
147        };
148        let process_enum = |n: syntax_nodes::EnumDeclaration,
149                            diag: &mut BuildDiagnostics,
150                            local_registry: &mut TypeRegister,
151                            inner_types: &mut Vec<Type>| {
152            let Some(name) = parser::identifier_text(&n.DeclaredIdentifier()) else {
153                assert!(diag.has_errors());
154                return;
155            };
156            let mut existing_names = HashSet::new();
157            let values = n
158                .EnumValue()
159                .filter_map(|v| {
160                    let value = parser::identifier_text(&v)?;
161                    if value == name {
162                        diag.push_error(
163                            format!("Enum '{value}' can't have a value with the same name"),
164                            &v,
165                        );
166                        None
167                    } else if !existing_names.insert(crate::generator::to_pascal_case(&value)) {
168                        diag.push_error(format!("Duplicated enum value '{value}'"), &v);
169                        None
170                    } else {
171                        Some(value)
172                    }
173                })
174                .collect();
175            let en = Enumeration {
176                name: name.clone(),
177                values,
178                default_value: 0,
179                node: Some(n.to_source_location()),
180                rust_attributes: n
181                    .AtRustAttr()
182                    .map(|a| SmolStr::from(a.text().to_string()))
183                    .collect(),
184            };
185            if en.values.is_empty() {
186                diag.push_error("Enums must have at least one value".into(), &n);
187            }
188
189            let ty = Type::Enumeration(Arc::new(en));
190            if !local_registry.insert_type_with_name(ty.clone(), name.clone()) {
191                diag.push_warning(
192                    format!(
193                        "Enum '{name}' is replacing a previously defined type with the same name"
194                    ),
195                    &n.DeclaredIdentifier(),
196                );
197            }
198            inner_types.push(ty);
199        };
200
201        for n in node.children() {
202            match n.kind() {
203                SyntaxKind::Component => {
204                    process_component(n.into(), diag, &mut local_registry);
205                }
206                SyntaxKind::StructDeclaration => {
207                    process_struct(n.into(), diag, &mut local_registry, &mut inner_types)
208                }
209                SyntaxKind::EnumDeclaration => {
210                    process_enum(n.into(), diag, &mut local_registry, &mut inner_types)
211                }
212                SyntaxKind::ExportsList => {
213                    for n in n.children() {
214                        match n.kind() {
215                            SyntaxKind::Component => {
216                                process_component(n.into(), diag, &mut local_registry)
217                            }
218                            SyntaxKind::StructDeclaration => process_struct(
219                                n.into(),
220                                diag,
221                                &mut local_registry,
222                                &mut inner_types,
223                            ),
224                            SyntaxKind::EnumDeclaration => {
225                                process_enum(n.into(), diag, &mut local_registry, &mut inner_types)
226                            }
227                            _ => {}
228                        }
229                    }
230                }
231                _ => {}
232            };
233        }
234        let mut exports = Exports::from_node(&node, &inner_components, &local_registry, diag);
235        exports.add_reexports(reexports, diag);
236
237        let custom_fonts = imports
238            .iter()
239            .filter(|import| matches!(import.import_kind, ImportKind::FileImport))
240            .filter_map(|import| {
241                if crate::pathutils::is_font_file(&import.file) {
242                    let token_path = import.import_uri_token.source_file.path();
243                    let import_file_path = PathBuf::from(import.file.clone());
244                    let import_file_path = crate::pathutils::join(token_path, &import_file_path)
245                        .unwrap_or(import_file_path);
246
247                    // Assume remote urls are valid, we need to load them at run-time (which we currently don't). For
248                    // local paths we should try to verify the existence and let the developer know ASAP.
249                    // When the resource URL mapper is set (e.g. remote viewer), fonts are
250                    // delivered out-of-band; skip the local existence check.
251                    if ignore_missing_font_files
252                        || crate::pathutils::is_url(&import_file_path)
253                        || crate::fileaccess::load_file(std::path::Path::new(&import_file_path))
254                            .is_some()
255                    {
256                        Some((import_file_path.to_string_lossy().into(), import.import_uri_token.clone()))
257                    } else {
258                        diag.push_error(
259                            format!("File \"{}\" not found", import.file),
260                            &import.import_uri_token,
261                        );
262                        None
263                    }
264                } else if import.file.ends_with(".slint") {
265                    diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
266                    None
267                } else {
268                    diag.push_error(
269                        format!("Unsupported foreign import \"{}\"", import.file),
270                        &import.import_uri_token,
271                    );
272                    None
273                }
274            })
275            .collect();
276
277        for local_compo in &inner_components {
278            if exports
279                .components_or_types
280                .iter()
281                .filter_map(|(_, exported_compo_or_type)| exported_compo_or_type.as_ref().left())
282                .any(|exported_compo| Rc::ptr_eq(exported_compo, local_compo))
283            {
284                continue;
285            }
286            // Don't warn about these for now - detecting their use can only be done after the resolve_expressions
287            // pass.
288            if local_compo.is_global() {
289                continue;
290            }
291            if !local_compo.used.get() {
292                diag.push_warning(
293                    "Component is neither used nor exported".into(),
294                    &local_compo.node.as_ref().map(|n| n.to_source_location()),
295                )
296            }
297        }
298
299        Document {
300            node: Some(node),
301            inner_components,
302            inner_types,
303            local_registry,
304            custom_fonts,
305            imports,
306            exports,
307            library_exports: Default::default(),
308            embedded_file_resources: Default::default(),
309            #[cfg(feature = "bundle-translations")]
310            translation_builder: None,
311            used_types: Default::default(),
312            popup_menu_impl: None,
313        }
314    }
315
316    pub fn exported_roots(&self) -> impl DoubleEndedIterator<Item = Rc<Component>> + '_ {
317        self.exports
318            .iter()
319            .filter_map(|e| e.1.as_ref().left())
320            .filter(|c| !c.is_global() && !c.is_interface())
321            .cloned()
322    }
323
324    /// This is the component that is going to be instantiated by the interpreter
325    pub fn last_exported_component(&self) -> Option<Rc<Component>> {
326        self.exports
327            .iter()
328            .filter_map(|e| Some((&e.0.name_ident, e.1.as_ref().left()?)))
329            .filter(|(_, c)| !c.is_global())
330            .max_by_key(|(n, _)| n.text_range().end())
331            .map(|(_, c)| c.clone())
332    }
333
334    /// visit all root and used component (including globals)
335    pub fn visit_all_used_components(&self, mut v: impl FnMut(&Rc<Component>)) {
336        let used_types = self.used_types.borrow();
337        for c in &used_types.sub_components {
338            v(c);
339        }
340        for c in self.exported_roots() {
341            v(&c);
342        }
343        for c in &used_types.globals {
344            v(c);
345        }
346        if let Some(c) = &self.popup_menu_impl {
347            v(c);
348        }
349    }
350}
351
352#[derive(Debug, Clone)]
353pub struct PopupWindow {
354    pub component: Rc<Component>,
355    pub x: NamedReference,
356    pub y: NamedReference,
357    pub close_policy: EnumerationValue,
358    pub parent_element: ElementRc,
359    pub is_tooltip: bool,
360    /// A reference to a synthesized property on the *parent* component that the runtime keeps in sync
361    /// with the popup's visibility (`true` while shown, `false` once closed). This is `Some` only when
362    /// the parent reads the PopupWindow's `is-open` property; see the `lower_popups` pass.
363    pub is_open: Option<NamedReference>,
364}
365
366#[derive(Debug, Clone)]
367pub struct Timer {
368    pub interval: NamedReference,
369    pub triggered: NamedReference,
370    pub running: NamedReference,
371    pub element: ElementWeak,
372}
373
374/// Key used for the default slot's insertion point and slot-target maps.
375/// Not a valid Slint identifier, so it can never collide with a user-declared slot name.
376pub const DEFAULT_SLOT_NAME: &str = "@children";
377
378pub fn slot_error_subject(name: &str) -> String {
379    if name == DEFAULT_SLOT_NAME {
380        "The @children placeholder".into()
381    } else {
382        format!("The slot '{name}'")
383    }
384}
385
386#[derive(Clone, Debug)]
387pub enum ChildInsertionPointNode {
388    DefaultChildrenPlaceHolder(SyntaxNode),
389    ChildrenPlaceHolder(syntax_nodes::ChildrenPlaceholder),
390    SlotPlaceholder(syntax_nodes::SubElement),
391    SlotForwarding(syntax_nodes::Expression),
392}
393
394impl ChildInsertionPointNode {
395    pub fn syntax_node(&self) -> &SyntaxNode {
396        match self {
397            Self::DefaultChildrenPlaceHolder(node) => node,
398            Self::ChildrenPlaceHolder(node) => node,
399            Self::SlotPlaceholder(node) => node,
400            Self::SlotForwarding(node) => node,
401        }
402    }
403}
404
405impl Spanned for ChildInsertionPointNode {
406    fn span(&self) -> crate::diagnostics::Span {
407        self.syntax_node().span()
408    }
409
410    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
411        self.syntax_node().source_file()
412    }
413}
414
415#[derive(Clone, Debug)]
416pub struct ChildrenInsertionPoint {
417    pub parent: ElementRc,
418    pub insertion_index: usize,
419    pub node: ChildInsertionPointNode,
420}
421
422#[derive(Clone, Debug)]
423pub struct DeclaredSlot {
424    pub name: SmolStr,
425    pub name_node: syntax_nodes::DeclaredIdentifier,
426    has_rejected_placeholder: bool,
427}
428
429#[derive(Clone, Debug)]
430pub struct SlotForwarding {
431    pub target: SmolStr,
432    pub source: SmolStr,
433    pub expression_node: syntax_nodes::Expression,
434}
435
436/// Used sub types for a root component
437#[derive(Debug, Default)]
438pub struct UsedSubTypes {
439    /// All the globals used by the component and its children.
440    pub globals: Vec<Rc<Component>>,
441    /// All the structs and enums used by the component and its children.
442    pub structs_and_enums: Vec<Type>,
443    /// All the sub components use by this components and its children,
444    /// and the amount of time it is used
445    pub sub_components: Vec<Rc<Component>>,
446    /// All types, structs, enums, that originates from an
447    /// external library
448    pub library_types_imports: Vec<(SmolStr, LibraryInfo)>,
449    /// All global components that originates from an
450    /// external library
451    pub library_global_imports: Vec<(SmolStr, LibraryInfo)>,
452    /// `(old_name, new_name)` for types renamed to their export name. The generators emit a
453    /// deprecated alias under `old_name` so code that used it keeps compiling.
454    pub deprecated_type_aliases: Vec<(SmolStr, SmolStr)>,
455    /// The fresh names given to types that collided with another declaration. These names were
456    /// never part of the public API, so the generators must not re-export them (deprecated or not).
457    pub collision_renamed_names: std::collections::BTreeSet<SmolStr>,
458}
459
460#[derive(Debug, Default, Clone)]
461pub struct InitCode {
462    // Code from init callbacks collected from elements
463    pub constructor_code: Vec<Expression>,
464    /// Code to set the initial focus via forward-focus on the Window
465    pub focus_setting_code: Vec<Expression>,
466    /// Code to register embedded fonts.
467    pub font_registration_code: Vec<Expression>,
468
469    /// Code inserted from inlined components, ordered by offset of the place where it was inlined from. This way
470    /// we can preserve the order across multiple inlining passes.
471    pub inlined_init_code: BTreeMap<usize, Expression>,
472}
473
474impl InitCode {
475    pub fn iter(&self) -> impl Iterator<Item = &Expression> {
476        self.font_registration_code.iter().chain(self.iter_without_font_registration())
477    }
478    /// The init code without the font registration, which has to run before the property init.
479    pub fn iter_without_font_registration(&self) -> impl Iterator<Item = &Expression> {
480        self.focus_setting_code
481            .iter()
482            .chain(self.constructor_code.iter())
483            .chain(self.inlined_init_code.values())
484    }
485    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Expression> {
486        self.font_registration_code
487            .iter_mut()
488            .chain(self.focus_setting_code.iter_mut())
489            .chain(self.constructor_code.iter_mut())
490            .chain(self.inlined_init_code.values_mut())
491    }
492}
493
494/// A component is a type in the language which can be instantiated,
495/// Or is materialized for repeated expression.
496#[derive(Default, Debug)]
497pub struct Component {
498    pub node: Option<syntax_nodes::Component>,
499    pub id: SmolStr,
500    pub root_element: ElementRc,
501
502    /// The parent element within the parent component if this component represents a repeated element
503    pub parent_element: RefCell<ElementWeak>,
504
505    /// List of elements that are not attached to the root anymore because they have been
506    /// optimized away, but their properties may still be in use
507    pub optimized_elements: RefCell<Vec<ElementRc>>,
508
509    /// The layout constraints of the root item
510    pub root_constraints: RefCell<LayoutConstraints>,
511
512    /// When creating this component and inserting "children", append them to the children of
513    /// the element pointer to by this field.
514    pub child_insertion_points: RefCell<BTreeMap<String, ChildrenInsertionPoint>>,
515
516    /// Slots declared in this component, in source order.
517    pub declared_slots: RefCell<Vec<DeclaredSlot>>,
518
519    pub init_code: RefCell<InitCode>,
520
521    pub popup_windows: RefCell<Vec<PopupWindow>>,
522    pub timers: RefCell<Vec<Timer>>,
523    pub menu_item_tree: RefCell<Vec<Rc<Component>>>,
524
525    /// This component actually inherits PopupWindow (although that has been changed to a Window by the lower_popups pass)
526    pub inherits_popup_window: Cell<bool>,
527
528    /// The names under which this component should be accessible
529    /// if it is a global singleton and exported.
530    pub exported_global_names: RefCell<Vec<ExportedName>>,
531
532    /// True if this component is used as a sub-component by at least one other component.
533    pub used: Cell<bool>,
534
535    /// The list of properties (name and type) declared as private in the component.
536    /// This is used to issue better error in the generated code if the property is used.
537    pub private_properties: RefCell<Vec<(SmolStr, Type)>>,
538
539    /// True if this component is imported from an external library.
540    pub from_library: Cell<bool>,
541}
542
543impl Component {
544    pub fn from_node(
545        node: syntax_nodes::Component,
546        diag: &mut BuildDiagnostics,
547        tr: &TypeRegister,
548    ) -> Rc<Self> {
549        let mut child_insertion_points = BTreeMap::new();
550        let mut declared_slots = Vec::new();
551        let is_legacy_syntax = node.child_token(SyntaxKind::ColonEqual).is_some();
552        let c = Component {
553            node: Some(node.clone()),
554            id: parser::identifier_text(&node.DeclaredIdentifier()).unwrap_or_default(),
555            root_element: Element::from_node(
556                node.Element(),
557                "root".into(),
558                match node.child_text(SyntaxKind::Identifier) {
559                    Some(t) if t == "global" => {
560                        #[cfg(feature = "slint-sc")]
561                        diag.slint_sc_error("Globals are", &node.DeclaredIdentifier());
562                        ElementType::Global
563                    }
564                    Some(t) if t == "interface" => {
565                        if reject_experimental_feature(diag, tr, "interface", &node) {
566                            ElementType::Error
567                        } else {
568                            ElementType::Interface
569                        }
570                    }
571                    _ => ElementType::Error,
572                },
573                &mut child_insertion_points,
574                &mut declared_slots,
575                is_legacy_syntax,
576                diag,
577                tr,
578            ),
579            child_insertion_points: RefCell::new(child_insertion_points),
580            declared_slots: RefCell::new(declared_slots),
581            ..Default::default()
582        };
583        c.check_slot_validity(diag);
584        let c = Rc::new(c);
585        // x and y on a Window are meaningless
586        if c.root_element
587            .borrow()
588            .builtin_type()
589            .is_some_and(|b| matches!(b.name.as_str(), "Window" | "Dialog"))
590        {
591            for prop in ["x", "y"] {
592                if let Some(b) = c.root_element.borrow().binding_cell_including_synthetic(prop) {
593                    #[cfg(feature = "slint-sc")]
594                    if diag.slint_sc {
595                        diag.slint_sc_error(&format!("The property '{prop}' is"), &*b.borrow());
596                        continue;
597                    }
598                    diag.push_warning(
599                        format!(
600                            "Setting '{prop}' on a Window is deprecated, it doesn't affect the position of the window"
601                        ),
602                        &*b.borrow(),
603                    );
604                }
605            }
606            // The application gives the window its size, so the size is an
607            // output of the component rather than something the file sets.
608            #[cfg(feature = "slint-sc")]
609            for prop in ["width", "height"] {
610                if let Some(b) = c.root_element.borrow().binding_cell_including_synthetic(prop) {
611                    diag.slint_sc_error(
612                        &format!("Binding the '{prop}' of the root element is"),
613                        &*b.borrow(),
614                    );
615                }
616            }
617        }
618        let weak = Rc::downgrade(&c);
619        recurse_elem(&c.root_element, &(), &mut |e, _| {
620            e.borrow_mut().enclosing_component = weak.clone();
621            if let Some(qualified_id) =
622                e.borrow_mut().debug.first_mut().and_then(|x| x.qualified_id.as_mut())
623            {
624                *qualified_id = format_smolstr!("{}::{}", c.id, qualified_id);
625            }
626        });
627        c
628    }
629
630    fn check_slot_validity(&self, diagnostics: &mut BuildDiagnostics) {
631        if !diagnostics.enable_experimental {
632            return;
633        }
634        if self.is_global() || self.is_interface() {
635            return;
636        }
637        let mut declared_slot_nodes = BTreeMap::<SmolStr, syntax_nodes::DeclaredIdentifier>::new();
638        for slot in self.declared_slots.borrow().iter() {
639            if slot.name == "children" {
640                diagnostics.push_error(
641                    format!(
642                        "The name '{}' is reserved for the default slot. Use @children instead",
643                        slot.name
644                    ),
645                    &slot.name_node,
646                );
647                continue;
648            }
649            if declared_slot_nodes.insert(slot.name.clone(), slot.name_node.clone()).is_some() {
650                diagnostics.push_error(
651                    format!("Duplicate slot declaration '{}'", slot.name),
652                    &slot.name_node,
653                );
654            }
655        }
656        for (name, cip) in self.child_insertion_points.borrow().iter() {
657            if name == DEFAULT_SLOT_NAME {
658                continue;
659            }
660            if !declared_slot_nodes.contains_key(name.as_str()) {
661                diagnostics
662                    .push_error(format!("The slot '{name}' is used but not declared"), &cip.node);
663            }
664        }
665        for (name, node) in declared_slot_nodes.iter() {
666            let has_rejected_placeholder = self
667                .declared_slots
668                .borrow()
669                .iter()
670                .any(|slot| slot.has_rejected_placeholder && &slot.name == name);
671            if !self.child_insertion_points.borrow().contains_key(name.as_str())
672                && !has_rejected_placeholder
673            {
674                diagnostics.push_error(format!("The slot '{name}' is declared but not used"), node);
675            }
676        }
677    }
678
679    /// This component is a global component introduced with the "global" keyword
680    pub fn is_global(&self) -> bool {
681        match &self.root_element.borrow().base_type {
682            ElementType::Global => true,
683            ElementType::Builtin(c) => c.is_global,
684            _ => false,
685        }
686    }
687
688    /// This is an interface introduced with the "interface" keyword
689    pub fn is_interface(&self) -> bool {
690        matches!(&self.root_element.borrow().base_type, ElementType::Interface)
691    }
692
693    /// True if this component's root resolves to the `SystemTrayIcon` native
694    /// class. Uses `native_class()` rather than `builtin_type()` so the check
695    /// still matches once the root has been resolved to `Native(SystemTrayIcon)`
696    /// after `resolve_native_classes`.
697    pub fn inherits_system_tray_icon(&self) -> bool {
698        self.root_element
699            .borrow()
700            .native_class()
701            .is_some_and(|n| n.class_name.as_str() == "SystemTrayIcon")
702    }
703
704    /// Returns the names of aliases to global singletons, exactly as
705    /// specified in the .slint markup (not normalized).
706    pub fn global_aliases(&self) -> Vec<SmolStr> {
707        self.exported_global_names
708            .borrow()
709            .iter()
710            .filter(|name| name.as_str() != self.root_element.borrow().id)
711            .map(|name| name.original_name())
712            .collect()
713    }
714
715    // Number of repeaters in this component, including sub-components
716    pub fn repeater_count(&self) -> u32 {
717        let mut count = 0;
718        recurse_elem(&self.root_element, &(), &mut |element, _| {
719            let element = element.borrow();
720            if let Some(sub_component) = element.sub_component() {
721                count += sub_component.repeater_count();
722            } else if element.repeated.is_some() {
723                count += 1;
724            }
725        });
726        count
727    }
728
729    /// Convenience accessor to get the parent element if this component is a repeated component, or None otherwise.
730    ///
731    /// # Panics
732    ///
733    /// Panics if the Self::parent_element member is currently mutably borrowed
734    pub fn parent_element(&self) -> Option<ElementRc> {
735        self.parent_element.borrow().upgrade()
736    }
737}
738
739#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
740pub enum PropertyVisibility {
741    #[default]
742    Private,
743    Input,
744    Output,
745    InOut,
746    /// for builtin properties that must be known at compile time and cannot be changed at runtime
747    Constexpr,
748    /// For builtin properties that are meant to just be bindings but cannot be read or written
749    /// (eg, Path's `commands`)
750    Fake,
751    /// For functions, not properties
752    Public,
753    Protected,
754}
755
756impl Display for PropertyVisibility {
757    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
758        match self {
759            PropertyVisibility::Private => f.write_str("private"),
760            PropertyVisibility::Input => f.write_str("in"),
761            PropertyVisibility::Output => f.write_str("out"),
762            PropertyVisibility::InOut => f.write_str("in-out"),
763            PropertyVisibility::Constexpr => f.write_str("constexpr"),
764            PropertyVisibility::Public => f.write_str("public"),
765            PropertyVisibility::Protected => f.write_str("protected"),
766            PropertyVisibility::Fake => f.write_str("fake"),
767        }
768    }
769}
770
771#[derive(Clone, Debug, Default)]
772pub struct PropertyDeclaration {
773    pub property_type: Type,
774    pub node: Option<SyntaxNode>,
775    /// Tells if getter and setter will be added to expose in the native language API
776    pub expose_in_public_api: bool,
777    /// Public API property exposed as an alias: it shouldn't be generated but instead forward to the alias.
778    pub is_alias: Option<NamedReference>,
779    pub visibility: PropertyVisibility,
780    /// For function or callback: whether it is declared as `pure` (None for private function for which this has to be deduced)
781    pub pure: Option<bool>,
782    /// For a declaration that shadows an inherited member: the name as written in the source.
783    /// The declaration itself is stored under a mangled name, see [`Element::shadowing_members`].
784    pub shadowed_name: Option<SmolStr>,
785    /// Declared `@shadowable`, so an inheriting component may shadow it.
786    pub shadowable: bool,
787    /// The name the declaration had on the element it was moved from, when the
788    /// move_declarations pass hoisted it onto the root element from another
789    /// element of the component, under a name of its own making. What the
790    /// component itself declares, in the source or through the component it
791    /// inherits from, keeps this `None`.
792    pub moved_from: Option<SmolStr>,
793    /// Some if the property was declared with `@deprecated`. The string is the hint shown after
794    /// "The property 'xxx' has been deprecated." in the warning: either derived from the two-way
795    /// binding target, or the custom message given as argument to `@deprecated("...")`.
796    pub deprecated: Option<SmolStr>,
797}
798
799impl PropertyDeclaration {
800    // For diagnostics: return a node pointing to the type
801    pub fn type_node(&self) -> Option<SyntaxNode> {
802        let node = self.node.as_ref()?;
803        if let Some(x) = syntax_nodes::PropertyDeclaration::new(node.clone()) {
804            Some(x.Type().map_or_else(|| x.into(), |x| x.into()))
805        } else {
806            node.clone().into()
807        }
808    }
809
810    /// The name the member is declared under, un-mangled, given its internal key.
811    pub fn declared_name<'a>(&'a self, internal_name: &'a SmolStr) -> &'a SmolStr {
812        self.shadowed_name.as_ref().unwrap_or(internal_name)
813    }
814
815    /// A declaration that shadows an inherited member but is private: it is invisible outside its
816    /// component, so from there the inherited member stays reachable instead.
817    pub fn is_private_shadow(&self) -> bool {
818        self.shadowed_name.is_some() && self.visibility == PropertyVisibility::Private
819    }
820
821    /// True when declared `@deprecated` without a custom message, so the hint in
822    /// [`Self::deprecated`] is derived from the two-way binding target.
823    pub fn has_derived_deprecation(&self) -> bool {
824        self.deprecated.is_some()
825            && self
826                .node
827                .as_ref()
828                .and_then(|n| syntax_nodes::PropertyDeclaration::new(n.clone()))
829                .and_then(|p| p.PropertyDeprecation())
830                .is_some_and(|d| d.child_token(SyntaxKind::StringLiteral).is_none())
831    }
832}
833
834/// Whether the declaration is marked `@shadowable` (an experimental feature).
835fn shadowable_attribute(
836    node: Option<syntax_nodes::ShadowableAttribute>,
837    tr: &TypeRegister,
838    diag: &mut BuildDiagnostics,
839) -> bool {
840    node.is_some_and(|node| !reject_experimental_feature(diag, tr, "@shadowable", &node))
841}
842
843/// How a `@deprecated` member without an explicit message derives its replacement hint.
844enum DeprecationHint {
845    /// A property or callback: derive it from the two-way binding target, if any.
846    TwoWayBinding(Option<syntax_nodes::QualifiedName>),
847    /// A function has no two-way binding, so an explicit message is required.
848    MessageRequired,
849}
850
851/// The hint from a `@deprecated` attribute on a member: the explicit message, or one derived from
852/// the two-way binding target when none is given. `None` when the member isn't deprecated.
853fn member_deprecation(
854    deprecation: Option<syntax_nodes::PropertyDeprecation>,
855    hint: DeprecationHint,
856    tr: &TypeRegister,
857    diag: &mut BuildDiagnostics,
858) -> Option<SmolStr> {
859    let deprecation = deprecation?;
860    if reject_experimental_feature(diag, tr, "@deprecated", &deprecation) {
861        return None;
862    }
863    if let Some(message) = deprecation.child_token(SyntaxKind::StringLiteral) {
864        return crate::literals::unescape_string(message.text());
865    }
866    let message = match hint {
867        DeprecationHint::TwoWayBinding(target) => {
868            // Derive the hint from the two-way binding target: keep the full path (e.g.
869            // `a-struct.field`), dropping a leading `self`/`root`. The resolving pass checks the
870            // target is actually reachable.
871            if let Some(qn) = target {
872                let mut segments = qn
873                    .children_with_tokens()
874                    .filter(|t| t.kind() == SyntaxKind::Identifier)
875                    .map(|t| parser::normalize_identifier(t.as_token().unwrap().text()))
876                    .peekable();
877                if segments.peek().is_some_and(|s| matches!(s.as_str(), "self" | "root")) {
878                    segments.next();
879                }
880                let path = segments.collect::<Vec<_>>().join(".");
881                if !path.is_empty() {
882                    return Some(format_smolstr!("Please use '{path}' instead"));
883                }
884            }
885            "@deprecated without a message requires a two-way binding to derive the replacement from"
886        }
887        DeprecationHint::MessageRequired => "@deprecated on a function requires a message",
888    };
889    diag.push_error(message.into(), &deprecation);
890    None
891}
892
893/// Shift the locality flags of a result that came from the element's base rather than itself.
894fn from_base(mut r: PropertyLookupResult<'_>) -> PropertyLookupResult<'_> {
895    r.is_in_direct_base = r.is_local_to_component;
896    r.is_local_to_component = false;
897    r
898}
899
900/// The error for a declaration that collides with a member it may not shadow.
901/// `kind` is `None` for a function.
902fn cannot_override_message(
903    kind: Option<&str>,
904    name: &SmolStr,
905    declared_in: &Option<Rc<Component>>,
906) -> String {
907    let kind = kind.map_or_else(String::new, |kind| format!("{kind} "));
908    match declared_in {
909        Some(base) => format!("Cannot override {kind}'{name}' declared in '{}'", base.id),
910        None => format!("Cannot override {kind}'{name}'"),
911    }
912}
913
914/// How a declaration relates to a member of the same name already reachable from the element.
915/// See [`Element::member_declaration`].
916enum MemberDeclaration {
917    /// No member of that name exists yet
918    New,
919    /// Shadows an inherited member: the declaration goes under `internal_name`, so the source
920    /// name keeps resolving to the shadowed member for the code written against it.
921    Shadow {
922        internal_name: SmolStr,
923        /// Shadowing a member that isn't visible here is silent
924        warning: Option<String>,
925    },
926    /// A member of that name already exists and may not be shadowed
927    Conflict {
928        existing_type: Type,
929        /// The base component declaring it, unless it is declared on this element itself
930        declared_in: Option<Rc<Component>>,
931    },
932}
933
934impl MemberDeclaration {
935    /// Record the member on `elem`, warning if it shadows a member visible here.
936    /// Returns the name to declare it under, which differs from `source_name` when it shadows.
937    fn register(
938        self,
939        elem: &mut Element,
940        source_name: &SmolStr,
941        node: &dyn Spanned,
942        diag: &mut BuildDiagnostics,
943    ) -> SmolStr {
944        let Self::Shadow { internal_name, warning } = self else {
945            return source_name.clone();
946        };
947        elem.shadowing_members.insert(source_name.clone(), internal_name.clone());
948        if let Some(warning) = warning {
949            diag.push_warning(warning, node);
950        }
951        internal_name
952    }
953}
954
955impl From<Type> for PropertyDeclaration {
956    fn from(ty: Type) -> Self {
957        PropertyDeclaration { property_type: ty, ..Self::default() }
958    }
959}
960
961#[derive(Debug, Clone, Copy, PartialEq)]
962pub enum TransitionDirection {
963    In,
964    Out,
965    InOut,
966}
967
968#[derive(Debug, Clone)]
969pub struct TransitionPropertyAnimation {
970    /// The state id as computed in lower_state
971    pub state_id: i32,
972    /// The direction of the transition
973    pub direction: TransitionDirection,
974    /// The content of the `animation` object
975    pub animation: ElementRc,
976}
977
978impl TransitionPropertyAnimation {
979    /// Return an expression which returns a boolean which is true if the transition is active.
980    /// The state argument is an expression referencing the state property of type StateInfo
981    pub fn condition(&self, state: Expression) -> Expression {
982        match self.direction {
983            TransitionDirection::In => Expression::BinaryExpression {
984                lhs: Box::new(Expression::StructFieldAccess {
985                    base: Box::new(state),
986                    name: "current-state".into(),
987                }),
988                rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
989                op: '=',
990                source_location: None,
991            },
992            TransitionDirection::Out => Expression::BinaryExpression {
993                lhs: Box::new(Expression::StructFieldAccess {
994                    base: Box::new(state),
995                    name: "previous-state".into(),
996                }),
997                rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
998                op: '=',
999                source_location: None,
1000            },
1001            TransitionDirection::InOut => Expression::BinaryExpression {
1002                lhs: Box::new(Expression::BinaryExpression {
1003                    source_location: None,
1004                    lhs: Box::new(Expression::StructFieldAccess {
1005                        base: Box::new(state.clone()),
1006                        name: "current-state".into(),
1007                    }),
1008                    rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
1009                    op: '=',
1010                }),
1011                rhs: Box::new(Expression::BinaryExpression {
1012                    source_location: None,
1013                    lhs: Box::new(Expression::StructFieldAccess {
1014                        base: Box::new(state),
1015                        name: "previous-state".into(),
1016                    }),
1017                    rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
1018                    op: '=',
1019                }),
1020                op: '|',
1021                source_location: None,
1022            },
1023        }
1024    }
1025}
1026
1027#[derive(Debug)]
1028pub enum PropertyAnimation {
1029    Static(ElementRc),
1030    Transition { state_ref: Expression, animations: Vec<TransitionPropertyAnimation> },
1031}
1032
1033impl Clone for PropertyAnimation {
1034    fn clone(&self) -> Self {
1035        fn deep_clone(e: &ElementRc) -> ElementRc {
1036            let e = e.borrow();
1037            debug_assert!(e.children.is_empty());
1038            debug_assert!(e.property_declarations.is_empty());
1039            debug_assert!(e.states.is_empty() && e.transitions.is_empty());
1040            Rc::new(RefCell::new(Element {
1041                id: e.id.clone(),
1042                base_type: e.base_type.clone(),
1043                bindings: e.bindings.clone(),
1044                property_analysis: e.property_analysis.clone(),
1045                enclosing_component: e.enclosing_component.clone(),
1046                repeated: None,
1047                debug: e.debug.clone(),
1048                ..Default::default()
1049            }))
1050        }
1051        match self {
1052            PropertyAnimation::Static(e) => PropertyAnimation::Static(deep_clone(e)),
1053            PropertyAnimation::Transition { state_ref, animations } => {
1054                PropertyAnimation::Transition {
1055                    state_ref: state_ref.clone(),
1056                    animations: animations
1057                        .iter()
1058                        .map(|t| TransitionPropertyAnimation {
1059                            state_id: t.state_id,
1060                            direction: t.direction,
1061                            animation: deep_clone(&t.animation),
1062                        })
1063                        .collect(),
1064                }
1065            }
1066        }
1067    }
1068}
1069
1070/// Map the accessibility property (eg "accessible-role", "accessible-label") to its named reference
1071#[derive(Default, Clone)]
1072pub struct AccessibilityProps(pub BTreeMap<String, NamedReference>);
1073
1074#[derive(Clone, Debug)]
1075pub struct GeometryProps {
1076    pub x: NamedReference,
1077    pub y: NamedReference,
1078    pub width: NamedReference,
1079    pub height: NamedReference,
1080}
1081
1082/// The z-order of a child element within a parent that has dynamic z-ordering.
1083#[derive(Clone, Debug)]
1084pub enum ZOrder {
1085    /// z is a compile-time constant (used for repeater/conditional children).
1086    Constant(f32),
1087    /// z is bound to a runtime expression (NamedReference to the child's z property).
1088    Dynamic(NamedReference),
1089    /// The child is a repeated element (`for` or `if`) whose instances each have
1090    /// their own z value: they are expanded and sorted individually among the
1091    /// parent's children. The NamedReference is the z property within the repeated
1092    /// component, evaluated per instance.
1093    PerInstance(NamedReference),
1094}
1095
1096impl GeometryProps {
1097    pub fn new(element: &ElementRc) -> Self {
1098        Self {
1099            x: NamedReference::new(element, SmolStr::new_static("x")),
1100            y: NamedReference::new(element, SmolStr::new_static("y")),
1101            width: NamedReference::new(element, SmolStr::new_static("width")),
1102            height: NamedReference::new(element, SmolStr::new_static("height")),
1103        }
1104    }
1105}
1106
1107pub type BindingsMap = BTreeMap<SmolStr, RefCell<BindingExpression>>;
1108
1109/// A sealed wrapper around an element's binding map.
1110///
1111/// The inner map is private to the `object_tree` module, so other modules cannot read or mutate
1112/// it in a hook-unaware way (treating a synthetic debug hook as a real binding). All access from
1113/// outside goes through the hook-aware accessors on [`Element`]. The field itself can stay public
1114/// — `Element` struct literals keep compiling — because the seal is on this inner map.
1115#[derive(Clone, Default)]
1116pub struct Bindings(BindingsMap);
1117
1118impl std::iter::FromIterator<(SmolStr, RefCell<BindingExpression>)> for Bindings {
1119    fn from_iter<T: IntoIterator<Item = (SmolStr, RefCell<BindingExpression>)>>(iter: T) -> Self {
1120        Bindings(iter.into_iter().collect())
1121    }
1122}
1123
1124impl From<BindingsMap> for Bindings {
1125    fn from(map: BindingsMap) -> Self {
1126        Bindings(map)
1127    }
1128}
1129
1130impl Bindings {
1131    /// The raw binding cell for `name`, including a synthetic debug hook.
1132    ///
1133    /// The counterpart of [`Element::binding_cell_including_synthetic`], for code that holds a
1134    /// `&Bindings` (e.g. an animation element's bindings) rather than a whole `Element`.
1135    pub fn binding_cell_including_synthetic(
1136        &self,
1137        name: &str,
1138    ) -> Option<&RefCell<BindingExpression>> {
1139        self.0.get(name)
1140    }
1141}
1142
1143#[derive(Clone, Debug)]
1144pub struct ElementDebugInfo {
1145    // The id qualified with the enclosing component name. Given `foo := Bar {}` this is `EnclosingComponent::foo`
1146    pub qualified_id: Option<SmolStr>,
1147    pub type_name: String,
1148    // Hold an id for each element that is unique during this build, based on the source file and
1149    // the offset of the `LBrace` token.
1150    //
1151    // This helps to cross-reference the element in the different build stages the LSP has to deal with.
1152    pub element_hash: u64,
1153    pub node: syntax_nodes::Element,
1154    // Field to indicate whether this element was a layout that had
1155    // been lowered into a rectangle in the lower_layouts pass.
1156    pub layout: Option<crate::layout::Layout>,
1157    /// Set to true if the ElementDebugInfo following this one in the debug vector
1158    /// in Element::debug is the last one and the next entry belongs to an other element.
1159    /// This can happen as a result of rectangle optimization, for example.
1160    pub element_boundary: bool,
1161}
1162
1163impl ElementDebugInfo {
1164    // Returns a comma separate string that encodes the element type name (`Rectangle`, `MyButton`, etc.),
1165    // the qualified id (`SurroundingComponent::my-id`), and optionally the layout kind
1166    // (`h-box`, `v-box`, `grid`, `flex-box`).
1167    fn encoded_element_info(&self) -> String {
1168        let mut info = self.type_name.clone();
1169        info.push(',');
1170        if let Some(id) = self.qualified_id.as_ref() {
1171            info.push_str(id);
1172        }
1173        info.push(',');
1174        if let Some(layout) = &self.layout {
1175            use crate::layout::{Layout, Orientation};
1176            match layout {
1177                Layout::BoxLayout(b) => match b.orientation {
1178                    Orientation::Horizontal => info.push_str("h-box"),
1179                    Orientation::Vertical => info.push_str("v-box"),
1180                },
1181                Layout::GridLayout(_) => info.push_str("grid"),
1182                Layout::FlexboxLayout(_) => info.push_str("flex-box"),
1183            }
1184        }
1185        info
1186    }
1187}
1188
1189/// An Element is an instantiation of a Component
1190#[derive(Default)]
1191pub struct Element {
1192    /// The id as named in the original .slint file.
1193    ///
1194    /// Note that it can only be used for lookup before inlining.
1195    /// After inlining there can be duplicated id in the component.
1196    /// The id are then re-assigned unique id in the assign_id pass
1197    pub id: SmolStr,
1198    //pub base: QualifiedTypeName,
1199    pub base_type: ElementType,
1200    /// Currently contains also the callbacks. FIXME: should that be changed?
1201    pub bindings: Bindings,
1202    pub change_callbacks: BTreeMap<SmolStr, RefCell<Vec<Expression>>>,
1203    pub property_analysis: RefCell<BTreeMap<SmolStr, PropertyAnalysis>>,
1204
1205    pub children: Vec<ElementRc>,
1206    /// The component which contains this element.
1207    pub enclosing_component: Weak<Component>,
1208
1209    pub property_declarations: BTreeMap<SmolStr, PropertyDeclaration>,
1210
1211    /// Members that shadow an inherited one, mapping the source name to the mangled key in
1212    /// `property_declarations`.
1213    pub shadowing_members: BTreeMap<SmolStr, SmolStr>,
1214
1215    /// Main owner for a reference to a property.
1216    pub named_references: crate::namedreference::NamedReferenceContainer,
1217
1218    /// This element is part of a `for <xxx> in <model>`:
1219    pub repeated: Option<RepeatedElementInfo>,
1220    /// This element is a placeholder to embed an Component at
1221    pub is_component_placeholder: bool,
1222    /// True when this element was injected by `lower_property_to_element` or the `visible` pass
1223    /// to wrap another element for a property like `opacity`/`transform-rotation`/`visible` (see
1224    /// `adjust_geometry_for_injected_parent`). Such wrappers take over the wrapped element's
1225    /// geometry, so consumers that need the wrapped element's source parent must walk past them.
1226    pub is_injected_wrapper_element: bool,
1227
1228    /// Z-order of this element within a parent whose children are dynamically z-ordered.
1229    /// Stored on the child so it remains consistent when the children vector is reordered
1230    /// or moved to another parent.
1231    pub z_order: Option<ZOrder>,
1232
1233    pub states: Vec<State>,
1234    pub transitions: Vec<Transition>,
1235    pub match_elements: Vec<MatchElementInfo>,
1236    /// true when this item's geometry is handled by a layout
1237    pub child_of_layout: bool,
1238    /// true when this item is a direct cell of a `FlexboxLayout`. Narrower
1239    /// than `child_of_layout`: only flexbox cells need the per-repeater
1240    /// `flexbox_layout_item_info` accessor.
1241    pub child_of_flexbox: bool,
1242    /// The orientation of the box layout this element is a repeated cell of.
1243    /// Only set when the cell also binds `cross-axis-self-alignment`; lets the
1244    /// generated `layout_item_info` return that value only for the cross axis,
1245    /// so the main-axis cache stays independent of it.
1246    pub parent_box_layout_orientation: Option<Orientation>,
1247    /// The property pointing to the layout info. `(horizontal, vertical)`
1248    ///
1249    /// Query it through `Element::effective_layout_info_prop`: the horizontal one
1250    /// it returns may be `layout_info_h_at_own_height`
1251    /// instead of `.0`. Use the field itself to copy, move, or write through it,
1252    /// where the element's own property is the one meant.
1253    pub layout_info_prop: Option<(NamedReference, NamedReference)>,
1254    /// `pure function layoutinfo-v-with-constraint(width: length) -> LayoutInfo`
1255    /// synthesized for elements whose vertical layout info depends on
1256    /// their width — lets the parent supply the width and avoid the
1257    /// recursion that would happen via the descendants' width property.
1258    pub layout_info_v_with_constraint: Option<NamedReference>,
1259    /// `layoutinfo-h-at-own-height`, the horizontal layout info of a column
1260    /// direction `FlexboxLayout` computed at its own height instead of an
1261    /// unbounded one, so a wrapping column counts the columns that fit. Only
1262    /// read where the height is settled by the source: see
1263    /// `Element::height_is_literal` and `Element::effective_layout_info_prop`.
1264    pub layout_info_h_at_own_height: Option<NamedReference>,
1265    /// Whether the effective `height` binding, on the element or a base, is a
1266    /// length literal that is not a percentage. Such a height can be read while
1267    /// computing the element's own horizontal layout info without closing a
1268    /// binding loop. `lower_layouts` computes it once with
1269    /// `Element::compute_height_is_literal`, which says why the rule is that
1270    /// narrow, while the bindings are still where the source put them.
1271    pub height_is_literal: bool,
1272    /// Whether we have `preferred-{width,height}: 100%`
1273    pub default_fill_parent: (bool, bool),
1274
1275    pub accessibility_props: AccessibilityProps,
1276
1277    /// Reference to the property.
1278    /// This is always initialized from the element constructor, but is Option because it references itself
1279    pub geometry_props: Option<GeometryProps>,
1280
1281    /// true if this Element is the fake Flickable content element
1282    pub is_flickable_content: bool,
1283
1284    /// true if this Element may have a popup as child meaning it cannot be optimized
1285    /// because the popup references it.
1286    pub has_popup_child: bool,
1287
1288    /// True for compiler-generated tooltip `PopupWindow` instances (see `lower_tooltips`).
1289    pub is_tooltip: bool,
1290
1291    /// This is the component-local index of this item in the item tree array.
1292    /// It is generated after the last pass and before the generators run.
1293    pub item_index: OnceCell<u32>,
1294    /// the index of the first children in the tree, set with item_index
1295    pub item_index_of_first_children: OnceCell<u32>,
1296
1297    /// True when this element is in a component was declared with the `:=` symbol instead of the `component` keyword
1298    pub is_legacy_syntax: bool,
1299
1300    /// How many times the element was inlined
1301    pub inline_depth: i32,
1302
1303    /// If this element is assigned to a specific slot in its parent component (e.g., `name << ...`)
1304    pub slot_target: Option<SmolStr>,
1305
1306    /// Slot forwarding mappings declared on this element: `target: source;`
1307    pub forwarded_slots: Vec<SlotForwarding>,
1308
1309    /// Information about the grid cell containing this element, if applicable
1310    pub grid_layout_cell: Option<Rc<RefCell<crate::layout::GridLayoutCell>>>,
1311
1312    /// Debug information about this element.
1313    ///
1314    /// There can be several in case of inlining or optimization (child merged into their parent).
1315    ///
1316    /// The order in the list is first the parent, and then the removed children.
1317    pub debug: Vec<ElementDebugInfo>,
1318}
1319
1320impl Spanned for Element {
1321    fn span(&self) -> crate::diagnostics::Span {
1322        self.debug
1323            .first()
1324            .map(|n| {
1325                // If possible, only span the qualified name of the Element (i.e. the `MyElement`
1326                // part of `MyElement { ... }`, as otherwise the span can get very large, which
1327                // isn't useful for showing diagnostics.
1328                // Only use the full span as the fallback.
1329                n.node.QualifiedName().as_ref().map(Spanned::span).unwrap_or_else(|| n.node.span())
1330            })
1331            .unwrap_or_default()
1332    }
1333
1334    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
1335        self.debug.first().map(|n| &n.node.source_file)
1336    }
1337}
1338
1339impl core::fmt::Debug for Element {
1340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1341        pretty_print(f, self, 0)
1342    }
1343}
1344
1345pub fn pretty_print(
1346    f: &mut impl std::fmt::Write,
1347    e: &Element,
1348    indentation: usize,
1349) -> std::fmt::Result {
1350    if let Some(repeated) = &e.repeated {
1351        write!(f, "for {}[{}] in ", repeated.model_data_id, repeated.index_id)?;
1352        expression_tree::pretty_print(f, &repeated.model)?;
1353        write!(f, ":")?;
1354        if let ElementType::Component(base) = &e.base_type {
1355            write!(f, "(base) ")?;
1356            if base.parent_element().is_some() {
1357                pretty_print(f, &base.root_element.borrow(), indentation)?;
1358                return Ok(());
1359            }
1360        }
1361    }
1362    if e.is_component_placeholder {
1363        write!(f, "/* Component Placeholder */ ")?;
1364    }
1365    writeln!(f, "{} := {} {{  /* {} */", e.id, e.base_type, e.element_infos())?;
1366    let mut indentation = indentation + 1;
1367    macro_rules! indent {
1368        () => {
1369            for _ in 0..indentation {
1370                write!(f, "   ")?
1371            }
1372        };
1373    }
1374    for (name, ty) in &e.property_declarations {
1375        indent!();
1376        if let Some(alias) = &ty.is_alias {
1377            writeln!(f, "alias<{}> {} <=> {:?};", ty.property_type, name, alias)?
1378        } else {
1379            writeln!(f, "property<{}> {};", ty.property_type, name)?
1380        }
1381    }
1382    for (name, expr) in &e.bindings.0 {
1383        indent!();
1384        write!(f, "{name}: ")?;
1385        let Ok(expr) = expr.try_borrow() else {
1386            writeln!(f, "<borrowed>")?;
1387            continue;
1388        };
1389        expression_tree::pretty_print(f, &expr.expression)?;
1390        if expr.analysis.as_ref().is_some_and(|a| a.is_const) {
1391            write!(f, "/*const*/")?;
1392        }
1393        writeln!(f, ";")?;
1394        //writeln!(f, "; /*{}*/", expr.priority)?;
1395        if let Some(anim) = &expr.animation {
1396            indent!();
1397            writeln!(f, "animate {name} {anim:?}")?;
1398        }
1399        for nr in &expr.two_way_bindings {
1400            indent!();
1401            writeln!(f, "{name} <=> {nr:?};")?;
1402        }
1403    }
1404    for (name, ch) in &e.change_callbacks {
1405        for ex in &*ch.borrow() {
1406            indent!();
1407            write!(f, "changed {name} => ")?;
1408            expression_tree::pretty_print(f, ex)?;
1409            writeln!(f)?;
1410        }
1411    }
1412    if !e.states.is_empty() {
1413        indent!();
1414        writeln!(f, "states {:?}", e.states)?;
1415    }
1416    if !e.transitions.is_empty() {
1417        indent!();
1418        writeln!(f, "transitions {:?} ", e.transitions)?;
1419    }
1420    for c in &e.children {
1421        indent!();
1422        pretty_print(f, &c.borrow(), indentation)?
1423    }
1424    if let Some(g) = &e.geometry_props {
1425        indent!();
1426        writeln!(f, "geometry {g:?} ")?;
1427    }
1428
1429    /*if let Type::Component(base) = &e.base_type {
1430        pretty_print(f, &c.borrow(), indentation)?
1431    }*/
1432    indentation -= 1;
1433    indent!();
1434    writeln!(f, "}}")
1435}
1436
1437#[derive(Clone, Default, Debug)]
1438pub struct PropertyAnalysis {
1439    /// true if somewhere in the code, there is an expression that changes this property with an assignment
1440    pub is_set: bool,
1441
1442    /// True if this property might be set from a different component.
1443    pub is_set_externally: bool,
1444
1445    /// true if somewhere in the code, an expression is reading this property
1446    /// Note: currently this is only set in the binding analysis pass
1447    pub is_read: bool,
1448
1449    /// true if this property is read from another component
1450    pub is_read_externally: bool,
1451
1452    /// True if the property is linked to another property that is read only. That property becomes read-only
1453    pub is_linked_to_read_only: bool,
1454
1455    /// True if this property is linked to another property
1456    pub is_linked: bool,
1457}
1458
1459impl PropertyAnalysis {
1460    /// Merge analysis from base element for inlining
1461    ///
1462    /// Contrary to `merge`, we don't keep the external uses because
1463    /// they should come from us
1464    pub fn merge_with_base(&mut self, other: &PropertyAnalysis) {
1465        self.is_set |= other.is_set;
1466        self.is_read |= other.is_read;
1467    }
1468
1469    /// Merge the analysis
1470    pub fn merge(&mut self, other: &PropertyAnalysis) {
1471        self.is_set |= other.is_set;
1472        self.is_read |= other.is_read;
1473        self.is_read_externally |= other.is_read_externally;
1474        self.is_set_externally |= other.is_set_externally;
1475    }
1476
1477    /// Return true if it is read or set or used in any way
1478    pub fn is_used(&self) -> bool {
1479        self.is_read || self.is_read_externally || self.is_set || self.is_set_externally
1480    }
1481}
1482
1483#[derive(Debug, Clone)]
1484pub struct ListViewInfo {
1485    pub content_y: NamedReference,
1486    /// `None` when the user explicitly sets `content-height` on the ListView;
1487    /// `Some` when the ListView computes it from the content.
1488    pub content_height: Option<NamedReference>,
1489    /// `None` when the user explicitly sets `content-width` on the ListView;
1490    /// `Some` when the ListView computes it from the content.
1491    pub content_width: Option<NamedReference>,
1492    /// The ListView's inner visible height (not counting eventual scrollbar)
1493    pub listview_height: NamedReference,
1494    /// The ListView's inner visible width (not counting eventual scrollbar)
1495    pub listview_width: NamedReference,
1496}
1497
1498#[derive(Debug, Clone)]
1499/// If the parent element is a repeated element, this has information about the models
1500pub struct RepeatedElementInfo {
1501    pub model: Expression,
1502    pub model_data_id: SmolStr,
1503    pub index_id: SmolStr,
1504    /// A conditional element is just a for whose model is a boolean expression
1505    ///
1506    /// When this is true, the model is of type boolean instead of Model
1507    pub is_conditional_element: bool,
1508    /// When the for is the delegate of a ListView
1509    pub is_listview: Option<ListViewInfo>,
1510}
1511
1512/// Struct for a match element that later is resolved into standard conditional elements
1513pub struct MatchElementInfo {
1514    /// The match element node, used for diagnostics related to the match element as a whole
1515    pub node: syntax_nodes::MatchElement,
1516    /// The value that is matched on
1517    pub subject: Expression,
1518    /// Each case and the corresponding element
1519    pub cases: Vec<MatchCaseInfo>,
1520    /// The `*` case of the match element, if any
1521    pub wildcard: WildcardMatchCaseInfo,
1522}
1523
1524pub enum WildcardMatchCaseInfo {
1525    None,
1526    Empty,
1527    Element(ElementRc),
1528}
1529
1530/// One case of a match element
1531pub struct MatchCaseInfo {
1532    /// The value the subject is compared against
1533    pub value: Expression,
1534    /// The syntax node
1535    pub node: syntax_nodes::Expression,
1536    /// The element to potentially show. None for the empty case
1537    pub element: Option<ElementRc>,
1538}
1539
1540impl MatchElementInfo {
1541    /// The elements of all the cases, skipping the empty cases
1542    pub fn elements(&self) -> impl Iterator<Item = ElementRc> + '_ {
1543        self.cases.iter().filter_map(|case| case.element.clone()).chain(match &self.wildcard {
1544            WildcardMatchCaseInfo::Element(e) => Some(e.clone()),
1545            WildcardMatchCaseInfo::None | WildcardMatchCaseInfo::Empty => None,
1546        })
1547    }
1548
1549    /// Make every case a conditional element
1550    pub fn lower_to_conditional_elements(&self) {
1551        let compare = |value: &Expression, op| Expression::BinaryExpression {
1552            lhs: Box::new(self.subject.clone()),
1553            rhs: Box::new(value.clone()),
1554            op,
1555            source_location: None,
1556        };
1557        let show_when = |element: &ElementRc, condition| {
1558            element.borrow_mut().repeated = Some(RepeatedElementInfo {
1559                model: condition,
1560                model_data_id: SmolStr::default(),
1561                index_id: SmolStr::default(),
1562                is_conditional_element: true,
1563                is_listview: None,
1564            });
1565        };
1566
1567        for case in &self.cases {
1568            if let Some(element) = &case.element {
1569                show_when(element, compare(&case.value, '='));
1570            }
1571        }
1572        if let WildcardMatchCaseInfo::Element(wildcard) = &self.wildcard {
1573            let condition = self
1574                .cases
1575                .iter()
1576                .map(|case| compare(&case.value, '!'))
1577                .reduce(|lhs, rhs| Expression::BinaryExpression {
1578                    lhs: Box::new(lhs),
1579                    rhs: Box::new(rhs),
1580                    op: '&',
1581                    source_location: None,
1582                })
1583                .unwrap_or(Expression::BoolLiteral(true));
1584            show_when(wildcard, condition);
1585        }
1586    }
1587}
1588
1589pub type ElementRc = Rc<RefCell<Element>>;
1590pub type ElementWeak = Weak<RefCell<Element>>;
1591
1592impl Element {
1593    pub fn make_rc(self) -> ElementRc {
1594        let r = ElementRc::new(RefCell::new(self));
1595        let g = GeometryProps::new(&r);
1596        r.borrow_mut().geometry_props = Some(g);
1597        r
1598    }
1599
1600    pub fn from_node(
1601        node: syntax_nodes::Element,
1602        id: SmolStr,
1603        parent_type: ElementType,
1604        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
1605        declared_slots: &mut Vec<DeclaredSlot>,
1606        is_legacy_syntax: bool,
1607        diag: &mut BuildDiagnostics,
1608        tr: &TypeRegister,
1609    ) -> ElementRc {
1610        // A child element's parent_type is the type of its parent; the root
1611        // gets a sentinel from Component::from_node
1612        #[cfg(feature = "slint-sc")]
1613        let is_component_root =
1614            !matches!(parent_type, ElementType::Builtin(_) | ElementType::Component(_));
1615        let base_type = if let Some(base_node) = node.QualifiedName() {
1616            let base = QualifiedTypeName::from_node(base_node.clone());
1617            let base_string = base.to_smolstr();
1618            match parent_type.lookup_type_for_child_element(&base_string, tr) {
1619                Ok(ElementType::Component(c)) if c.is_global() => {
1620                    diag.push_error(
1621                        "Cannot create an instance of a global component".into(),
1622                        &base_node,
1623                    );
1624                    ElementType::Error
1625                }
1626                Ok(ty) => {
1627                    // Window children, including through a component that
1628                    // inherits Window, error in warn_about_child_windows.
1629                    #[cfg(feature = "slint-sc")]
1630                    if let ElementType::Builtin(b) = &ty
1631                        && !b.slint_sc
1632                    {
1633                        diag.slint_sc_error(
1634                            &format!("The builtin element '{}' is", b.name),
1635                            &base_node,
1636                        );
1637                    }
1638                    ty
1639                }
1640                Err(err) => {
1641                    diag.push_error(err, &base_node);
1642                    ElementType::Error
1643                }
1644            }
1645        } else if parent_type == ElementType::Global || parent_type == ElementType::Interface {
1646            // This must be a global component or interface. It can only have properties and callbacks
1647            let mut error_on = |node: &dyn Spanned, what: &str| {
1648                let element_type = match parent_type {
1649                    ElementType::Global => "A global component",
1650                    ElementType::Interface => "An interface",
1651                    _ => "An unexpected type",
1652                };
1653                diag.push_error(format!("{element_type} cannot have {what}"), node);
1654            };
1655            node.SubElement().for_each(|n| error_on(&n, "sub elements"));
1656            node.RepeatedElement().for_each(|n| error_on(&n, "sub elements"));
1657            if let Some(n) = node.ChildrenPlaceholder() {
1658                error_on(&n, "sub elements");
1659            }
1660            node.PropertyAnimation().for_each(|n| error_on(&n, "animations"));
1661            node.States().for_each(|n| error_on(&n, "states"));
1662            node.Transitions().for_each(|n| error_on(&n, "transitions"));
1663            node.CallbackDeclaration().for_each(|cb| {
1664                if parser::identifier_text(&cb.DeclaredIdentifier()).is_some_and(|s| s == "init") {
1665                    error_on(&cb, "an 'init' callback")
1666                }
1667            });
1668            node.CallbackConnection().for_each(|cb| {
1669                if parser::identifier_text(&cb).is_some_and(|s| s == "init") {
1670                    error_on(&cb, "an 'init' callback")
1671                }
1672            });
1673            node.MatchElement().for_each(|n| error_on(&n, "match elements"));
1674            node.SlotDeclaration().for_each(|n| error_on(&n, "slots"));
1675
1676            if parent_type == ElementType::Interface {
1677                node.Binding().for_each(|n| error_on(&n, "bindings"));
1678                node.TwoWayBinding().for_each(|n| error_on(&n, "two-way bindings"));
1679
1680                node.ImplementStatement().for_each(|stmt| {
1681                    diag.push_error("Interfaces cannot implement another interface".into(), &stmt);
1682                });
1683            } else {
1684                node.ImplementStatement().for_each(|stmt| {
1685                    diag.push_error("Globals cannot implement an interface".into(), &stmt);
1686                });
1687            }
1688
1689            parent_type
1690        } else if parent_type != ElementType::Error {
1691            // This should normally never happen because the parser does not allow for this
1692            assert!(diag.has_errors());
1693            return ElementRc::default();
1694        } else {
1695            tr.empty_type()
1696        };
1697        let is_interface = base_type == ElementType::Interface;
1698        // This isn't truly qualified yet, the enclosing component is added at the end of Component::from_node
1699        let qualified_id = (!id.is_empty()).then(|| id.clone());
1700        if let ElementType::Component(c) = &base_type {
1701            c.used.set(true);
1702        }
1703        let type_name = base_type
1704            .type_name()
1705            .filter(|_| base_type != tr.empty_type())
1706            .unwrap_or_default()
1707            .to_string();
1708        let mut r = Element {
1709            id,
1710            base_type: base_type.clone(),
1711            debug: vec![ElementDebugInfo {
1712                qualified_id,
1713                element_hash: 0,
1714                type_name,
1715                node: node.clone(),
1716                layout: None,
1717                element_boundary: false,
1718            }],
1719            is_legacy_syntax,
1720            ..Default::default()
1721        };
1722
1723        let mut property_bindings: Vec<(
1724            SmolStr,
1725            syntax_nodes::BindingExpression,
1726            syntax_nodes::DeclaredIdentifier,
1727        )> = Vec::new();
1728
1729        let mut two_way_bindings: Vec<(
1730            SmolStr,
1731            syntax_nodes::TwoWayBinding,
1732            syntax_nodes::DeclaredIdentifier,
1733        )> = Vec::new();
1734
1735        for prop_decl in node.PropertyDeclaration() {
1736            // Only the root element's properties become part of the component's API
1737            #[cfg(feature = "slint-sc")]
1738            if !is_component_root {
1739                diag.slint_sc_error(
1740                    "Declaring a property on an element other than the root is",
1741                    &prop_decl,
1742                );
1743            }
1744            let prop_type = prop_decl
1745                .Type()
1746                .map(|type_node| type_from_node(type_node, diag, tr))
1747                // Type::Void is used for two way bindings without type specified
1748                .unwrap_or(Type::InferredProperty);
1749
1750            let unresolved_prop_name =
1751                unwrap_or_continue!(parser::identifier_text(&prop_decl.DeclaredIdentifier()); diag);
1752            let declaration = r.member_declaration(&unresolved_prop_name);
1753            let name_token =
1754                prop_decl.DeclaredIdentifier().child_token(SyntaxKind::Identifier).unwrap();
1755            if let MemberDeclaration::Conflict { existing_type, declared_in } = &declaration {
1756                match existing_type {
1757                    Type::Callback { .. } => diag.push_error(
1758                        format!("Cannot declare property '{unresolved_prop_name}' when a callback with the same name exists"),
1759                        &name_token,
1760                    ),
1761                    Type::Function { .. } => diag.push_error(
1762                        format!("Cannot declare property '{unresolved_prop_name}' when a function with the same name exists"),
1763                        &name_token,
1764                    ),
1765                    _ => diag.push_error(
1766                        cannot_override_message(Some("property"), &unresolved_prop_name, declared_in),
1767                        &name_token,
1768                    ),
1769                }
1770                continue;
1771            }
1772            let prop_name = declaration.register(&mut r, &unresolved_prop_name, &name_token, diag);
1773            let shadowed_name =
1774                (prop_name != unresolved_prop_name).then(|| unresolved_prop_name.clone());
1775
1776            let mut visibility = None;
1777            for token in prop_decl.children_with_tokens() {
1778                if token.kind() != SyntaxKind::Identifier {
1779                    continue;
1780                }
1781                match (token.as_token().unwrap().text(), visibility) {
1782                    ("in", None) => visibility = Some(PropertyVisibility::Input),
1783                    ("in", Some(_)) => diag.push_error("Extra 'in' keyword".into(), &token),
1784                    ("out", None) => visibility = Some(PropertyVisibility::Output),
1785                    ("out", Some(_)) => diag.push_error("Extra 'out' keyword".into(), &token),
1786                    ("in-out" | "in_out", None) => visibility = Some(PropertyVisibility::InOut),
1787                    ("in-out" | "in_out", Some(_)) => {
1788                        diag.push_error("Extra 'in-out' keyword".into(), &token)
1789                    }
1790                    ("private", None) => visibility = Some(PropertyVisibility::Private),
1791                    ("private", Some(_)) => {
1792                        diag.push_error("Extra 'private' keyword".into(), &token)
1793                    }
1794                    _ => (),
1795                }
1796            }
1797            let visibility = visibility.unwrap_or({
1798                if is_legacy_syntax {
1799                    PropertyVisibility::InOut
1800                } else {
1801                    PropertyVisibility::Private
1802                }
1803            });
1804
1805            if is_interface {
1806                if let Some(binding_expression) = &prop_decl.BindingExpression() {
1807                    diag.push_error(
1808                        "Interface properties cannot have default values".into(),
1809                        binding_expression,
1810                    )
1811                }
1812                if let Some(two_way) = &prop_decl.TwoWayBinding() {
1813                    diag.push_error(
1814                        "Interface properties cannot have default bindings".into(),
1815                        two_way,
1816                    )
1817                }
1818                if visibility == PropertyVisibility::Private {
1819                    diag.push_error(
1820                        "'private' properties are inaccessible in an interface".into(),
1821                        &prop_decl,
1822                    );
1823                }
1824            }
1825
1826            let deprecated = member_deprecation(
1827                prop_decl.PropertyDeprecation(),
1828                DeprecationHint::TwoWayBinding(
1829                    prop_decl.TwoWayBinding().and_then(|twb| twb.Expression().QualifiedName()),
1830                ),
1831                tr,
1832                diag,
1833            );
1834
1835            r.property_declarations.insert(
1836                prop_name.clone(),
1837                PropertyDeclaration {
1838                    property_type: prop_type,
1839                    node: Some(prop_decl.clone().into()),
1840                    visibility,
1841                    shadowed_name,
1842                    shadowable: shadowable_attribute(prop_decl.ShadowableAttribute(), tr, diag),
1843                    deprecated,
1844                    ..Default::default()
1845                },
1846            );
1847
1848            if let Some(csn) = prop_decl.BindingExpression() {
1849                property_bindings.push((prop_name.clone(), csn, prop_decl.DeclaredIdentifier()));
1850            }
1851
1852            if let Some(csn) = prop_decl.TwoWayBinding() {
1853                #[cfg(feature = "slint-sc")]
1854                diag.slint_sc_error("Two-way bindings are", &csn);
1855                two_way_bindings.push((prop_name, csn, prop_decl.DeclaredIdentifier()));
1856            }
1857        }
1858
1859        let (implemented_interfaces, child_implements) =
1860            if matches!(r.base_type, ElementType::Global | ElementType::Interface) {
1861                // Already rejected above with a more specific diagnostic.
1862                (Vec::new(), Vec::new())
1863            } else if r.id == "root" {
1864                interfaces::get_implemented_interfaces(&r, &node, tr, diag)
1865            } else {
1866                interfaces::disallow_implement_in_non_root(&node, tr, diag);
1867                (Vec::new(), Vec::new())
1868            };
1869
1870        for (prop_name, csn, source) in property_bindings {
1871            match r.bindings.0.entry(prop_name.clone()) {
1872                Entry::Vacant(e) => {
1873                    e.insert(BindingExpression::new_uncompiled(csn.into()).into());
1874                }
1875                Entry::Occupied(_) => {
1876                    diag.push_error("Duplicated property binding".into(), &source);
1877                }
1878            }
1879        }
1880
1881        for (prop_name, csn, source) in two_way_bindings {
1882            if r.bindings
1883                .0
1884                .insert(prop_name, BindingExpression::new_uncompiled(csn.into()).into())
1885                .is_some()
1886            {
1887                diag.push_error("Duplicated property binding".into(), &source);
1888            }
1889        }
1890
1891        r.parse_bindings(
1892            node.Binding().filter_map(|b| {
1893                Some((b.child_token(SyntaxKind::Identifier)?, b.BindingExpression().into()))
1894            }),
1895            is_legacy_syntax,
1896            diag,
1897        );
1898        r.parse_bindings(
1899            node.TwoWayBinding()
1900                .filter_map(|b| Some((b.child_token(SyntaxKind::Identifier)?, b.into()))),
1901            is_legacy_syntax,
1902            diag,
1903        );
1904
1905        apply_default_type_properties(&mut r);
1906
1907        for sig_decl in node.CallbackDeclaration() {
1908            let name =
1909                unwrap_or_continue!(parser::identifier_text(&sig_decl.DeclaredIdentifier()); diag);
1910
1911            let pure = Some(
1912                sig_decl.child_token(SyntaxKind::Identifier).is_some_and(|t| t.text() == "pure"),
1913            );
1914
1915            #[cfg(feature = "slint-sc")]
1916            {
1917                // Only the root element's callbacks become part of the component's API
1918                if !is_component_root {
1919                    diag.slint_sc_error(
1920                        "Declaring a callback on an element other than the root is",
1921                        &sig_decl,
1922                    );
1923                }
1924                if pure == Some(true) {
1925                    diag.slint_sc_error("Pure callbacks are", &sig_decl);
1926                }
1927                if let Some(param) = sig_decl.CallbackDeclarationParameter().next() {
1928                    diag.slint_sc_error("Callback parameters are", &param);
1929                }
1930                if let Some(ret) = sig_decl.ReturnType() {
1931                    diag.slint_sc_error("Callback return types are", &ret);
1932                }
1933            }
1934
1935            let declaration = r.member_declaration(&name);
1936            if let MemberDeclaration::Conflict { existing_type, declared_in } = &declaration {
1937                if matches!(existing_type, Type::Callback { .. }) {
1938                    // Already declared on this very element, rather than inherited
1939                    if r.declaration(&name).is_some() {
1940                        diag.push_error(
1941                            "Duplicated callback declaration".into(),
1942                            &sig_decl.DeclaredIdentifier(),
1943                        );
1944                    } else {
1945                        diag.push_error(
1946                            cannot_override_message(Some("callback"), &name, declared_in),
1947                            &sig_decl.DeclaredIdentifier(),
1948                        )
1949                    }
1950                } else {
1951                    diag.push_error(
1952                        format!(
1953                            "Cannot declare callback '{name}' when a {} with the same name exists",
1954                            if matches!(existing_type, Type::Function { .. }) {
1955                                "function"
1956                            } else {
1957                                "property"
1958                            }
1959                        ),
1960                        &sig_decl.DeclaredIdentifier(),
1961                    );
1962                }
1963                continue;
1964            }
1965            let shadowable = shadowable_attribute(sig_decl.ShadowableAttribute(), tr, diag);
1966            let deprecated = member_deprecation(
1967                sig_decl.PropertyDeprecation(),
1968                DeprecationHint::TwoWayBinding(
1969                    sig_decl.TwoWayBinding().and_then(|twb| twb.Expression().QualifiedName()),
1970                ),
1971                tr,
1972                diag,
1973            );
1974            let source_name = name;
1975            let name =
1976                declaration.register(&mut r, &source_name, &sig_decl.DeclaredIdentifier(), diag);
1977            let shadowed_name = (name != source_name).then_some(source_name);
1978
1979            if let Some(csn) = sig_decl.TwoWayBinding() {
1980                #[cfg(feature = "slint-sc")]
1981                diag.slint_sc_error("Callback aliases are", &csn);
1982                r.bindings
1983                    .0
1984                    .insert(name.clone(), BindingExpression::new_uncompiled(csn.into()).into());
1985                r.property_declarations.insert(
1986                    name,
1987                    PropertyDeclaration {
1988                        property_type: Type::InferredCallback,
1989                        node: Some(sig_decl.into()),
1990                        visibility: PropertyVisibility::InOut,
1991                        pure,
1992                        shadowed_name,
1993                        shadowable,
1994                        deprecated,
1995                        ..Default::default()
1996                    },
1997                );
1998                continue;
1999            }
2000
2001            let args = sig_decl
2002                .CallbackDeclarationParameter()
2003                .map(|p| type_from_node(p.Type(), diag, tr))
2004                .collect();
2005            let return_type = sig_decl
2006                .ReturnType()
2007                .map(|ret_ty| type_from_node(ret_ty.Type(), diag, tr))
2008                .unwrap_or(Type::Void);
2009            let arg_names = sig_decl
2010                .CallbackDeclarationParameter()
2011                .map(|a| {
2012                    a.DeclaredIdentifier()
2013                        .and_then(|x| parser::identifier_text(&x))
2014                        .unwrap_or_default()
2015                })
2016                .collect();
2017            r.property_declarations.insert(
2018                name,
2019                PropertyDeclaration {
2020                    property_type: Type::Callback(Arc::new(Function {
2021                        return_type,
2022                        args,
2023                        arg_names,
2024                    })),
2025                    node: Some(sig_decl.into()),
2026                    visibility: PropertyVisibility::InOut,
2027                    pure,
2028                    shadowed_name,
2029                    shadowable,
2030                    deprecated,
2031                    ..Default::default()
2032                },
2033            );
2034        }
2035
2036        for func in node.Function() {
2037            #[cfg(feature = "slint-sc")]
2038            diag.slint_sc_error("Function declarations are", &func);
2039            let name =
2040                unwrap_or_continue!(parser::identifier_text(&func.DeclaredIdentifier()); diag);
2041
2042            let member_decl = r.member_declaration(&name);
2043            if let MemberDeclaration::Conflict { existing_type, declared_in } = &member_decl {
2044                if matches!(existing_type, Type::Callback { .. } | Type::Function { .. }) {
2045                    diag.push_error(
2046                        cannot_override_message(None, &name, declared_in),
2047                        &func.DeclaredIdentifier(),
2048                    )
2049                } else {
2050                    diag.push_error(
2051                        format!("Cannot declare function '{name}' when a property with the same name exists"),
2052                        &func.DeclaredIdentifier(),
2053                    );
2054                }
2055                continue;
2056            }
2057            let source_name = name;
2058            let name = member_decl.register(&mut r, &source_name, &func.DeclaredIdentifier(), diag);
2059            let shadowed_name = (name != source_name).then_some(source_name);
2060
2061            let mut args = Vec::new();
2062            let mut arg_names = Vec::new();
2063            for a in func.ArgumentDeclaration() {
2064                args.push(type_from_node(a.Type(), diag, tr));
2065                let name =
2066                    unwrap_or_continue!(parser::identifier_text(&a.DeclaredIdentifier()); diag);
2067                if arg_names.contains(&name) {
2068                    diag.push_error(
2069                        format!("Duplicated argument name '{name}'"),
2070                        &a.DeclaredIdentifier(),
2071                    );
2072                }
2073                arg_names.push(name);
2074            }
2075            let return_type = func
2076                .ReturnType()
2077                .map_or(Type::Void, |ret_ty| type_from_node(ret_ty.Type(), diag, tr));
2078
2079            let mut visibility = PropertyVisibility::Private;
2080            let mut pure = None;
2081            for token in func.children_with_tokens() {
2082                if token.kind() != SyntaxKind::Identifier {
2083                    continue;
2084                }
2085                match token.as_token().unwrap().text() {
2086                    "pure" => pure = Some(true),
2087                    "public" => {
2088                        visibility = PropertyVisibility::Public;
2089                        pure = pure.or(Some(false));
2090                    }
2091                    "protected" => {
2092                        visibility = PropertyVisibility::Protected;
2093                        pure = pure.or(Some(false));
2094                    }
2095                    _ => (),
2096                }
2097            }
2098
2099            if is_interface && visibility != PropertyVisibility::Public {
2100                diag.push_error(
2101                    "Function declarations in an interface must be public".into(),
2102                    &func,
2103                );
2104            }
2105
2106            let declaration = PropertyDeclaration {
2107                property_type: Type::Function(Arc::new(Function { return_type, args, arg_names })),
2108                node: Some(func.clone().into()),
2109                visibility,
2110                pure,
2111                shadowed_name,
2112                shadowable: shadowable_attribute(func.ShadowableAttribute(), tr, diag),
2113                deprecated: member_deprecation(
2114                    func.PropertyDeprecation(),
2115                    DeprecationHint::MessageRequired,
2116                    tr,
2117                    diag,
2118                ),
2119                ..Default::default()
2120            };
2121
2122            match (base_type.clone(), func.CodeBlock()) {
2123                (ElementType::Interface, Some(code_block)) => {
2124                    diag.push_error(
2125                        "Function declarations in interfaces must not have a body".into(),
2126                        &code_block,
2127                    );
2128                    continue;
2129                }
2130                (ElementType::Interface, None) => {
2131                    // Do not create a binding for this function, as it is just a declaration without body. It will be
2132                    // implemented by the component that implements the interface.
2133                    r.property_declarations.insert(name, declaration);
2134                    continue;
2135                }
2136                (_, None) => {
2137                    diag.push_error("Functions must have a code block".into(), &func);
2138                }
2139                (_, Some(_)) => {}
2140            }
2141
2142            if r.bindings
2143                .0
2144                .insert(name.clone(), BindingExpression::new_uncompiled(func.clone().into()).into())
2145                .is_some()
2146            {
2147                assert!(diag.has_errors());
2148            }
2149
2150            r.property_declarations.insert(name, declaration);
2151        }
2152
2153        for con_node in node.CallbackConnection() {
2154            let unresolved_name = unwrap_or_continue!(parser::identifier_text(&con_node); diag);
2155            let lookup_result =
2156                r.lookup_property(&unresolved_name, PropertyLookupMode::ComponentLocal);
2157            #[cfg(feature = "slint-sc")]
2158            {
2159                // A callback declared in the file is in the subset by construction;
2160                // a builtin one only when marked in its declaration, which keeps
2161                // `init` and the rest of TouchArea out.
2162                if !r.is_user_declared_member(&unresolved_name) && !lookup_result.is_slint_sc {
2163                    diag.slint_sc_error(
2164                        &format!("The callback '{unresolved_name}' is"),
2165                        &con_node.child_token(SyntaxKind::Identifier).unwrap(),
2166                    );
2167                }
2168                // The application implements the callbacks of the root element,
2169                // so a handler here would be a second answer to one invocation.
2170                if is_component_root
2171                    && r.property_declarations
2172                        .get(lookup_result.internal_or_resolved_name().as_str())
2173                        .is_some_and(|d| d.node.is_some())
2174                {
2175                    diag.slint_sc_error(
2176                        "A handler for a callback declared on the root element is",
2177                        &con_node.child_token(SyntaxKind::Identifier).unwrap(),
2178                    );
2179                }
2180                if let Some(param) = con_node.DeclaredIdentifier().next() {
2181                    diag.slint_sc_error("Callback handler parameters are", &param);
2182                }
2183            }
2184            // Setting a handler on a deprecated callback from outside the declaring component warns,
2185            // like assigning a deprecated property does.
2186            let deprecation =
2187                lookup_result.deprecated.clone().filter(|_| !lookup_result.is_local_to_component);
2188            let resolved_name = lookup_result.internal_or_resolved_name();
2189            let property_type = lookup_result.property_type;
2190            if let Type::Callback(callback) = &property_type {
2191                let num_arg = con_node.DeclaredIdentifier().count();
2192                if num_arg > callback.args.len() {
2193                    diag.push_error(
2194                        format!(
2195                            "'{}' only has {} arguments, but {} were provided",
2196                            unresolved_name,
2197                            callback.args.len(),
2198                            num_arg
2199                        ),
2200                        &con_node.child_token(SyntaxKind::Identifier).unwrap(),
2201                    );
2202                }
2203            } else if property_type == Type::InferredCallback {
2204                // argument matching will happen later
2205            } else {
2206                if r.base_type != ElementType::Error {
2207                    diag.push_error(
2208                        format!("'{}' is not a callback in {}", unresolved_name, r.base_type),
2209                        &con_node.child_token(SyntaxKind::Identifier).unwrap(),
2210                    );
2211                }
2212                continue;
2213            }
2214            if let Some(message) = &deprecation {
2215                diag.push_property_deprecation_warning_with_message(
2216                    &unresolved_name,
2217                    message,
2218                    &con_node.child_token(SyntaxKind::Identifier).unwrap(),
2219                );
2220            }
2221            match r.bindings.0.entry(resolved_name) {
2222                Entry::Vacant(e) => {
2223                    e.insert(BindingExpression::new_uncompiled(con_node.clone().into()).into());
2224                }
2225                Entry::Occupied(mut e) => {
2226                    // A global may implement a callback declared in another global: the
2227                    // callback is declared as a two-way alias (`callback foo <=> Other.foo;`)
2228                    // and also given a handler (`foo => { ... }`). The alias node stays on
2229                    // the declaration, and the handler takes the binding expression slot.
2230                    let is_global_alias = r.base_type == ElementType::Global
2231                        && matches!(
2232                            &e.get().borrow().expression,
2233                            Expression::Uncompiled(node) if node.kind() == SyntaxKind::TwoWayBinding
2234                        );
2235                    if is_global_alias {
2236                        // Keep the handler as the binding and point its span at the handler
2237                        // name, so a duplicate-implementation error refers to the
2238                        // implementation rather than the alias. The alias is recovered from
2239                        // the declaration node, so dropping it from the binding is fine.
2240                        let mut handler =
2241                            BindingExpression::new_uncompiled(con_node.clone().into());
2242                        if let Some(name) = con_node.child_token(SyntaxKind::Identifier) {
2243                            handler.span = Some(name.to_source_location());
2244                        }
2245                        e.insert(handler.into());
2246                    } else {
2247                        diag.push_error(
2248                            "Duplicated callback".into(),
2249                            &con_node.child_token(SyntaxKind::Identifier).unwrap(),
2250                        );
2251                    }
2252                }
2253            }
2254        }
2255
2256        for anim in node.PropertyAnimation() {
2257            #[cfg(feature = "slint-sc")]
2258            diag.slint_sc_error("Animations are", &anim);
2259            if let Some(star) = anim.child_token(SyntaxKind::Star) {
2260                diag.push_error(
2261                    "catch-all property is only allowed within transitions".into(),
2262                    &star,
2263                )
2264            };
2265            for prop_name_token in anim.QualifiedName() {
2266                match QualifiedTypeName::from_node(prop_name_token.clone()).members.as_slice() {
2267                    [unresolved_prop_name] => {
2268                        if r.base_type == ElementType::Error {
2269                            continue;
2270                        };
2271                        let lookup_result = r.lookup_property(
2272                            unresolved_prop_name,
2273                            PropertyLookupMode::ComponentLocal,
2274                        );
2275                        let valid_assign = lookup_result.is_valid_for_assignment();
2276                        let binding_name = lookup_result.internal_or_resolved_name();
2277                        if let Some(anim_element) = animation_element_from_node(
2278                            &anim,
2279                            &prop_name_token,
2280                            lookup_result.property_type.clone(),
2281                            diag,
2282                            tr,
2283                        ) {
2284                            if !valid_assign {
2285                                diag.push_error(
2286                                    format!(
2287                                        "Cannot animate '{}' property '{}'",
2288                                        lookup_result.property_visibility, unresolved_prop_name
2289                                    ),
2290                                    &prop_name_token,
2291                                );
2292                            }
2293
2294                            if unresolved_prop_name != lookup_result.resolved_name.as_ref() {
2295                                diag.push_property_deprecation_warning(
2296                                    unresolved_prop_name,
2297                                    &lookup_result.resolved_name,
2298                                    &prop_name_token,
2299                                );
2300                            } else if let Some(message) = lookup_result
2301                                .deprecated
2302                                .as_ref()
2303                                .filter(|_| !lookup_result.is_local_to_component)
2304                            {
2305                                diag.push_property_deprecation_warning_with_message(
2306                                    unresolved_prop_name,
2307                                    message,
2308                                    &prop_name_token,
2309                                );
2310                            }
2311
2312                            let expr_binding =
2313                                r.bindings.0.entry(binding_name).or_insert_with(|| {
2314                                    let mut r = BindingExpression::from(Expression::Invalid);
2315                                    r.priority = 1;
2316                                    r.span = Some(prop_name_token.to_source_location());
2317                                    r.into()
2318                                });
2319                            if expr_binding
2320                                .get_mut()
2321                                .animation
2322                                .replace(PropertyAnimation::Static(anim_element))
2323                                .is_some()
2324                            {
2325                                diag.push_error("Duplicated animation".into(), &prop_name_token)
2326                            }
2327                        }
2328                    }
2329                    _ => diag.push_error(
2330                        "Can only refer to property in the current element".into(),
2331                        &prop_name_token,
2332                    ),
2333                }
2334            }
2335        }
2336
2337        for ch in node.PropertyChangedCallback() {
2338            #[cfg(feature = "slint-sc")]
2339            diag.slint_sc_error("Change callbacks are", &ch);
2340            let Some(prop) = parser::identifier_text(&ch.DeclaredIdentifier()) else { continue };
2341            let lookup_result = r.lookup_property(&prop, PropertyLookupMode::ComponentLocal);
2342            if !lookup_result.is_valid() {
2343                if r.base_type != ElementType::Error {
2344                    diag.push_error(
2345                        format!("Property '{prop}' does not exist"),
2346                        &ch.DeclaredIdentifier(),
2347                    );
2348                }
2349            } else if !lookup_result.property_type.is_property_type() {
2350                let what = match lookup_result.property_type {
2351                    Type::Function { .. } => "a function",
2352                    Type::Callback { .. } => "a callback",
2353                    _ => "not a property",
2354                };
2355                diag.push_error(
2356                    format!(
2357                        "Change callback can only be set on properties, and '{prop}' is {what}"
2358                    ),
2359                    &ch.DeclaredIdentifier(),
2360                );
2361            } else if lookup_result.property_visibility == PropertyVisibility::Private
2362                && !lookup_result.is_local_to_component
2363            {
2364                diag.push_error(
2365                    format!("Change callback on a private property '{prop}'"),
2366                    &ch.DeclaredIdentifier(),
2367                );
2368            }
2369            let handler = Expression::Uncompiled(ch.clone().into());
2370            match r.change_callbacks.entry(lookup_result.internal_or_resolved_name()) {
2371                Entry::Vacant(e) => {
2372                    e.insert(vec![handler].into());
2373                }
2374                Entry::Occupied(mut e) => {
2375                    diag.push_error(
2376                        format!("Duplicated change callback on '{prop}'"),
2377                        &ch.DeclaredIdentifier(),
2378                    );
2379                    e.get_mut().get_mut().push(handler);
2380                }
2381            }
2382        }
2383
2384        let r = r.make_rc();
2385
2386        for se in node.children() {
2387            if se.kind() != SyntaxKind::SlotForwarding {
2388                continue;
2389            }
2390            if !Self::assert_experimental_slots(diag, &se, "slot forwarding") {
2391                continue;
2392            }
2393
2394            let target_node = se.child_node(SyntaxKind::DeclaredIdentifier).unwrap();
2395            let target = parser::identifier_text(&target_node.clone()).unwrap_or_default();
2396
2397            if target == "children" {
2398                diag.push_error(
2399                    format!(
2400                        "The name '{target}' is reserved for the default slot. Use @children instead"
2401                    ),
2402                    &target_node,
2403                );
2404                continue;
2405            }
2406
2407            if r.borrow().forwarded_slots.iter().any(|f| f.target == target) {
2408                diag.push_error(format!("Duplicate assignment to slot '{target}'"), &target_node);
2409                continue;
2410            }
2411
2412            match &r.borrow().base_type {
2413                ElementType::Component(component)
2414                    if !component
2415                        .declared_slots
2416                        .borrow()
2417                        .iter()
2418                        .any(|slot| slot.name == target) =>
2419                {
2420                    diag.push_error(
2421                        format!("Unknown slot '{target}' in '{}'", component.id),
2422                        &target_node,
2423                    );
2424                    continue;
2425                }
2426                ElementType::Component(_) => {}
2427                _ => {
2428                    diag.push_error("Slot forwarding can only be used on components".into(), &se);
2429                    continue;
2430                }
2431            }
2432
2433            let Some(expression_node) = se.child_node(SyntaxKind::Expression) else {
2434                diag.push_error(
2435                    "Slot forwarding requires a slot identifier on the right-hand side".into(),
2436                    &se,
2437                );
2438                continue;
2439            };
2440            let Some(source) = Self::slot_forwarding_expr_identifier(&expression_node) else {
2441                diag.push_error(
2442                    "Slot forwarding requires a slot identifier on the right-hand side".into(),
2443                    &expression_node,
2444                );
2445                continue;
2446            };
2447
2448            if source == "children" {
2449                diag.push_error(
2450                    format!(
2451                        "The name '{source}' is reserved for the default slot. Use @children instead"
2452                    ),
2453                    &expression_node,
2454                );
2455                continue;
2456            }
2457
2458            r.borrow_mut().forwarded_slots.push(SlotForwarding {
2459                target,
2460                source,
2461                expression_node: expression_node.into(),
2462            });
2463        }
2464
2465        for forwarding in r.borrow().forwarded_slots.clone() {
2466            let source = forwarding.source.clone();
2467            if let Some(existing_cip) = component_child_insertion_points.get(source.as_str()) {
2468                if matches!(existing_cip.node, ChildInsertionPointNode::SlotPlaceholder(_)) {
2469                    diag.push_error(
2470                        format!(
2471                            "The slot '{source}' cannot be forwarded and used as a placeholder in the same component"
2472                        ),
2473                        &forwarding.expression_node,
2474                    );
2475                } else {
2476                    diag.push_error(
2477                        format!(
2478                            "{} can only appear once in an element",
2479                            slot_error_subject(&source)
2480                        ),
2481                        &forwarding.expression_node,
2482                    );
2483                }
2484                continue;
2485            }
2486            component_child_insertion_points.insert(
2487                source.to_string(),
2488                ChildrenInsertionPoint {
2489                    parent: r.clone(),
2490                    insertion_index: 0,
2491                    node: ChildInsertionPointNode::SlotForwarding(forwarding.expression_node),
2492                },
2493            );
2494        }
2495
2496        let mut assigned_slots = HashSet::new();
2497
2498        for se in node.children() {
2499            if se.kind() == SyntaxKind::SubElement {
2500                if let Some(slot_name) =
2501                    Self::sub_element_slot_placeholder_name(&se, declared_slots)
2502                {
2503                    Self::register_slot_placeholder(
2504                        &se,
2505                        slot_name,
2506                        &r,
2507                        component_child_insertion_points,
2508                        diag,
2509                        tr,
2510                    );
2511                    continue;
2512                }
2513                let parent_type = r.borrow().base_type.clone();
2514                r.borrow_mut().children.push(Element::from_sub_element_node(
2515                    se.into(),
2516                    parent_type,
2517                    component_child_insertion_points,
2518                    declared_slots,
2519                    is_legacy_syntax,
2520                    diag,
2521                    tr,
2522                ));
2523            } else if se.kind() == SyntaxKind::RepeatedElement {
2524                let mut sub_child_insertion_points = BTreeMap::new();
2525                let rep = Element::from_repeated_node(
2526                    se.into(),
2527                    &r,
2528                    &mut sub_child_insertion_points,
2529                    declared_slots,
2530                    is_legacy_syntax,
2531                    diag,
2532                    tr,
2533                );
2534                Self::reject_slot_placeholders(
2535                    diag,
2536                    declared_slots,
2537                    sub_child_insertion_points,
2538                    "a repeated element",
2539                );
2540                r.borrow_mut().children.push(rep);
2541            } else if se.kind() == SyntaxKind::ConditionalElement {
2542                let mut sub_child_insertion_points = BTreeMap::new();
2543                let rep = Element::from_conditional_node(
2544                    se.into(),
2545                    r.borrow().base_type.clone(),
2546                    &mut sub_child_insertion_points,
2547                    declared_slots,
2548                    is_legacy_syntax,
2549                    diag,
2550                    tr,
2551                );
2552                Self::reject_slot_placeholders(
2553                    diag,
2554                    declared_slots,
2555                    sub_child_insertion_points,
2556                    "a conditional element",
2557                );
2558                r.borrow_mut().children.push(rep);
2559            } else if se.kind() == SyntaxKind::MatchElement {
2560                let mut sub_child_insertion_points = BTreeMap::new();
2561                let match_element = Element::from_match_node(
2562                    se.into(),
2563                    r.borrow().base_type.clone(),
2564                    &mut sub_child_insertion_points,
2565                    declared_slots,
2566                    is_legacy_syntax,
2567                    diag,
2568                    tr,
2569                );
2570                Self::reject_slot_placeholders(
2571                    diag,
2572                    declared_slots,
2573                    sub_child_insertion_points,
2574                    "a match element",
2575                );
2576                let mut r = r.borrow_mut();
2577                r.children.extend(match_element.elements());
2578                r.match_elements.push(match_element);
2579            } else if se.kind() == SyntaxKind::ChildrenPlaceholder {
2580                #[cfg(feature = "slint-sc")]
2581                diag.slint_sc_error("The @children placeholder is", &se);
2582                if component_child_insertion_points.contains_key(DEFAULT_SLOT_NAME) {
2583                    diag.push_error(
2584                        format!(
2585                            "{} can only appear once in an element",
2586                            slot_error_subject(DEFAULT_SLOT_NAME)
2587                        ),
2588                        &se,
2589                    );
2590                } else {
2591                    component_child_insertion_points.insert(
2592                        DEFAULT_SLOT_NAME.into(),
2593                        ChildrenInsertionPoint {
2594                            parent: r.clone(),
2595                            insertion_index: r.borrow().children.len(),
2596                            node: ChildInsertionPointNode::ChildrenPlaceHolder(se.into()),
2597                        },
2598                    );
2599                }
2600            } else if se.kind() == SyntaxKind::SlotDeclaration {
2601                Self::assert_experimental_slots(diag, &se, "named slots");
2602                let decl: syntax_nodes::SlotDeclaration = se.into();
2603                let name_node = decl.DeclaredIdentifier();
2604                let name = parser::identifier_text(&name_node).unwrap_or_default();
2605                declared_slots.push(DeclaredSlot {
2606                    name,
2607                    name_node,
2608                    has_rejected_placeholder: false,
2609                });
2610            } else if se.kind() == SyntaxKind::SlotAssignment {
2611                if !Self::assert_experimental_slots(diag, &se, "named slots") {
2612                    continue;
2613                }
2614                let name_node = se.child_node(SyntaxKind::DeclaredIdentifier).unwrap();
2615                let name = parser::identifier_text(&name_node).unwrap_or_default();
2616                if name == "children" {
2617                    diag.push_error(
2618                        format!(
2619                            "The name '{name}' is reserved for the default slot. Use @children instead"
2620                        ),
2621                        &name_node,
2622                    );
2623                }
2624                if !assigned_slots.insert(name.clone()) {
2625                    diag.push_error(format!("Duplicate assignment to slot '{name}'"), &name_node);
2626                }
2627                if r.borrow().forwarded_slots.iter().any(|f| f.target == name) {
2628                    diag.push_error(format!("Duplicate assignment to slot '{name}'"), &name_node);
2629                }
2630                let sub_element_node = se.child_node(SyntaxKind::SubElement).unwrap();
2631                let parent_type = r.borrow().base_type.clone();
2632                match &parent_type {
2633                    ElementType::Component(component)
2634                        if !component
2635                            .declared_slots
2636                            .borrow()
2637                            .iter()
2638                            .any(|slot| slot.name == name) =>
2639                    {
2640                        diag.push_error(
2641                            format!("Unknown slot '{name}' in '{}'", component.id),
2642                            &name_node,
2643                        );
2644                    }
2645                    ElementType::Component(_) => {}
2646                    _ => {
2647                        diag.push_error(
2648                            "Slot assignments can only be used on components".to_string(),
2649                            &se,
2650                        );
2651                    }
2652                }
2653                let element = Element::from_sub_element_node(
2654                    sub_element_node.into(),
2655                    parent_type,
2656                    component_child_insertion_points,
2657                    declared_slots,
2658                    is_legacy_syntax,
2659                    diag,
2660                    tr,
2661                );
2662                element.borrow_mut().slot_target = Some(name);
2663                r.borrow_mut().children.push(element);
2664            }
2665        }
2666
2667        for state in node.States().flat_map(|s| s.State()) {
2668            let condition = state.Expression();
2669            // `when` is a contextual keyword, so it is the state's only
2670            // `Identifier` token: its name is a `DeclaredIdentifier`.
2671            let when = state.child_token(SyntaxKind::Identifier).filter(|t| t.text() == "when");
2672            // Without a condition a state is never selected, so its property
2673            // changes are code that can't run.
2674            #[cfg(feature = "slint-sc")]
2675            if condition.is_none() {
2676                diag.slint_sc_error(
2677                    "A state without a 'when' condition is",
2678                    &state.DeclaredIdentifier(),
2679                );
2680            }
2681            let s = State {
2682                id: parser::identifier_text(&state.DeclaredIdentifier()).unwrap_or_default(),
2683                condition: condition.map(|e| Expression::Uncompiled(e.into())),
2684                property_changes: state
2685                    .StatePropertyChange()
2686                    .filter_map(|s| {
2687                        lookup_property_from_qualified_name_for_state(s.QualifiedName(), &r, diag)
2688                            .map(|(ne, _)| {
2689                                (ne, Expression::Uncompiled(s.BindingExpression().into()), s)
2690                            })
2691                    })
2692                    .collect(),
2693                selection: when.map(|when| ConditionLocation::StateSelection {
2694                    name: state.DeclaredIdentifier().to_source_location(),
2695                    when: when.to_source_location(),
2696                }),
2697            };
2698            for trs in state.Transition() {
2699                #[cfg(feature = "slint-sc")]
2700                diag.slint_sc_error("Transitions are", &trs);
2701                let mut t = Transition::from_node(trs, &r, tr, diag);
2702                t.state_id.clone_from(&s.id);
2703                r.borrow_mut().transitions.push(t);
2704            }
2705            r.borrow_mut().states.push(s);
2706        }
2707
2708        for ts in node.Transitions() {
2709            #[cfg(feature = "slint-sc")]
2710            diag.slint_sc_error("Transitions are", &ts);
2711            if !is_legacy_syntax {
2712                diag.push_error("'transitions' block are no longer supported. Use 'in {...}' and 'out {...}' directly in the state definition".into(), &ts);
2713            }
2714            for trs in ts.Transition() {
2715                let trans = Transition::from_node(trs, &r, tr, diag);
2716                r.borrow_mut().transitions.push(trans);
2717            }
2718        }
2719
2720        if r.borrow().base_type.to_smolstr() == "ListView" {
2721            let mut seen_for = false;
2722            for se in node.children() {
2723                if se.kind() == SyntaxKind::RepeatedElement && !seen_for {
2724                    seen_for = true;
2725                } else if matches!(
2726                    se.kind(),
2727                    SyntaxKind::SubElement
2728                        | SyntaxKind::ConditionalElement
2729                        | SyntaxKind::RepeatedElement
2730                        | SyntaxKind::ChildrenPlaceholder
2731                ) {
2732                    diag.push_error("A ListView can just have a single 'for' as children. Anything else is not supported".into(), &se)
2733                }
2734            }
2735        }
2736
2737        interfaces::validate_self_implement_statements(&r.borrow(), &implemented_interfaces, diag);
2738        interfaces::apply_child_implement_statements(&r, child_implements, diag);
2739
2740        r
2741    }
2742
2743    fn from_sub_element_node(
2744        node: syntax_nodes::SubElement,
2745        parent_type: ElementType,
2746        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
2747        declared_slots: &mut Vec<DeclaredSlot>,
2748        is_in_legacy_component: bool,
2749        diag: &mut BuildDiagnostics,
2750        tr: &TypeRegister,
2751    ) -> ElementRc {
2752        let mut id = parser::identifier_text(&node).unwrap_or_default();
2753        if matches!(id.as_ref(), "parent" | "self" | "root") {
2754            diag.push_error(
2755                format!("'{id}' is a reserved id"),
2756                &node.child_token(SyntaxKind::Identifier).unwrap(),
2757            );
2758            id = SmolStr::default();
2759        }
2760        Element::from_node(
2761            node.Element(),
2762            id,
2763            parent_type,
2764            component_child_insertion_points,
2765            declared_slots,
2766            is_in_legacy_component,
2767            diag,
2768            tr,
2769        )
2770    }
2771
2772    fn assert_experimental_slots(
2773        diagnostics: &mut BuildDiagnostics,
2774        node: &SyntaxNode,
2775        what: &str,
2776    ) -> bool {
2777        if diagnostics.enable_experimental {
2778            return true;
2779        }
2780        diagnostics.push_error(format!("'{what}' is an experimental feature"), node);
2781        false
2782    }
2783
2784    fn sub_element_slot_placeholder_name(
2785        node: &SyntaxNode,
2786        declared_slots: &[DeclaredSlot],
2787    ) -> Option<SmolStr> {
2788        if node.child_token(SyntaxKind::ColonEqual).is_some() {
2789            return None;
2790        }
2791        let element = node.child_node(SyntaxKind::Element)?;
2792        if element.children().any(|c| c.kind() != SyntaxKind::QualifiedName) {
2793            return None;
2794        }
2795        let qualified_name = element.child_node(SyntaxKind::QualifiedName)?;
2796        if qualified_name.child_token(SyntaxKind::Dot).is_some() {
2797            return None;
2798        }
2799        let name = parser::identifier_text(&qualified_name)?;
2800        declared_slots.iter().any(|slot| slot.name == name).then_some(name)
2801    }
2802
2803    fn mark_placeholder_rejected(declared_slots: &mut [DeclaredSlot], name: &str) {
2804        if let Some(slot) = declared_slots.iter_mut().find(|slot| slot.name.as_str() == name) {
2805            slot.has_rejected_placeholder = true;
2806        }
2807    }
2808
2809    fn reject_slot_placeholders(
2810        diagnostics: &mut BuildDiagnostics,
2811        declared_slots: &mut [DeclaredSlot],
2812        insertion_points: BTreeMap<String, ChildrenInsertionPoint>,
2813        context: &str,
2814    ) {
2815        for (name, ChildrenInsertionPoint { node, .. }) in insertion_points {
2816            Self::mark_placeholder_rejected(declared_slots, &name);
2817            diagnostics.push_error(
2818                format!("{} cannot appear in {context}", slot_error_subject(&name)),
2819                &node,
2820            );
2821        }
2822    }
2823
2824    fn register_slot_placeholder(
2825        node: &SyntaxNode,
2826        slot_name: SmolStr,
2827        parent: &ElementRc,
2828        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
2829        diagnostics: &mut BuildDiagnostics,
2830        type_register: &TypeRegister,
2831    ) {
2832        Self::assert_experimental_slots(diagnostics, node, "named slots");
2833        if let Some(existing) = component_child_insertion_points.get(slot_name.as_str()) {
2834            if matches!(existing.node, ChildInsertionPointNode::SlotForwarding(_)) {
2835                diagnostics.push_error(
2836                    format!(
2837                        "The slot '{slot_name}' cannot be forwarded and used as a placeholder in the same component"
2838                    ),
2839                    node,
2840                );
2841            } else {
2842                diagnostics.push_error(
2843                    format!(
2844                        "{} can only appear once in an element",
2845                        slot_error_subject(&slot_name)
2846                    ),
2847                    node,
2848                );
2849            }
2850            return;
2851        }
2852        if type_register.lookup_element(slot_name.as_str()).is_ok() {
2853            diagnostics.push_warning(
2854                format!(
2855                    "{} shadows an element type of the same name. This element is a slot placeholder, not an instance of '{slot_name}'",
2856                    slot_error_subject(&slot_name)
2857                ),
2858                node,
2859            );
2860        }
2861        let insertion_index = parent.borrow().children.len();
2862        component_child_insertion_points.insert(
2863            slot_name.to_string(),
2864            ChildrenInsertionPoint {
2865                parent: parent.clone(),
2866                insertion_index,
2867                node: ChildInsertionPointNode::SlotPlaceholder(node.clone().into()),
2868            },
2869        );
2870    }
2871
2872    fn from_repeated_node(
2873        node: syntax_nodes::RepeatedElement,
2874        parent: &ElementRc,
2875        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
2876        declared_slots: &mut Vec<DeclaredSlot>,
2877        is_in_legacy_component: bool,
2878        diag: &mut BuildDiagnostics,
2879        tr: &TypeRegister,
2880    ) -> ElementRc {
2881        #[cfg(feature = "slint-sc")]
2882        diag.slint_sc_error("Repeated elements (for-in) are", &node);
2883        let e = Element::from_sub_element_node(
2884            node.SubElement(),
2885            parent.borrow().base_type.clone(),
2886            component_child_insertion_points,
2887            declared_slots,
2888            is_in_legacy_component,
2889            diag,
2890            tr,
2891        );
2892        let parent_is_listview = {
2893            let parent = parent.borrow();
2894            parent.base_type.to_string() == "ListView"
2895                // Custom "ListView" is OK, but it must have these properties
2896                && [
2897                    "content-y",
2898                    "content-height",
2899                    "content-width",
2900                    "visible-height",
2901                    "visible-width",
2902                ]
2903                .iter()
2904                .all(|p| parent.lookup_property(p, PropertyLookupMode::InternalName).property_type == Type::LogicalLength)
2905        };
2906        let is_listview = if parent_is_listview
2907            && let Some(geometry_props) = e.borrow().geometry_props.as_ref()
2908        {
2909            let parent_elem = parent.borrow();
2910            // Check if content-width and content-height are explicitly set by the user,
2911            // either under their own name or through the deprecated viewport-* aliases.
2912            let (content_width_is_explicitly_set, content_height_is_explicitly_set) = {
2913                let has_binding = |name| parent_elem.binding(name).is_some_and(|b| b.has_binding());
2914                (
2915                    has_binding("content-width") || has_binding("viewport-width"),
2916                    has_binding("content-height") || has_binding("viewport-height"),
2917                )
2918            };
2919            drop(parent_elem); // Drop the borrow before creating NamedReference
2920
2921            let lvi = ListViewInfo {
2922                content_y: NamedReference::new(parent, SmolStr::new_static("content-y")),
2923                content_height: (!content_height_is_explicitly_set)
2924                    .then(|| NamedReference::new(parent, SmolStr::new_static("content-height"))),
2925                content_width: (!content_width_is_explicitly_set)
2926                    .then(|| NamedReference::new(parent, SmolStr::new_static("content-width"))),
2927                listview_height: NamedReference::new(parent, SmolStr::new_static("visible-height")),
2928                listview_width: NamedReference::new(parent, SmolStr::new_static("visible-width")),
2929            };
2930            // these properties are set by the ListView layouting code
2931            if let Some(content_height) = &lvi.content_height {
2932                content_height.mark_as_set();
2933            }
2934            if let Some(content_width) = &lvi.content_width {
2935                content_width.mark_as_set();
2936            }
2937            geometry_props.y.mark_as_set();
2938            Some(lvi)
2939        } else {
2940            None
2941        };
2942        let rei = RepeatedElementInfo {
2943            model: Expression::Uncompiled(node.Expression().into()),
2944            model_data_id: node
2945                .DeclaredIdentifier()
2946                .and_then(|n| parser::identifier_text(&n))
2947                .unwrap_or_default(),
2948            index_id: node
2949                .RepeatedIndex()
2950                .and_then(|r| parser::identifier_text(&r))
2951                .unwrap_or_default(),
2952            is_conditional_element: false,
2953            is_listview,
2954        };
2955        e.borrow_mut().repeated = Some(rei);
2956        e
2957    }
2958
2959    fn from_conditional_node(
2960        node: syntax_nodes::ConditionalElement,
2961        parent_type: ElementType,
2962        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
2963        declared_slots: &mut Vec<DeclaredSlot>,
2964        is_in_legacy_component: bool,
2965        diag: &mut BuildDiagnostics,
2966        tr: &TypeRegister,
2967    ) -> ElementRc {
2968        #[cfg(feature = "slint-sc")]
2969        diag.slint_sc_error("Conditional elements (if) are", &node);
2970        let rei = RepeatedElementInfo {
2971            model: Expression::Uncompiled(node.Expression().into()),
2972            model_data_id: SmolStr::default(),
2973            index_id: SmolStr::default(),
2974            is_conditional_element: true,
2975            is_listview: None,
2976        };
2977        let e = Element::from_sub_element_node(
2978            node.SubElement(),
2979            parent_type,
2980            component_child_insertion_points,
2981            declared_slots,
2982            is_in_legacy_component,
2983            diag,
2984            tr,
2985        );
2986        e.borrow_mut().repeated = Some(rei);
2987        e
2988    }
2989
2990    fn from_match_node(
2991        node: syntax_nodes::MatchElement,
2992        parent_type: ElementType,
2993        component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
2994        declared_slots: &mut Vec<DeclaredSlot>,
2995        is_in_legacy_component: bool,
2996        diag: &mut BuildDiagnostics,
2997        tr: &TypeRegister,
2998    ) -> MatchElementInfo {
2999        if !diag.enable_experimental {
3000            diag.push_error("match elements are an experimental feature".into(), &node);
3001        }
3002        if node.MatchCase().next().is_none() && node.WildcardMatchCase().is_none() {
3003            diag.push_error("Expected at least one case".into(), &node);
3004        }
3005        if let Some(wildcard) = node.WildcardMatchCase()
3006            && node.MatchCase().next().is_none()
3007        {
3008            diag.push_warning(
3009                "Unnecessary match statement always matches the '*' case".into(),
3010                &wildcard,
3011            );
3012        }
3013        let mut element_of = |sub_element| {
3014            Element::from_sub_element_node(
3015                sub_element,
3016                parent_type.clone(),
3017                component_child_insertion_points,
3018                declared_slots,
3019                is_in_legacy_component,
3020                diag,
3021                tr,
3022            )
3023        };
3024        // A case without a sub element is an empty case that shows nothing
3025        let cases = node
3026            .MatchCase()
3027            .map(|case| {
3028                let node = case.Expression();
3029                MatchCaseInfo {
3030                    value: Expression::Uncompiled(node.clone().into()),
3031                    node,
3032                    element: case.SubElement().map(&mut element_of),
3033                }
3034            })
3035            .collect();
3036        let wildcard = match node.WildcardMatchCase() {
3037            None => WildcardMatchCaseInfo::None,
3038            Some(w) => match w.SubElement().map(&mut element_of) {
3039                None => WildcardMatchCaseInfo::Empty,
3040                Some(element) => WildcardMatchCaseInfo::Element(element),
3041            },
3042        };
3043        MatchElementInfo {
3044            subject: Expression::Uncompiled(node.Expression().into()),
3045            node,
3046            cases,
3047            wildcard,
3048        }
3049    }
3050
3051    /// Whether the member is declared in the source, on this element or on the
3052    /// root element of a component it inherits from, rather than coming from a
3053    /// builtin element. Follows the same chain as [`Self::lookup_property`].
3054    #[cfg(feature = "slint-sc")]
3055    pub fn is_user_declared_member(&self, name: &str) -> bool {
3056        match self.declaration(name) {
3057            Some((_, declaration)) => declaration.node.is_some(),
3058            None => match &self.base_type {
3059                ElementType::Component(c) => c.root_element.borrow().is_user_declared_member(name),
3060                _ => false,
3061            },
3062        }
3063    }
3064
3065    /// Resolve `name` in the given [`PropertyLookupMode`], following aliases; `Type::Invalid` if absent.
3066    /// For a shadowing member the result's `internal_name` carries its storage key.
3067    pub fn lookup_property<'a>(
3068        &self,
3069        name: &'a str,
3070        mode: PropertyLookupMode,
3071    ) -> PropertyLookupResult<'a> {
3072        let declaration = match mode {
3073            PropertyLookupMode::InternalName => self.property_declarations.get_key_value(name),
3074            PropertyLookupMode::ComponentLocal | PropertyLookupMode::FromOutside => {
3075                self.declaration(name)
3076            }
3077        };
3078        if let Some((internal_name, decl)) = declaration {
3079            if mode == PropertyLookupMode::FromOutside && decl.is_private_shadow() {
3080                return from_base(
3081                    self.base_type.lookup_property(name, PropertyLookupMode::FromOutside),
3082                );
3083            }
3084            let mut r = self.lookup_result_for_declaration(name.into(), decl);
3085            if internal_name != name {
3086                r.internal_name = Some(internal_name.clone());
3087            }
3088            return r;
3089        }
3090        // A base component's private members are invisible from here.
3091        let base_mode = match mode {
3092            PropertyLookupMode::InternalName => PropertyLookupMode::InternalName,
3093            _ => PropertyLookupMode::FromOutside,
3094        };
3095        from_base(self.base_type.lookup_property(name, base_mode))
3096    }
3097
3098    /// The declaration for a member written as `name` in `.slint` source, with its internal key.
3099    /// A mangled key is private: it is reachable only through `shadowing_members`, never as a source
3100    /// name.
3101    pub fn declaration(&self, name: &str) -> Option<(&SmolStr, &PropertyDeclaration)> {
3102        if let Some(internal_name) = self.shadowing_members.get(name) {
3103            return self.property_declarations.get_key_value(internal_name);
3104        }
3105        self.property_declarations.get_key_value(name).filter(|(_, d)| d.shadowed_name.is_none())
3106    }
3107
3108    /// Source names of shadowing declarations that are visible from outside the component, so they
3109    /// hide the inherited member of the same name. A private shadow is excluded: it stays transparent
3110    /// from outside, leaving the inherited member reachable there.
3111    pub fn visible_shadowing_members(&self) -> impl Iterator<Item = &SmolStr> {
3112        self.shadowing_members.iter().filter_map(|(source, internal)| {
3113            self.property_declarations
3114                .get(internal)
3115                .filter(|d| !d.is_private_shadow())
3116                .map(|_| source)
3117        })
3118    }
3119
3120    /// How a declaration of `name` relates to a member of the same name already reachable here.
3121    /// See [`MemberDeclaration`].
3122    fn member_declaration(&self, name: &SmolStr) -> MemberDeclaration {
3123        // A prior shadow's mangled key is private, so this declaration may take the source name and
3124        // get a fresh key of its own.
3125        if self.property_declarations.get(name.as_str()).is_some_and(|d| d.shadowed_name.is_some())
3126        {
3127            return MemberDeclaration::Shadow {
3128                internal_name: self.unique_member_name(name),
3129                warning: None,
3130            };
3131        }
3132        let existing = self.lookup_property(name, PropertyLookupMode::ComponentLocal);
3133        if !existing.is_valid() {
3134            return MemberDeclaration::New;
3135        }
3136        if existing.is_local_to_component {
3137            return MemberDeclaration::Conflict {
3138                existing_type: existing.property_type,
3139                declared_in: None,
3140            };
3141        }
3142        let declared_in = self.declaring_base_component(name);
3143        // A private member of a base component isn't visible here, so shadowing it can't
3144        // surprise anyone. Anything else has to be opted into by the base declaration.
3145        let private =
3146            declared_in.is_some() && existing.property_visibility == PropertyVisibility::Private;
3147        if !private && !existing.is_shadowable {
3148            return MemberDeclaration::Conflict {
3149                existing_type: existing.property_type,
3150                declared_in,
3151            };
3152        }
3153        let origin = if declared_in.is_some() { "inherited" } else { "builtin" };
3154        MemberDeclaration::Shadow {
3155            internal_name: self.unique_member_name(name),
3156            warning: (!private).then(|| {
3157                let kind = match existing.property_type {
3158                    Type::Callback { .. } => "callback",
3159                    Type::Function { .. } => "function",
3160                    _ => "property",
3161                };
3162                format!("'{name}' shadows the {origin} {kind} of the same name")
3163            }),
3164        }
3165    }
3166
3167    /// A name derived from `base` that no member reachable from this element uses.
3168    pub fn unique_member_name(&self, base: &str) -> SmolStr {
3169        (1..)
3170            .map(|counter| format_smolstr!("{base}-{counter}"))
3171            .find(|n| !self.lookup_property(n, PropertyLookupMode::InternalName).is_valid())
3172            .unwrap()
3173    }
3174
3175    /// The base component whose root element declares `name`, if any. `None` when the member comes
3176    /// from a builtin element or doesn't exist.
3177    fn declaring_base_component(&self, name: &str) -> Option<Rc<Component>> {
3178        let mut base = self.base_type.clone();
3179        loop {
3180            let ElementType::Component(c) = base else { return None };
3181            let declares = {
3182                let root = c.root_element.borrow();
3183                root.shadowing_members.contains_key(name)
3184                    || root.property_declarations.contains_key(name)
3185            };
3186            if declares {
3187                return Some(c);
3188            }
3189            base = c.root_element.borrow().base_type.clone();
3190        }
3191    }
3192
3193    fn lookup_result_for_declaration<'a>(
3194        &self,
3195        resolved_name: std::borrow::Cow<'a, str>,
3196        p: &PropertyDeclaration,
3197    ) -> PropertyLookupResult<'a> {
3198        PropertyLookupResult {
3199            resolved_name,
3200            property_type: p.property_type.clone(),
3201            property_visibility: p.visibility,
3202            declared_pure: p.pure,
3203            is_local_to_component: true,
3204            is_in_direct_base: false,
3205            is_shadowable: p.shadowable,
3206            builtin_function: None,
3207            is_slint_sc: true,
3208            deprecated: p.deprecated.clone(),
3209            internal_name: None,
3210        }
3211    }
3212
3213    fn parse_bindings(
3214        &mut self,
3215        bindings: impl Iterator<Item = (crate::parser::SyntaxToken, SyntaxNode)>,
3216        is_in_legacy_component: bool,
3217        diag: &mut BuildDiagnostics,
3218    ) {
3219        for (name_token, b) in bindings {
3220            let unresolved_name = crate::parser::normalize_identifier(name_token.text());
3221            let lookup_result =
3222                self.lookup_property(&unresolved_name, PropertyLookupMode::ComponentLocal);
3223            #[cfg(feature = "slint-sc")]
3224            if b.kind() == SyntaxKind::TwoWayBinding {
3225                diag.slint_sc_error("Two-way bindings are", &b);
3226            } else {
3227                lookup_result.check_slint_sc(&unresolved_name, &name_token, diag);
3228            }
3229            if !lookup_result.property_type.is_property_type() {
3230                match lookup_result.property_type {
3231                        Type::Invalid => {
3232                            if self.base_type != ElementType::Error {
3233                                let msg = if let Some(suggestion) = css_property_suggestion(&unresolved_name, &self.base_type) {
3234                                    suggestion
3235                                } else if self.base_type.to_smolstr() == "Empty" {
3236                                    format!( "Unknown property {unresolved_name}")
3237                                } else {
3238                                    format!( "Unknown property {unresolved_name} in {}", self.base_type)
3239                                };
3240                                diag.push_error(msg, &name_token);
3241                            }
3242                        }
3243                        Type::Callback { .. } => {
3244                            diag.push_error(format!("'{unresolved_name}' is a callback. Use `=>` to connect"),
3245                            &name_token)
3246                        }
3247                        _ => diag.push_error(format!(
3248                            "Cannot assign to {} in {} because it does not have a valid property type",
3249                            unresolved_name, self.base_type,
3250                        ),
3251                        &name_token),
3252                    }
3253            } else if !lookup_result.is_local_to_component
3254                && (lookup_result.property_visibility == PropertyVisibility::Private
3255                    || lookup_result.property_visibility == PropertyVisibility::Output)
3256            {
3257                if is_in_legacy_component
3258                    && lookup_result.property_visibility == PropertyVisibility::Output
3259                {
3260                    diag.push_warning(
3261                        format!(
3262                            "Assigning to '{}' property '{unresolved_name}' is deprecated",
3263                            PropertyVisibility::Output
3264                        ),
3265                        &name_token,
3266                    );
3267                } else {
3268                    diag.push_error(
3269                        format!(
3270                            "Cannot assign to '{}' property '{}'",
3271                            lookup_result.property_visibility, unresolved_name
3272                        ),
3273                        &name_token,
3274                    );
3275                }
3276            }
3277
3278            if *lookup_result.resolved_name != *unresolved_name {
3279                diag.push_property_deprecation_warning(
3280                    &unresolved_name,
3281                    &lookup_result.resolved_name,
3282                    &name_token,
3283                );
3284            } else if let Some(message) =
3285                lookup_result.deprecated.as_ref().filter(|_| !lookup_result.is_local_to_component)
3286            {
3287                diag.push_property_deprecation_warning_with_message(
3288                    &unresolved_name,
3289                    message,
3290                    &name_token,
3291                );
3292            }
3293
3294            match self.bindings.0.entry(lookup_result.internal_or_resolved_name()) {
3295                Entry::Occupied(_) => {
3296                    diag.push_error("Duplicated property binding".into(), &name_token);
3297                }
3298                Entry::Vacant(entry) => {
3299                    entry.insert(BindingExpression::new_uncompiled(b).into());
3300                }
3301            };
3302        }
3303    }
3304
3305    /// Return the node declaring `name` in this element or one of its bases, if there is one.
3306    pub fn property_declaration_node(&self, name: &str) -> Option<SyntaxNode> {
3307        self.property_declarations
3308            .get(name)
3309            .and_then(|declaration| declaration.node.clone())
3310            .or_else(|| self.base_type.property_declaration_node(name))
3311    }
3312
3313    fn slot_forwarding_expr_identifier(expression: &SyntaxNode) -> Option<SmolStr> {
3314        if expression.kind() != SyntaxKind::Expression {
3315            return None;
3316        }
3317
3318        let mut expr_children = expression.children();
3319        let qualified_name = expr_children.find(|n| n.kind() == SyntaxKind::QualifiedName)?;
3320        if expr_children.next().is_some() {
3321            return None;
3322        }
3323
3324        let mut identifiers = qualified_name
3325            .children_with_tokens()
3326            .filter(|n| n.kind() == SyntaxKind::Identifier)
3327            .filter_map(|n| n.into_token());
3328        let identifier = identifiers.next()?;
3329        if identifiers.next().is_some() {
3330            return None;
3331        }
3332
3333        Some(crate::parser::normalize_identifier(identifier.text()))
3334    }
3335
3336    /// Return the alias node of a `callback foo <=> ...;` declaration, if `name` is one.
3337    ///
3338    /// This lives on the callback declaration itself, which is where the alias of a
3339    /// global callback that also has a handler ends up (the handler takes the binding
3340    /// expression slot).
3341    pub fn callback_alias_declaration_node(
3342        &self,
3343        name: &str,
3344    ) -> Option<syntax_nodes::TwoWayBinding> {
3345        self.property_declarations
3346            .get(name)
3347            .and_then(|d| d.node.clone())
3348            .and_then(syntax_nodes::CallbackDeclaration::new)
3349            .and_then(|cb| cb.TwoWayBinding())
3350    }
3351
3352    /// Return the two-way-binding syntax node of a `<=>` alias for the given property, if any.
3353    ///
3354    /// Usually the alias is the binding's own (uncompiled) expression. But a global
3355    /// callback may both alias another global's callback (`callback foo <=> Other.foo;`)
3356    /// and provide a handler (`foo => { ... }`): the handler then occupies the binding
3357    /// expression slot, so the alias node lives on the callback declaration instead.
3358    pub fn two_way_binding_node(&self, name: &str) -> Option<syntax_nodes::TwoWayBinding> {
3359        if let Some(binding) = self.bindings.0.get(name)
3360            && let Ok(b) = binding.try_borrow()
3361            && let Expression::Uncompiled(node) = b.value_expression()
3362            && let Some(twb) = syntax_nodes::TwoWayBinding::new(node.clone())
3363        {
3364            return Some(twb);
3365        }
3366        self.callback_alias_declaration_node(name)
3367    }
3368
3369    pub fn native_class(&self) -> Option<Arc<NativeClass>> {
3370        let mut base_type = self.base_type.clone();
3371        loop {
3372            match &base_type {
3373                ElementType::Component(component) => {
3374                    base_type = component.root_element.clone().borrow().base_type.clone();
3375                }
3376                ElementType::Builtin(builtin) => break Some(builtin.native_class.clone()),
3377                ElementType::Native(native) => break Some(native.clone()),
3378                _ => break None,
3379            }
3380        }
3381    }
3382
3383    pub fn builtin_type(&self) -> Option<Rc<BuiltinElement>> {
3384        let mut base_type = self.base_type.clone();
3385        loop {
3386            match &base_type {
3387                ElementType::Component(component) => {
3388                    base_type = component.root_element.clone().borrow().base_type.clone();
3389                }
3390                ElementType::Builtin(builtin) => break Some(builtin.clone()),
3391                _ => break None,
3392            }
3393        }
3394    }
3395
3396    /// The property holding the layout info for that orientation. For the
3397    /// horizontal info of a column flex whose height is settled by the source
3398    /// (see [`Self::height_is_literal`]), that is `layout_info_h_at_own_height`;
3399    /// never on the root of a component with instances, which decide for themselves.
3400    pub(crate) fn effective_layout_info_prop(
3401        &self,
3402        orientation: Orientation,
3403    ) -> Option<&NamedReference> {
3404        let prop = self.layout_info_prop.as_ref()?;
3405        match orientation {
3406            Orientation::Horizontal => Some(
3407                self.layout_info_h_at_own_height
3408                    .as_ref()
3409                    .filter(|_| self.height_is_literal)
3410                    .unwrap_or(&prop.0),
3411            ),
3412            Orientation::Vertical => Some(&prop.1),
3413        }
3414    }
3415
3416    /// Whether this element is a *builtin* whose vertical layout info
3417    /// depends on its width. Returns `false` for user components — even
3418    /// ones whose own bindings derive height from width (e.g.
3419    /// `component Foo { height: self.width; }`); those don't carry the
3420    /// information needed to detect the dependency here. The synthesis
3421    /// pass catches user components by other means (descendant
3422    /// height-for-width + `layoutinfo-v-with-constraint` propagation).
3423    pub fn is_builtin_height_for_width(&self) -> bool {
3424        let Some(builtin) = self.builtin_type() else { return false };
3425        match builtin.name.as_str() {
3426            // Conservatively treat any wrap binding (including a literal
3427            // `no-wrap`) as height-for-width.
3428            "Text" | "TextInput" => self.is_binding_set("wrap", false),
3429            // An Image is height-for-width only while its height comes from its
3430            // width; with an explicit height it is fixed, so it is not.
3431            "Image" | "ClippedImage" => !self.is_binding_set("height", true),
3432            // Markdown text always wraps to fill the given width.
3433            "StyledText" => true,
3434            _ => false,
3435        }
3436    }
3437
3438    /// Returns the `layoutinfo-v-with-constraint` NamedReference reachable
3439    /// from `self`, looking through the base-type chain. The NR points to
3440    /// the element actually carrying the binding.
3441    pub fn inherited_layout_info_v_with_constraint(&self) -> Option<NamedReference> {
3442        if let Some(nr) = &self.layout_info_v_with_constraint {
3443            return Some(nr.clone());
3444        }
3445        let mut base = self.base_type.clone();
3446        while let ElementType::Component(base_comp) = base {
3447            let root = base_comp.root_element.borrow();
3448            if let Some(nr) = &root.layout_info_v_with_constraint {
3449                return Some(nr.clone());
3450            }
3451            base = root.base_type.clone();
3452        }
3453        None
3454    }
3455
3456    /// Whether [`Self::inherited_layout_info_v_with_constraint`] would return
3457    /// `Some`, without cloning the `NamedReference`.
3458    pub fn has_inherited_layout_info_v_with_constraint(&self) -> bool {
3459        if self.layout_info_v_with_constraint.is_some() {
3460            return true;
3461        }
3462        let mut base = self.base_type.clone();
3463        while let ElementType::Component(base_comp) = base {
3464            let root = base_comp.root_element.borrow();
3465            if root.layout_info_v_with_constraint.is_some() {
3466                return true;
3467            }
3468            base = root.base_type.clone();
3469        }
3470        false
3471    }
3472
3473    /// Whether this element's `layoutinfo-{orientation}` already incorporates its
3474    /// own explicit min/max/preferred/stretch constraints. True for elements with
3475    /// a `layoutinfo-*` property (layouts, sub-components) or an inherited
3476    /// `layoutinfo-v-with-constraint` function (a component forwarding a
3477    /// height-for-width layout).
3478    ///
3479    /// A parent layout must then NOT re-apply the cell's explicit constraints on
3480    /// top of the measured info — they are already included, and re-reading them
3481    /// unconstrained can reintroduce a height-for-width binding loop. Only native
3482    /// items (no `layoutinfo-*`) need their constraints applied separately.
3483    pub fn layout_info_includes_own_constraints(&self, orientation: Orientation) -> bool {
3484        self.effective_layout_info_prop(orientation).is_some()
3485            || (orientation == Orientation::Vertical
3486                && self.has_inherited_layout_info_v_with_constraint())
3487    }
3488
3489    /// Returns the element's name as specified in the markup, not normalized.
3490    pub fn original_name(&self) -> SmolStr {
3491        self.debug
3492            .first()
3493            .and_then(|n| n.node.child_token(parser::SyntaxKind::Identifier))
3494            .map(|n| n.to_smolstr())
3495            .unwrap_or_else(|| self.id.clone())
3496    }
3497
3498    /// Whether the children of this element are dynamically sorted by their z value
3499    pub fn has_dynamic_z_order(&self) -> bool {
3500        self.children.iter().any(|c| c.borrow().z_order.is_some())
3501    }
3502
3503    /// Return true if the binding is set, either on this element or in a base
3504    ///
3505    /// If `need_explicit` is true, then only consider binding set in the code, not the ones set
3506    /// by the compiler later.
3507    ///
3508    /// Synthetic debug hooks (materialized for unbound properties) are never considered set
3509    /// (`has_binding` treats them as "no expression").
3510    pub fn is_binding_set(self: &Element, property_name: &str, need_explicit: bool) -> bool {
3511        self.any_in_inheritance_chain(|element| {
3512            element.bindings.0.get(property_name).is_some_and(|binding| {
3513                let binding = binding.borrow();
3514                binding.has_binding() && (!need_explicit || binding.priority > 0)
3515            })
3516        })
3517    }
3518
3519    /// The layout info property of the base component's root that an instance
3520    /// reads. For the horizontal one, that is `layoutinfo-h-at-own-height` when
3521    /// `height_settled`, the instance's [`Self::height_is_literal`], which the
3522    /// root itself cannot know.
3523    pub(crate) fn base_layout_info_prop(
3524        &self,
3525        orientation: Orientation,
3526        height_settled: bool,
3527    ) -> Option<NamedReference> {
3528        let ElementType::Component(base) = &self.base_type else { return None };
3529        let root = base.root_element.borrow();
3530        root.layout_info_h_at_own_height
3531            .clone()
3532            .filter(|_| orientation == Orientation::Horizontal && height_settled)
3533            .or_else(|| root.effective_layout_info_prop(orientation).cloned())
3534    }
3535
3536    /// Compute [`Self::height_is_literal`] for `elem`: whether its effective
3537    /// `height` binding is a length literal that is not a percentage.
3538    ///
3539    /// The effective binding is the one [`crate::layout::find_binding`] finds,
3540    /// the same walk that sets `LayoutConstraints::fixed_height`, so the two
3541    /// cannot disagree about which binding a height has. Why only a literal
3542    /// counts is in `docs/development/layout-system.md`, under "Width down,
3543    /// height up".
3544    pub(crate) fn compute_height_is_literal(elem: &ElementRc) -> bool {
3545        // The root of a component that is used elsewhere cannot answer for
3546        // itself: every instance may override the height, and each is asked
3547        // separately (see [`Element::base_layout_info_prop`]).
3548        let overridable_root = elem.borrow().enclosing_component.upgrade().is_some_and(|c| {
3549            Rc::ptr_eq(&c.root_element, elem)
3550                && c.used.get()
3551                && c.parent_element.borrow().upgrade().is_none()
3552        });
3553        if overridable_root {
3554            return false;
3555        }
3556        crate::layout::find_binding(elem, "height", |b, _, _| {
3557            matches!(b.value_expression(), Expression::NumberLiteral(_, unit) if *unit != Unit::Percent)
3558        })
3559        .unwrap_or(false)
3560    }
3561
3562    /// Returns true if the property is set by a binding or an assignment expression
3563    ///
3564    /// Synthetic debug hooks (materialized for unbound properties) are not considered set.
3565    pub fn is_property_set(self: &Element, property_name: &str) -> bool {
3566        self.any_in_inheritance_chain(|element| {
3567            element
3568                .bindings
3569                .0
3570                .get(property_name)
3571                .is_some_and(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
3572                || element
3573                    .property_analysis
3574                    .borrow()
3575                    .get(property_name)
3576                    .is_some_and(|analysis| analysis.is_set || analysis.is_linked)
3577        })
3578    }
3579
3580    pub(crate) fn is_property_target_of_two_way_binding(&self, property_name: &str) -> bool {
3581        self.any_in_inheritance_chain(|element| {
3582            element
3583                .property_analysis
3584                .borrow()
3585                .get(property_name)
3586                .is_some_and(|analysis| analysis.is_linked)
3587        })
3588    }
3589
3590    /// Whether `predicate` holds for this element or the root element of a component it derives from
3591    pub fn any_in_inheritance_chain(&self, predicate: impl Fn(&Element) -> bool + Copy) -> bool {
3592        predicate(self)
3593            || matches!(
3594                &self.base_type,
3595                ElementType::Component(base)
3596                    if base.root_element.borrow().any_in_inheritance_chain(predicate)
3597            )
3598    }
3599
3600    /// The binding for `property_name`, if one exists and is not a synthetic debug hook.
3601    ///
3602    /// This is the hook-aware replacement for `self.bindings.get(..)`: a synthetic debug hook
3603    /// is a materialized placeholder for an *unbound* property and must read as "no binding".
3604    /// Use this instead of the raw map whenever the question is "did anything bind this
3605    /// property" or "what is this property's binding".
3606    pub fn binding(&self, property_name: &str) -> Option<Ref<'_, BindingExpression>> {
3607        self.bindings
3608            .0
3609            .get(property_name)
3610            .filter(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
3611            .map(|binding| binding.borrow())
3612    }
3613
3614    /// Same as [`Self::binding`], but returns a mutable reference to the binding.
3615    pub fn binding_mut(&self, property_name: &str) -> Option<RefMut<'_, BindingExpression>> {
3616        self.bindings
3617            .0
3618            .get(property_name)
3619            .filter(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
3620            .map(|binding| binding.borrow_mut())
3621    }
3622
3623    /// Iterate over the bindings that are not synthetic debug hooks.
3624    ///
3625    /// The hook-aware replacement for iterating `self.bindings` directly when enumerating the
3626    /// properties that are actually set on this element.
3627    pub fn real_bindings(&self) -> impl Iterator<Item = (&SmolStr, &RefCell<BindingExpression>)> {
3628        self.bindings
3629            .0
3630            .iter()
3631            .filter(|(_, binding)| !binding.borrow().expression.is_synthetic_debug_hook())
3632    }
3633
3634    /// Iterate over every binding entry, including synthetic debug hooks.
3635    ///
3636    /// The counterpart to [`Self::real_bindings`]. Use only where synthetic hooks must be lowered
3637    /// or emitted (codegen, LLR, native-class selection); prefer `real_bindings()` everywhere else.
3638    pub fn bindings_including_synthetic(
3639        &self,
3640    ) -> impl Iterator<Item = (&SmolStr, &RefCell<BindingExpression>)> {
3641        self.bindings.0.iter()
3642    }
3643
3644    /// The raw binding cell for `property_name`, including a synthetic debug hook.
3645    ///
3646    /// Returns the `&RefCell` rather than a borrow guard, so callers that need to borrow, drop,
3647    /// and re-borrow within one scope (reentrant binding analysis) or use `try_borrow` can do so.
3648    /// Does not filter synthetic hooks — prefer [`Self::binding`] unless synthetic hooks matter.
3649    pub fn binding_cell_including_synthetic(
3650        &self,
3651        property_name: &str,
3652    ) -> Option<&RefCell<BindingExpression>> {
3653        self.bindings.0.get(property_name)
3654    }
3655
3656    /// Set the property `property_name` of this Element only if it was not set.
3657    /// the `expression_fn` will only be called if it isn't set.
3658    ///
3659    /// If a synthetic debug hook exists for this property, the hook's inner expression is
3660    /// replaced with the new value (keeping the wrapper and id) and the hook is marked
3661    /// non-synthetic — so the property becomes live-editable at its real computed value.
3662    ///
3663    /// Returns true if the binding was changed.
3664    pub fn set_binding_if_not_set(
3665        &mut self,
3666        property_name: SmolStr,
3667        expression_fn: impl FnOnce() -> Expression,
3668    ) -> bool {
3669        if self.is_binding_set(&property_name, false) {
3670            return false;
3671        }
3672
3673        match self.bindings.0.entry(property_name) {
3674            Entry::Vacant(vacant_entry) => {
3675                let mut binding: BindingExpression = expression_fn().into();
3676                binding.priority = i32::MAX;
3677                vacant_entry.insert(binding.into());
3678            }
3679            Entry::Occupied(mut existing_entry) => {
3680                let inner = existing_entry.get_mut().get_mut();
3681                let mut binding: BindingExpression = expression_fn().into();
3682                binding.priority = i32::MAX;
3683                // merge_with takes care of overwriting synthetic debug hooks.
3684                inner.merge_with(&binding);
3685            }
3686        };
3687        true
3688    }
3689
3690    /// Unconditionally set the property `property_name` to `new_binding`, but handle a synthetic
3691    /// debug hook that already occupies the slot specially: instead of replacing it, upgrade it
3692    /// in-place (replace its inner expression, clear `synthetic`, keep the wrapper+id so the
3693    /// property stays live-editable).
3694    ///
3695    /// Returns the old `BindingExpression` if a *real* (non-synthetic) binding was displaced so
3696    /// the caller can report a conflict.  Returns `None` if the slot was empty or held only a
3697    /// synthetic hook (no conflict).
3698    ///
3699    /// This is intended for passes like `lower_layout` that must force-set a property and need to
3700    /// distinguish a genuine conflict from a synthetic placeholder.
3701    pub fn set_binding(
3702        &mut self,
3703        property_name: SmolStr,
3704        mut new_binding: BindingExpression,
3705    ) -> Option<BindingExpression> {
3706        match self.bindings.0.entry(property_name) {
3707            Entry::Vacant(v) => {
3708                v.insert(RefCell::new(new_binding));
3709                None
3710            }
3711            Entry::Occupied(mut e) => {
3712                let existing = e.get_mut().get_mut();
3713                if let expression_tree::Expression::DebugHook { expression: _, synthetic, id } =
3714                    &mut existing.expression
3715                    && *synthetic
3716                {
3717                    // Adopt the previous synthetic debug hook into the new binding, which is now no
3718                    // longer synthetic.
3719                    let new_debug_hook = expression_tree::Expression::DebugHook {
3720                        expression: Box::new(new_binding.expression),
3721                        id: id.clone(),
3722                        synthetic: false,
3723                    };
3724                    new_binding.expression = new_debug_hook;
3725                    *existing = new_binding;
3726                    // previously the binding was only a synthetic debug hook - don't report a
3727                    // conflict
3728                    return None;
3729                }
3730                // Real (non-synthetic) binding exists: replace it and report the conflict.
3731                Some(std::mem::replace(e.get_mut().get_mut(), new_binding))
3732            }
3733        }
3734    }
3735
3736    /// Remove the binding for `property_name` and return it.
3737    ///
3738    /// The map entry is removed whether it held a real binding or only a synthetic debug hook,
3739    /// but the result is `Some` only for a real binding — a synthetic-only slot reads as `None`,
3740    /// matching "nothing was ever bound here". Dropping a leftover synthetic hook is safe for the
3741    /// lowering passes that consume, rename, or delete a property this way.
3742    pub fn take_binding(&mut self, property_name: &str) -> Option<BindingExpression> {
3743        self.take_binding_including_synthetic(property_name)
3744            .filter(|binding| !binding.expression.is_synthetic_debug_hook())
3745    }
3746
3747    /// Remove the binding for `property_name` and return it (including synthetic debug hooks).
3748    pub fn take_binding_including_synthetic(
3749        &mut self,
3750        property_name: &str,
3751    ) -> Option<BindingExpression> {
3752        self.bindings.0.remove(property_name).map(RefCell::into_inner)
3753    }
3754
3755    /// Remove and return the whole binding map, including synthetic debug hooks.
3756    ///
3757    /// For bulk transfers that move an element's bindings wholesale.
3758    pub(crate) fn take_bindings_including_synthetic(&mut self) -> BindingsMap {
3759        std::mem::take(&mut self.bindings.0)
3760    }
3761
3762    /// Add the given binding entries, including synthetic debug hooks, to this element.
3763    ///
3764    /// For bulk transfers; like `BTreeMap::extend`, an entry with the same name is overwritten
3765    /// (no merge or priority adjustment).
3766    pub(crate) fn extend_bindings_including_synthetic(
3767        &mut self,
3768        bindings: impl IntoIterator<Item = (SmolStr, RefCell<BindingExpression>)>,
3769    ) {
3770        self.bindings.0.extend(bindings);
3771    }
3772
3773    pub fn sub_component(&self) -> Option<&Rc<Component>> {
3774        if self.repeated.is_some() {
3775            None
3776        } else if let ElementType::Component(sub_component) = &self.base_type {
3777            Some(sub_component)
3778        } else {
3779            None
3780        }
3781    }
3782
3783    pub fn element_infos(&self) -> String {
3784        let mut debug_infos = self.debug.clone();
3785        let mut base = self.base_type.clone();
3786        while let ElementType::Component(b) = base {
3787            let elem = b.root_element.borrow();
3788            base = elem.base_type.clone();
3789            debug_infos.extend(elem.debug.iter().cloned());
3790        }
3791
3792        let (infos, _, _) = debug_infos.into_iter().fold(
3793            (String::new(), false, true),
3794            |(mut infos, elem_boundary, first), debug_info| {
3795                if elem_boundary {
3796                    infos.push('/');
3797                } else if !first {
3798                    infos.push(';');
3799                }
3800
3801                infos.push_str(&debug_info.encoded_element_info());
3802                (infos, debug_info.element_boundary, false)
3803            },
3804        );
3805        infos
3806    }
3807}
3808
3809/// For FlexboxLayout, suggest Slint property names for CSS properties.
3810fn css_property_suggestion(property_name: &str, base_type: &ElementType) -> Option<String> {
3811    let base_name = base_type.to_smolstr();
3812    if base_name != "FlexboxLayout" {
3813        return None;
3814    }
3815    match property_name {
3816        "gap" => Some("Use spacing instead of gap".into()),
3817        "row-gap" => Some("Use spacing-vertical instead of row-gap".into()),
3818        "column-gap" => Some("Use spacing-horizontal instead of column-gap".into()),
3819        "justify-content" => Some("Use alignment instead of justify-content".into()),
3820        _ => None,
3821    }
3822}
3823
3824/// Apply the default property values of the builtin element to the element.
3825pub(crate) fn apply_default_type_properties(element: &mut Element) {
3826    // Apply default property values on top:
3827    if let ElementType::Builtin(builtin_base) = &element.base_type {
3828        for (prop, info) in &builtin_base.properties {
3829            // A property the element declares under the same name is a different property.
3830            // `ensure_window` gets here with an element that declared its members before it
3831            // became a window.
3832            if element.property_declarations.contains_key(prop) {
3833                continue;
3834            }
3835            if let Some(expr) = info.default_value.expr_without_element() {
3836                element.bindings.0.entry(prop.clone()).or_insert_with(|| {
3837                    let mut binding = BindingExpression::from(expr);
3838                    binding.priority = i32::MAX;
3839                    RefCell::new(binding)
3840                });
3841            }
3842        }
3843    }
3844}
3845
3846/// Create a Type for this node
3847pub fn type_from_node(
3848    node: syntax_nodes::Type,
3849    diag: &mut BuildDiagnostics,
3850    tr: &TypeRegister,
3851) -> Type {
3852    if let Some(qualified_type_node) = node.QualifiedName() {
3853        let qualified_type = QualifiedTypeName::from_node(qualified_type_node.clone());
3854
3855        let prop_type = tr.lookup_qualified(&qualified_type.members);
3856
3857        #[cfg(feature = "slint-sc")]
3858        if !prop_type.is_slint_sc() {
3859            diag.slint_sc_error(&format!("The type '{qualified_type}' is"), &qualified_type_node);
3860        }
3861
3862        if prop_type == Type::Invalid && tr.lookup_element(&qualified_type.to_smolstr()).is_err() {
3863            diag.push_error(format!("Unknown type '{qualified_type}'"), &qualified_type_node);
3864        } else if !prop_type.is_property_type() {
3865            diag.push_error(
3866                format!("'{qualified_type}' is not a valid type"),
3867                &qualified_type_node,
3868            );
3869        }
3870        prop_type
3871    } else if let Some(object_node) = node.ObjectType() {
3872        #[cfg(feature = "slint-sc")]
3873        diag.slint_sc_error("Inline struct types are", &object_node);
3874        type_struct_from_node(object_node, diag, tr, None, None)
3875    } else if let Some(array_node) = node.ArrayType() {
3876        #[cfg(feature = "slint-sc")]
3877        diag.slint_sc_error("Array types are", &array_node);
3878        Type::Array(Arc::new(type_from_node(array_node.Type(), diag, tr)))
3879    } else {
3880        assert!(diag.has_errors());
3881        Type::Invalid
3882    }
3883}
3884
3885/// Create a [`Type::Struct`] from a [`syntax_nodes::ObjectType`]
3886///
3887/// `symbol_counters` is only available for named struct declarations,
3888/// where field default values (`struct Foo { bar: int = 42 }`) are supported.
3889pub fn type_struct_from_node(
3890    object_node: syntax_nodes::ObjectType,
3891    diag: &mut BuildDiagnostics,
3892    tr: &TypeRegister,
3893    name: Option<SmolStr>,
3894    symbol_counters: Option<&Rc<crate::symbol_counters::SymbolCounters>>,
3895) -> Type {
3896    let mut field_defaults = BTreeMap::default();
3897    let mut field_order = Vec::new();
3898    let fields: BTreeMap<SmolStr, Type> = object_node
3899        .ObjectTypeMember()
3900        .map(|member| {
3901            let field_name = parser::identifier_text(&member).unwrap_or_default();
3902            field_order.push(field_name.clone());
3903            let field_ty = type_from_node(member.Type(), diag, tr);
3904            if let Some(default_value_node) = member.Expression() {
3905                if name.is_none() {
3906                    diag.push_error(
3907                        "Field default values are only supported in named struct declarations"
3908                            .into(),
3909                        &default_value_node,
3910                    );
3911                } else if let Some(expr) = resolve_struct_field_default_value(
3912                    default_value_node,
3913                    &field_ty,
3914                    diag,
3915                    tr,
3916                    symbol_counters.expect("named struct declarations have symbol counters"),
3917                ) {
3918                    field_defaults.insert(field_name.clone(), expr);
3919                }
3920            }
3921            (field_name, field_ty)
3922        })
3923        .collect();
3924    // The `@rust-attr` attributes and the declaration node live on the
3925    // enclosing `StructDeclaration` (the parent of the `ObjectType`).
3926    let struct_decl = object_node.parent();
3927    Type::Struct(Arc::new(Struct {
3928        fields,
3929        field_defaults,
3930        name: name.map_or(StructName::None, |name| {
3931            let rust_attributes = struct_decl
3932                .as_ref()
3933                .and_then(|p| syntax_nodes::StructDeclaration::new(p.clone()))
3934                .map(|d| d.AtRustAttr().map(|a| SmolStr::from(a.text().to_string())).collect())
3935                .unwrap_or_default();
3936            let node = struct_decl.as_ref().unwrap_or(&object_node).to_source_location();
3937            StructName::User { name, node, rust_attributes, field_order }
3938        }),
3939    }))
3940}
3941
3942/// Resolve, type-check, and constant-fold the default value expression of a struct field.
3943/// Returns `None` (with a diagnostic) if the expression is not a compile time constant.
3944fn resolve_struct_field_default_value(
3945    node: syntax_nodes::Expression,
3946    field_ty: &Type,
3947    diag: &mut BuildDiagnostics,
3948    tr: &TypeRegister,
3949    symbol_counters: &Rc<crate::symbol_counters::SymbolCounters>,
3950) -> Option<crate::langtype::ConstantExpression> {
3951    #[cfg(feature = "slint-sc")]
3952    diag.slint_sc_error("Struct field default values are", &node);
3953    let mut expr = {
3954        let mut ctx = crate::lookup::LookupCtx::empty_context(tr, diag, symbol_counters.clone());
3955        ctx.property_type = field_ty.clone();
3956        Expression::from_expression_node(node.clone(), &mut ctx).maybe_convert_to(
3957            field_ty.clone(),
3958            &node,
3959            ctx.diag,
3960            &ctx.symbol_counters,
3961        )
3962    };
3963    crate::passes::const_propagation::fold_const_expression(&mut expr);
3964    // An error was already reported when a part of the expression failed to resolve
3965    // or convert; don't report a confusing constant-ness error on top of it
3966    let mut has_invalid = false;
3967    expr.visit_recursive(&mut |e| has_invalid |= matches!(e, Expression::Invalid));
3968    if has_invalid {
3969        return None;
3970    }
3971    let constant = crate::langtype::ConstantExpression::from_expression(&expr);
3972    if constant.is_none() {
3973        let reason = non_constant_expression_reason(&expr)
3974            .map_or_else(Default::default, |reason| format!(": {reason}"));
3975        diag.push_error(
3976            format!("The default value of a struct field must be a constant expression{reason}"),
3977            &node,
3978        );
3979    }
3980    constant
3981}
3982
3983/// A user-facing explanation of what makes the expression non-constant, if there is
3984/// a better one than "it is not in the supported subset"
3985fn non_constant_expression_reason(expr: &Expression) -> Option<String> {
3986    use crate::expression_tree::{BuiltinFunction, Callable};
3987    let mut reason = None;
3988    expr.visit_recursive(&mut |e| {
3989        if reason.is_some() {
3990            return;
3991        }
3992        reason = match e {
3993            Expression::PropertyReference(nr) => {
3994                Some(format!("it references the property '{}'", nr.name()))
3995            }
3996            Expression::FunctionCall { function, .. } => match function {
3997                Callable::Function(nr) => Some(format!("it calls the function '{}'", nr.name())),
3998                Callable::Callback(nr) => Some(format!("it calls the callback '{}'", nr.name())),
3999                Callable::Builtin(BuiltinFunction::GetWindowScaleFactor) => Some(
4000                    "the conversion to logical pixels depends on the window's scale factor".into(),
4001                ),
4002                Callable::Builtin(BuiltinFunction::GetWindowDefaultFontSize) => Some(
4003                    "the conversion from 'rem' depends on the window's default font size".into(),
4004                ),
4005                Callable::Builtin(BuiltinFunction::Translate) => {
4006                    Some("the translation is selected at run-time".into())
4007                }
4008                Callable::Builtin(_) => Some("functions are not evaluated at compile time".into()),
4009            },
4010            Expression::Cast { to: Type::String, .. } => {
4011                Some("the conversion from a number to a string depends on the locale".into())
4012            }
4013            _ => None,
4014        };
4015    });
4016    reason
4017}
4018
4019fn animation_element_from_node(
4020    anim: &syntax_nodes::PropertyAnimation,
4021    prop_name: &syntax_nodes::QualifiedName,
4022    prop_type: Type,
4023    diag: &mut BuildDiagnostics,
4024    tr: &TypeRegister,
4025) -> Option<ElementRc> {
4026    let anim_type = tr.property_animation_type_for_property(prop_type);
4027    if !matches!(anim_type, ElementType::Builtin(..)) {
4028        diag.push_error(
4029            format!(
4030                "'{}' is not a property that can be animated",
4031                prop_name.text().to_string().trim()
4032            ),
4033            prop_name,
4034        );
4035        None
4036    } else {
4037        let mut anim_element =
4038            Element { id: "".into(), base_type: anim_type, ..Default::default() };
4039        anim_element.parse_bindings(
4040            anim.Binding().filter_map(|b| {
4041                Some((b.child_token(SyntaxKind::Identifier)?, b.BindingExpression().into()))
4042            }),
4043            false,
4044            diag,
4045        );
4046
4047        apply_default_type_properties(&mut anim_element);
4048
4049        Some(Rc::new(RefCell::new(anim_element)))
4050    }
4051}
4052
4053#[derive(Default, Debug, Clone)]
4054pub struct QualifiedTypeName {
4055    pub members: Vec<SmolStr>,
4056}
4057
4058impl QualifiedTypeName {
4059    pub fn from_node(node: syntax_nodes::QualifiedName) -> Self {
4060        debug_assert_eq!(node.kind(), SyntaxKind::QualifiedName);
4061        let members = node
4062            .children_with_tokens()
4063            .filter(|n| n.kind() == SyntaxKind::Identifier)
4064            .filter_map(|x| x.as_token().map(|x| crate::parser::normalize_identifier(x.text())))
4065            .collect();
4066        Self { members }
4067    }
4068
4069    pub fn to_smolstr(&self) -> SmolStr {
4070        self.members.join(".").into()
4071    }
4072}
4073
4074impl Display for QualifiedTypeName {
4075    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4076        write!(f, "{}", self.members.join("."))
4077    }
4078}
4079
4080/// Return a NamedReference for a qualified name used in a state (or transition),
4081/// if the reference is invalid, there will be a diagnostic
4082fn lookup_property_from_qualified_name_for_state(
4083    node: syntax_nodes::QualifiedName,
4084    r: &ElementRc,
4085    diag: &mut BuildDiagnostics,
4086) -> Option<(NamedReference, Type)> {
4087    let qualname = QualifiedTypeName::from_node(node.clone());
4088    let check = |lookup: &PropertyLookupResult<'_>, diag: &mut BuildDiagnostics| {
4089        #[cfg(feature = "slint-sc")]
4090        lookup.check_slint_sc(&qualname, &node, diag);
4091        if !lookup.property_type.is_property_type() {
4092            diag.push_error(format!("'{qualname}' is not a valid property"), &node);
4093        } else if !lookup.is_valid_for_assignment() {
4094            diag.push_error(
4095                format!(
4096                    "'{}' cannot be set in a state because it is '{}'",
4097                    qualname, lookup.property_visibility
4098                ),
4099                &node,
4100            );
4101        }
4102    };
4103    match qualname.members.as_slice() {
4104        [unresolved_prop_name] => {
4105            let lookup_result = r
4106                .borrow()
4107                .lookup_property(unresolved_prop_name.as_ref(), PropertyLookupMode::ComponentLocal);
4108            check(&lookup_result, diag);
4109            Some((
4110                NamedReference::new(r, lookup_result.internal_or_resolved_name()),
4111                lookup_result.property_type,
4112            ))
4113        }
4114        [elem_id, unresolved_prop_name] => {
4115            if let Some(element) = find_element_by_id(r, elem_id.as_ref()) {
4116                let lookup_result = element.borrow().lookup_property(
4117                    unresolved_prop_name.as_ref(),
4118                    PropertyLookupMode::ComponentLocal,
4119                );
4120                if !lookup_result.is_valid() {
4121                    diag.push_error(
4122                        format!("'{unresolved_prop_name}' not found in '{elem_id}'"),
4123                        &node,
4124                    );
4125                } else {
4126                    check(&lookup_result, diag);
4127                }
4128                Some((
4129                    NamedReference::new(&element, lookup_result.internal_or_resolved_name()),
4130                    lookup_result.property_type,
4131                ))
4132            } else {
4133                diag.push_error(format!("'{elem_id}' is not a valid element id"), &node);
4134                None
4135            }
4136        }
4137        _ => {
4138            diag.push_error(format!("'{qualname}' is not a valid property"), &node);
4139            None
4140        }
4141    }
4142}
4143
4144/// FIXME: this is duplicated the resolving pass. Also, we should use a hash table
4145fn find_element_by_id(e: &ElementRc, name: &str) -> Option<ElementRc> {
4146    if e.borrow().id == name {
4147        return Some(e.clone());
4148    }
4149    for x in &e.borrow().children {
4150        if x.borrow().repeated.is_some() {
4151            continue;
4152        }
4153        if let Some(x) = find_element_by_id(x, name) {
4154            return Some(x);
4155        }
4156    }
4157
4158    None
4159}
4160
4161/// Find the parent element to a given element.
4162/// (since there is no parent mapping we need to fo an exhaustive search)
4163pub fn find_parent_element(e: &ElementRc) -> Option<ElementRc> {
4164    fn recurse(base: &ElementRc, e: &ElementRc) -> Option<ElementRc> {
4165        for child in &base.borrow().children {
4166            if Rc::ptr_eq(child, e) {
4167                return Some(base.clone());
4168            }
4169            if let Some(x) = recurse(child, e) {
4170                return Some(x);
4171            }
4172        }
4173        None
4174    }
4175
4176    let root = e.borrow().enclosing_component.upgrade().unwrap().root_element.clone();
4177    if Rc::ptr_eq(&root, e) {
4178        return None;
4179    }
4180    recurse(&root, e)
4181}
4182
4183/// Call the visitor for each children of the element recursively, starting with the element itself
4184///
4185/// The state returned by the visitor is passed to the children
4186pub fn recurse_elem<State>(
4187    elem: &ElementRc,
4188    state: &State,
4189    vis: &mut impl FnMut(&ElementRc, &State) -> State,
4190) {
4191    recurse_elem_dyn(elem, state, vis)
4192}
4193
4194fn recurse_elem_dyn<State>(
4195    elem: &ElementRc,
4196    state: &State,
4197    vis: &mut dyn FnMut(&ElementRc, &State) -> State,
4198) {
4199    let state = vis(elem, state);
4200    for sub in &elem.borrow().children {
4201        recurse_elem_dyn(sub, &state, vis);
4202    }
4203}
4204
4205/// Same as [`recurse_elem`] but include the elements from sub_components
4206pub fn recurse_elem_including_sub_components<State>(
4207    component: &Component,
4208    state: &State,
4209    vis: &mut impl FnMut(&ElementRc, &State) -> State,
4210) {
4211    recurse_elem_including_sub_components_dyn(component, state, vis)
4212}
4213
4214fn recurse_elem_including_sub_components_dyn<State>(
4215    component: &Component,
4216    state: &State,
4217    vis: &mut dyn FnMut(&ElementRc, &State) -> State,
4218) {
4219    recurse_elem_dyn(&component.root_element, state, &mut |elem, state| {
4220        debug_assert!(std::ptr::eq(
4221            component as *const Component,
4222            (&*elem.borrow().enclosing_component.upgrade().unwrap()) as *const Component
4223        ));
4224        if elem.borrow().repeated.is_some()
4225            && let ElementType::Component(base) = &elem.borrow().base_type
4226            && base.parent_element().is_some()
4227        {
4228            recurse_elem_including_sub_components_dyn(base, state, vis);
4229        }
4230        vis(elem, state)
4231    });
4232    component
4233        .popup_windows
4234        .borrow()
4235        .iter()
4236        .for_each(|p| recurse_elem_including_sub_components_dyn(&p.component, state, vis));
4237    component
4238        .menu_item_tree
4239        .borrow()
4240        .iter()
4241        .for_each(|c| recurse_elem_including_sub_components_dyn(c, state, vis));
4242}
4243
4244/// Same as recurse_elem, but will take the children from the element as to not keep the element borrow
4245pub fn recurse_elem_no_borrow<State>(
4246    elem: &ElementRc,
4247    state: &State,
4248    vis: &mut impl FnMut(&ElementRc, &State) -> State,
4249) {
4250    recurse_elem_no_borrow_dyn(elem, state, vis)
4251}
4252
4253fn recurse_elem_no_borrow_dyn<State>(
4254    elem: &ElementRc,
4255    state: &State,
4256    vis: &mut dyn FnMut(&ElementRc, &State) -> State,
4257) {
4258    let state = vis(elem, state);
4259    let children = elem.borrow().children.clone();
4260    for sub in &children {
4261        recurse_elem_no_borrow_dyn(sub, &state, vis);
4262    }
4263}
4264
4265/// Same as [`recurse_elem`] but include the elements form sub_components
4266pub fn recurse_elem_including_sub_components_no_borrow<State>(
4267    component: &Component,
4268    state: &State,
4269    vis: &mut impl FnMut(&ElementRc, &State) -> State,
4270) {
4271    recurse_elem_including_sub_components_no_borrow_dyn(component, state, vis)
4272}
4273
4274fn recurse_elem_including_sub_components_no_borrow_dyn<State>(
4275    component: &Component,
4276    state: &State,
4277    vis: &mut dyn FnMut(&ElementRc, &State) -> State,
4278) {
4279    recurse_elem_no_borrow_dyn(&component.root_element, state, &mut |elem, state| {
4280        let base = if elem.borrow().repeated.is_some() {
4281            if let ElementType::Component(base) = &elem.borrow().base_type {
4282                if base.parent_element().is_some() {
4283                    Some(base.clone())
4284                } else {
4285                    // The process_repeater_components pass was not run yet
4286                    None
4287                }
4288            } else {
4289                None
4290            }
4291        } else {
4292            None
4293        };
4294        if let Some(base) = base {
4295            recurse_elem_including_sub_components_no_borrow_dyn(&base, state, vis);
4296        }
4297        vis(elem, state)
4298    });
4299    component.popup_windows.borrow().iter().for_each(|p| {
4300        recurse_elem_including_sub_components_no_borrow_dyn(&p.component, state, vis)
4301    });
4302    component
4303        .menu_item_tree
4304        .borrow()
4305        .iter()
4306        .for_each(|c| recurse_elem_including_sub_components_no_borrow_dyn(c, state, vis));
4307}
4308
4309/// Visit the model expression of `elem`, if `elem` is the body of a `for`.
4310///
4311/// The expression is temporarily moved out of `repeated.model` so the visitor
4312/// can mutate it without holding a borrow on `elem`.
4313pub fn visit_repeater_model_expression(
4314    elem: &ElementRc,
4315    mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
4316) {
4317    let repeated = elem
4318        .borrow_mut()
4319        .repeated
4320        .as_mut()
4321        .map(|r| (std::mem::take(&mut r.model), r.is_conditional_element));
4322    if let Some((mut model, is_cond)) = repeated {
4323        vis(&mut model, None, &|| if is_cond { Type::Bool } else { Type::Model });
4324        elem.borrow_mut().repeated.as_mut().unwrap().model = model;
4325    }
4326}
4327
4328/// Like [`visit_element_expressions`] but skips the repeater model
4329/// expression. Use [`visit_repeater_model_expression`] separately for that.
4330pub fn visit_element_expressions_excluding_repeater_model(
4331    elem: &ElementRc,
4332    mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
4333) {
4334    visit_element_expressions_excluding_repeater_model_dyn(elem, &mut vis)
4335}
4336
4337fn visit_element_expressions_excluding_repeater_model_dyn(
4338    elem: &ElementRc,
4339    vis: &mut dyn FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
4340) {
4341    fn visit_element_expressions_simple(
4342        elem: &ElementRc,
4343        vis: &mut dyn FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
4344    ) {
4345        for (name, expr) in elem.borrow().bindings_including_synthetic() {
4346            vis(&mut expr.borrow_mut(), Some(name.as_str()), &|| {
4347                elem.borrow().lookup_property(name, PropertyLookupMode::InternalName).property_type
4348            });
4349
4350            for twb in &mut expr.borrow_mut().two_way_bindings {
4351                if let expression_tree::TwoWayBinding::ModelData { repeated_element, .. } = twb {
4352                    let mut e =
4353                        Expression::RepeaterModelReference { element: repeated_element.clone() };
4354                    vis(&mut e, None, &|| Type::Invalid);
4355                    if let Expression::RepeaterModelReference { element } = e {
4356                        *repeated_element = element;
4357                    }
4358                }
4359            }
4360
4361            match &mut expr.borrow_mut().animation {
4362                Some(PropertyAnimation::Static(e)) => visit_element_expressions_simple(e, vis),
4363                Some(PropertyAnimation::Transition { animations, state_ref }) => {
4364                    vis(state_ref, None, &|| Type::Int32);
4365                    for a in animations {
4366                        visit_element_expressions_simple(&a.animation, vis)
4367                    }
4368                }
4369                None => (),
4370            }
4371        }
4372    }
4373
4374    visit_element_expressions_simple(elem, vis);
4375
4376    for expr in elem.borrow().change_callbacks.values() {
4377        for expr in expr.borrow_mut().iter_mut() {
4378            vis(expr, Some("$change callback$"), &|| Type::Void);
4379        }
4380    }
4381
4382    let mut states = std::mem::take(&mut elem.borrow_mut().states);
4383    for s in &mut states {
4384        if let Some(cond) = s.condition.as_mut() {
4385            vis(cond, None, &|| Type::Bool)
4386        }
4387        for (ne, e, _) in &mut s.property_changes {
4388            vis(e, Some(ne.name()), &|| {
4389                ne.element()
4390                    .borrow()
4391                    .lookup_property(ne.name(), PropertyLookupMode::InternalName)
4392                    .property_type
4393            });
4394        }
4395    }
4396    elem.borrow_mut().states = states;
4397
4398    let mut transitions = std::mem::take(&mut elem.borrow_mut().transitions);
4399    for t in &mut transitions {
4400        for (_, _, a) in &mut t.property_animations {
4401            visit_element_expressions_simple(a, vis);
4402        }
4403    }
4404    elem.borrow_mut().transitions = transitions;
4405
4406    let component = elem.borrow().enclosing_component.upgrade().unwrap();
4407    if Rc::ptr_eq(&component.root_element, elem) {
4408        for e in component.init_code.borrow_mut().iter_mut() {
4409            vis(e, None, &|| Type::Void);
4410        }
4411    }
4412}
4413
4414pub fn visit_element_expressions(
4415    elem: &ElementRc,
4416    mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
4417) {
4418    visit_repeater_model_expression(elem, &mut vis);
4419    visit_element_expressions_excluding_repeater_model(elem, &mut vis);
4420}
4421
4422pub fn visit_named_references_in_expression(
4423    expr: &mut Expression,
4424    vis: &mut impl FnMut(&mut NamedReference),
4425) {
4426    visit_named_references_in_expression_dyn(expr, vis)
4427}
4428
4429fn visit_named_references_in_expression_dyn(
4430    expr: &mut Expression,
4431    vis: &mut dyn FnMut(&mut NamedReference),
4432) {
4433    expr.visit_mut(|sub| visit_named_references_in_expression_dyn(sub, vis));
4434    match expr {
4435        Expression::PropertyReference(r) => vis(r),
4436        Expression::FunctionCall {
4437            function: Callable::Callback(r) | Callable::Function(r),
4438            ..
4439        } => vis(r),
4440        Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
4441        Expression::GridRepeaterCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
4442        Expression::OrganizeGridLayout(l) => l.visit_named_references(vis),
4443        Expression::ComputeBoxLayoutInfo { layout, .. } => layout.visit_named_references(vis),
4444        Expression::ComputeFlexboxLayoutInfo { layout, .. } => layout.visit_named_references(vis),
4445        Expression::ComputeGridLayoutInfo { layout_organized_data_prop, layout, .. } => {
4446            vis(layout_organized_data_prop);
4447            layout.visit_named_references(vis);
4448        }
4449        Expression::SolveBoxLayout(l, _) => l.visit_named_references(vis),
4450        Expression::SolveFlexboxLayout(l) => l.visit_named_references(vis),
4451        Expression::SolveGridLayout { layout_organized_data_prop, layout, .. } => {
4452            vis(layout_organized_data_prop);
4453            layout.visit_named_references(vis);
4454        }
4455        // This is not really a named reference, but the result is the same, it need to be updated
4456        // FIXME: this should probably be lowered into a PropertyReference
4457        Expression::RepeaterModelReference { element }
4458        | Expression::RepeaterIndexReference { element } => {
4459            // FIXME: this is questionable
4460            let mut nc =
4461                NamedReference::new(&element.upgrade().unwrap(), SmolStr::new_static("$model"));
4462            vis(&mut nc);
4463            debug_assert!(nc.element().borrow().repeated.is_some());
4464            *element = Rc::downgrade(&nc.element());
4465        }
4466        _ => {}
4467    }
4468}
4469
4470/// Visit all the named reference in an element
4471/// But does not recurse in sub-elements. (unlike [`visit_all_named_references`] which recurse)
4472pub fn visit_all_named_references_in_element(
4473    elem: &ElementRc,
4474    mut vis: impl FnMut(&mut NamedReference),
4475) {
4476    visit_all_named_references_in_element_dyn(elem, &mut vis)
4477}
4478
4479fn visit_all_named_references_in_element_dyn(
4480    elem: &ElementRc,
4481    mut vis: &mut dyn FnMut(&mut NamedReference),
4482) {
4483    visit_element_expressions(elem, |expr, _, _| {
4484        visit_named_references_in_expression_dyn(expr, vis)
4485    });
4486    let mut states = std::mem::take(&mut elem.borrow_mut().states);
4487    for s in &mut states {
4488        for (r, _, _) in &mut s.property_changes {
4489            vis(r);
4490        }
4491    }
4492    elem.borrow_mut().states = states;
4493    let mut transitions = std::mem::take(&mut elem.borrow_mut().transitions);
4494    for t in &mut transitions {
4495        for (r, _, _) in &mut t.property_animations {
4496            vis(r)
4497        }
4498    }
4499    elem.borrow_mut().transitions = transitions;
4500    let mut repeated = std::mem::take(&mut elem.borrow_mut().repeated);
4501    if let Some(r) = &mut repeated
4502        && let Some(lv) = &mut r.is_listview
4503    {
4504        vis(&mut lv.content_y);
4505        if let Some(content_height) = &mut lv.content_height {
4506            vis(content_height);
4507        }
4508        if let Some(content_width) = &mut lv.content_width {
4509            vis(content_width);
4510        }
4511        vis(&mut lv.listview_height);
4512        vis(&mut lv.listview_width);
4513    }
4514    elem.borrow_mut().repeated = repeated;
4515    let mut layout_info_prop = std::mem::take(&mut elem.borrow_mut().layout_info_prop);
4516    layout_info_prop.as_mut().map(|(h, b)| (vis(h), vis(b)));
4517    elem.borrow_mut().layout_info_prop = layout_info_prop;
4518    let mut constrained_v = std::mem::take(&mut elem.borrow_mut().layout_info_v_with_constraint);
4519    if let Some(nr) = constrained_v.as_mut() {
4520        vis(nr);
4521    }
4522    elem.borrow_mut().layout_info_v_with_constraint = constrained_v;
4523    let mut at_own_height = std::mem::take(&mut elem.borrow_mut().layout_info_h_at_own_height);
4524    if let Some(nr) = at_own_height.as_mut() {
4525        vis(nr);
4526    }
4527    elem.borrow_mut().layout_info_h_at_own_height = at_own_height;
4528    let mut debug = std::mem::take(&mut elem.borrow_mut().debug);
4529    for d in debug.iter_mut() {
4530        if let Some(l) = d.layout.as_mut() {
4531            l.visit_named_references(vis)
4532        }
4533    }
4534    elem.borrow_mut().debug = debug;
4535
4536    let mut accessibility_props = std::mem::take(&mut elem.borrow_mut().accessibility_props);
4537    accessibility_props.0.iter_mut().for_each(|(_, x)| vis(x));
4538    elem.borrow_mut().accessibility_props = accessibility_props;
4539
4540    let geometry_props = elem.borrow_mut().geometry_props.take();
4541    if let Some(mut geometry_props) = geometry_props {
4542        vis(&mut geometry_props.x);
4543        vis(&mut geometry_props.y);
4544        vis(&mut geometry_props.width);
4545        vis(&mut geometry_props.height);
4546        elem.borrow_mut().geometry_props = Some(geometry_props);
4547    }
4548
4549    let z_order = elem.borrow_mut().z_order.take();
4550    if let Some(mut zo) = z_order {
4551        if let ZOrder::Dynamic(ref mut nr) | ZOrder::PerInstance(ref mut nr) = zo {
4552            vis(nr);
4553        }
4554        elem.borrow_mut().z_order = Some(zo);
4555    }
4556
4557    // visit two way bindings
4558    for (_, expr) in elem.borrow().real_bindings() {
4559        for twb in &mut expr.borrow_mut().two_way_bindings {
4560            if let expression_tree::TwoWayBinding::Property { property, .. } = twb {
4561                vis(property);
4562            }
4563        }
4564    }
4565
4566    let mut property_declarations = std::mem::take(&mut elem.borrow_mut().property_declarations);
4567    for pd in property_declarations.values_mut() {
4568        pd.is_alias.as_mut().map(&mut vis);
4569    }
4570    elem.borrow_mut().property_declarations = property_declarations;
4571
4572    // Visit grid_layout_cell for repeated Row elements
4573    let grid_layout_cell = elem.borrow_mut().grid_layout_cell.take();
4574    if let Some(grid_layout_cell) = grid_layout_cell {
4575        grid_layout_cell.borrow_mut().visit_named_references(&mut vis);
4576        elem.borrow_mut().grid_layout_cell = Some(grid_layout_cell);
4577    }
4578}
4579
4580/// Visit all named reference in this component and sub component
4581pub fn visit_all_named_references(
4582    component: &Component,
4583    vis: &mut impl FnMut(&mut NamedReference),
4584) {
4585    visit_all_named_references_dyn(component, vis)
4586}
4587
4588fn visit_all_named_references_dyn(component: &Component, vis: &mut dyn FnMut(&mut NamedReference)) {
4589    recurse_elem_including_sub_components_no_borrow_dyn(
4590        component,
4591        &Weak::new(),
4592        &mut |elem, parent_compo| {
4593            visit_all_named_references_in_element_dyn(elem, vis);
4594            let compo = elem.borrow().enclosing_component.clone();
4595            if !Weak::ptr_eq(parent_compo, &compo) {
4596                let compo = compo.upgrade().unwrap();
4597                compo.root_constraints.borrow_mut().visit_named_references(vis);
4598                compo.popup_windows.borrow_mut().iter_mut().for_each(|p| {
4599                    vis(&mut p.x);
4600                    vis(&mut p.y);
4601                    if let Some(is_open) = &mut p.is_open {
4602                        vis(is_open);
4603                    }
4604                });
4605                compo.timers.borrow_mut().iter_mut().for_each(|t| {
4606                    vis(&mut t.interval);
4607                    vis(&mut t.triggered);
4608                    vis(&mut t.running);
4609                });
4610                for o in compo.optimized_elements.borrow().iter() {
4611                    visit_element_expressions(o, |expr, _, _| {
4612                        visit_named_references_in_expression_dyn(expr, vis)
4613                    });
4614                }
4615            }
4616            compo
4617        },
4618    );
4619}
4620
4621/// Visit all expression in this component and sub components
4622///
4623/// Does not recurse in the expression itself
4624pub fn visit_all_expressions(
4625    component: &Component,
4626    mut vis: impl FnMut(&mut Expression, &dyn Fn() -> Type),
4627) {
4628    visit_all_expressions_dyn(component, &mut vis)
4629}
4630
4631fn visit_all_expressions_dyn(
4632    component: &Component,
4633    vis: &mut dyn FnMut(&mut Expression, &dyn Fn() -> Type),
4634) {
4635    recurse_elem_including_sub_components_dyn(component, &Weak::new(), &mut |elem, parent_compo| {
4636        visit_element_expressions(elem, |expr, _, ty| vis(expr, ty));
4637        let compo = elem.borrow().enclosing_component.clone();
4638        if !Weak::ptr_eq(parent_compo, &compo) {
4639            let compo = compo.upgrade().unwrap();
4640            for o in compo.optimized_elements.borrow().iter() {
4641                visit_element_expressions(o, |expr, _, ty| vis(expr, ty));
4642            }
4643        }
4644        compo
4645    })
4646}
4647
4648#[derive(Debug, Clone)]
4649pub struct State {
4650    pub id: SmolStr,
4651    pub condition: Option<Expression>,
4652    pub property_changes: Vec<(NamedReference, Expression, syntax_nodes::StatePropertyChange)>,
4653    /// Where the source writes this state's selection. `None` for a state
4654    /// without a condition, which is never selected.
4655    pub selection: Option<ConditionLocation>,
4656}
4657
4658#[derive(Debug, Clone)]
4659pub struct Transition {
4660    pub direction: TransitionDirection,
4661    pub state_id: SmolStr,
4662    pub property_animations: Vec<(NamedReference, SourceLocation, ElementRc)>,
4663    pub node: syntax_nodes::Transition,
4664}
4665
4666impl Transition {
4667    fn from_node(
4668        trs: syntax_nodes::Transition,
4669        r: &ElementRc,
4670        tr: &TypeRegister,
4671        diag: &mut BuildDiagnostics,
4672    ) -> Transition {
4673        if let Some(star) = trs.child_token(SyntaxKind::Star) {
4674            diag.push_error("catch-all not yet implemented".into(), &star);
4675        };
4676        let direction_text = trs
4677            .first_child_or_token()
4678            .and_then(|t| t.as_token().map(|tok| tok.text().to_string()))
4679            .unwrap_or_default();
4680
4681        Transition {
4682            direction: match direction_text.as_str() {
4683                "in" => TransitionDirection::In,
4684                "out" => TransitionDirection::Out,
4685                "in-out" => TransitionDirection::InOut,
4686                "in_out" => TransitionDirection::InOut,
4687                _ => {
4688                    unreachable!("Unknown transition direction: '{}'", direction_text);
4689                }
4690            },
4691            state_id: trs
4692                .DeclaredIdentifier()
4693                .and_then(|x| parser::identifier_text(&x))
4694                .unwrap_or_default(),
4695            property_animations: trs
4696                .PropertyAnimation()
4697                .flat_map(|pa| pa.QualifiedName().map(move |qn| (pa.clone(), qn)))
4698                .filter_map(|(pa, qn)| {
4699                    lookup_property_from_qualified_name_for_state(qn.clone(), r, diag).and_then(
4700                        |(ne, prop_type)| {
4701                            animation_element_from_node(&pa, &qn, prop_type, diag, tr)
4702                                .map(|anim_element| (ne, qn.to_source_location(), anim_element))
4703                        },
4704                    )
4705                })
4706                .collect(),
4707            node: trs.clone(),
4708        }
4709    }
4710}
4711
4712#[derive(Clone, Debug, derive_more::Deref)]
4713pub struct ExportedName {
4714    #[deref]
4715    pub name: SmolStr, // normalized
4716    pub name_ident: SyntaxNode,
4717}
4718
4719impl ExportedName {
4720    pub fn original_name(&self) -> SmolStr {
4721        self.name_ident
4722            .child_token(parser::SyntaxKind::Identifier)
4723            .map(|n| n.to_smolstr())
4724            .unwrap_or_else(|| self.name.clone())
4725    }
4726
4727    pub fn from_export_specifier(
4728        export_specifier: &syntax_nodes::ExportSpecifier,
4729    ) -> (SmolStr, ExportedName) {
4730        let internal_name =
4731            parser::identifier_text(&export_specifier.ExportIdentifier()).unwrap_or_default();
4732
4733        let (name, name_ident): (SmolStr, SyntaxNode) = export_specifier
4734            .ExportName()
4735            .and_then(|ident| {
4736                parser::identifier_text(&ident).map(|text| (text, ident.clone().into()))
4737            })
4738            .unwrap_or_else(|| (internal_name.clone(), export_specifier.ExportIdentifier().into()));
4739        (internal_name, ExportedName { name, name_ident })
4740    }
4741}
4742
4743#[derive(Default, Debug, derive_more::Deref)]
4744pub struct Exports {
4745    #[deref]
4746    components_or_types: Vec<(ExportedName, Either<Rc<Component>, Type>)>,
4747}
4748
4749impl Exports {
4750    pub fn from_node(
4751        doc: &syntax_nodes::Document,
4752        inner_components: &[Rc<Component>],
4753        type_registry: &TypeRegister,
4754        diag: &mut BuildDiagnostics,
4755    ) -> Self {
4756        let resolve_export_to_inner_component_or_import =
4757            |internal_name: &str, internal_name_node: &dyn Spanned, diag: &mut BuildDiagnostics| {
4758                if let Ok(ElementType::Component(c)) = type_registry.lookup_element(internal_name) {
4759                    Some(Either::Left(c))
4760                } else if let ty @ Type::Struct { .. } | ty @ Type::Enumeration(_) =
4761                    type_registry.lookup(internal_name)
4762                {
4763                    Some(Either::Right(ty))
4764                } else if type_registry.lookup_element(internal_name).is_ok()
4765                    || type_registry.lookup(internal_name) != Type::Invalid
4766                {
4767                    diag.push_error(
4768                        format!("Cannot export '{internal_name}' because it is not a component",),
4769                        internal_name_node,
4770                    );
4771                    None
4772                } else {
4773                    diag.push_error(format!("'{internal_name}' not found",), internal_name_node);
4774                    None
4775                }
4776            };
4777
4778        // Collect all exports from the three sources, then sort once (O(n log n))
4779        // instead of insertion sort (O(n²))
4780        let mut exports_with_duplicates: Vec<(ExportedName, Either<Rc<Component>, Type>)> =
4781            Vec::new();
4782
4783        // Source 1: ExportSpecifiers
4784        exports_with_duplicates.extend(
4785            doc.ExportsList()
4786                // re-export are handled in the TypeLoader::load_dependencies_recursively_impl
4787                .filter(|exports| exports.ExportModule().is_none())
4788                .flat_map(|exports| exports.ExportSpecifier())
4789                .filter_map(|export_specifier| {
4790                    let (internal_name, exported_name) =
4791                        ExportedName::from_export_specifier(&export_specifier);
4792                    Some((
4793                        exported_name,
4794                        resolve_export_to_inner_component_or_import(
4795                            &internal_name,
4796                            &export_specifier.ExportIdentifier(),
4797                            diag,
4798                        )?,
4799                    ))
4800                }),
4801        );
4802
4803        // Source 2: Exported components
4804        exports_with_duplicates.extend(
4805            doc.ExportsList().flat_map(|exports| exports.Component()).filter_map(|component| {
4806                let name_ident: SyntaxNode = component.DeclaredIdentifier().into();
4807                let name =
4808                    parser::identifier_text(&component.DeclaredIdentifier()).unwrap_or_else(|| {
4809                        debug_assert!(diag.has_errors());
4810                        SmolStr::default()
4811                    });
4812
4813                let compo_or_type =
4814                    resolve_export_to_inner_component_or_import(&name, &name_ident, diag)?;
4815
4816                Some((ExportedName { name, name_ident }, compo_or_type))
4817            }),
4818        );
4819
4820        // Source 3: Exported structs and enums
4821        exports_with_duplicates.extend(
4822            doc.ExportsList()
4823                .flat_map(|exports| {
4824                    exports
4825                        .StructDeclaration()
4826                        .map(|st| st.DeclaredIdentifier())
4827                        .chain(exports.EnumDeclaration().map(|en| en.DeclaredIdentifier()))
4828                })
4829                .filter_map(|name_ident| {
4830                    let name = parser::identifier_text(&name_ident).unwrap_or_else(|| {
4831                        debug_assert!(diag.has_errors());
4832                        SmolStr::default()
4833                    });
4834
4835                    let name_ident = name_ident.into();
4836
4837                    let compo_or_type =
4838                        resolve_export_to_inner_component_or_import(&name, &name_ident, diag)?;
4839
4840                    Some((ExportedName { name, name_ident }, compo_or_type))
4841                }),
4842        );
4843
4844        exports_with_duplicates.sort_by(|(a, _), (b, _)| a.name.cmp(&b.name));
4845
4846        let mut sorted_deduped_exports = Vec::with_capacity(exports_with_duplicates.len());
4847        let mut it = exports_with_duplicates.into_iter().peekable();
4848        while let Some((exported_name, compo_or_type)) = it.next() {
4849            let mut warning_issued_on_first_occurrence = false;
4850
4851            // Skip over duplicates and issue warnings
4852            while it.peek().map(|(name, _)| &name.name) == Some(&exported_name.name) {
4853                let message = format!("Duplicated export '{}'", exported_name.name);
4854
4855                if !warning_issued_on_first_occurrence {
4856                    diag.push_error(message.clone(), &exported_name.name_ident);
4857                    warning_issued_on_first_occurrence = true;
4858                }
4859
4860                let duplicate_loc = it.next().unwrap().0.name_ident;
4861                diag.push_error(message.clone(), &duplicate_loc);
4862            }
4863
4864            sorted_deduped_exports.push((exported_name, compo_or_type));
4865        }
4866
4867        if let Some(last_compo) = inner_components.last() {
4868            let name = last_compo.id.clone();
4869            if last_compo.is_global() {
4870                if sorted_deduped_exports.is_empty() {
4871                    diag.push_warning("Global singleton is implicitly marked for export. This is deprecated and it should be explicitly exported".into(), &last_compo.node.as_ref().map(|n| n.to_source_location()));
4872                    sorted_deduped_exports.push((
4873                        ExportedName { name, name_ident: doc.clone().into() },
4874                        Either::Left(last_compo.clone()),
4875                    ))
4876                }
4877            } else if !sorted_deduped_exports
4878                .iter()
4879                .any(|e| e.1.as_ref().left().is_some_and(|c| !c.is_global()))
4880            {
4881                diag.push_warning("Component is implicitly marked for export. This is deprecated and it should be explicitly exported".into(), &last_compo.node.as_ref().map(|n| n.to_source_location()));
4882                let insert_pos = sorted_deduped_exports
4883                    .partition_point(|(existing_export, _)| existing_export.name <= name);
4884                sorted_deduped_exports.insert(
4885                    insert_pos,
4886                    (
4887                        ExportedName { name, name_ident: doc.clone().into() },
4888                        Either::Left(last_compo.clone()),
4889                    ),
4890                )
4891            }
4892        }
4893        Self { components_or_types: sorted_deduped_exports }
4894    }
4895
4896    pub fn add_reexports(
4897        &mut self,
4898        other_exports: impl IntoIterator<Item = (ExportedName, Either<Rc<Component>, Type>)>,
4899        diag: &mut BuildDiagnostics,
4900    ) {
4901        for export in other_exports {
4902            match self.components_or_types.binary_search_by(|entry| entry.0.cmp(&export.0)) {
4903                Ok(_) => {
4904                    diag.push_warning(
4905                        format!(
4906                            "'{}' is already exported in this file; it will not be re-exported",
4907                            *export.0
4908                        ),
4909                        &export.0.name_ident,
4910                    );
4911                }
4912                Err(insert_pos) => {
4913                    self.components_or_types.insert(insert_pos, export);
4914                }
4915            }
4916        }
4917    }
4918
4919    pub fn find(&self, name: &str) -> Option<Either<Rc<Component>, Type>> {
4920        self.components_or_types
4921            .binary_search_by(|(exported_name, _)| exported_name.as_str().cmp(name))
4922            .ok()
4923            .map(|index| self.components_or_types[index].1.clone())
4924    }
4925
4926    /// The `(original, alias)` pairs for renamed `export { Original as Alias }`
4927    /// of components (non-global), structs and enums — the aliases the
4928    /// generators attach to the generated type. Global aliases are handled
4929    /// separately, through `GlobalComponent::aliases`.
4930    pub fn named_type_aliases(&self) -> Vec<(SmolStr, SmolStr)> {
4931        self.iter()
4932            .filter_map(|(exported, item)| match item {
4933                Either::Left(component) if !component.is_global() => {
4934                    Some((component.id.clone(), exported.name.clone()))
4935                }
4936                Either::Right(ty) => match ty {
4937                    Type::Struct(s) if s.node().is_some() => match &s.name {
4938                        StructName::User { name, .. } => {
4939                            Some((name.clone(), exported.name.clone()))
4940                        }
4941                        _ => None,
4942                    },
4943                    Type::Enumeration(en) => Some((en.name.clone(), exported.name.clone())),
4944                    _ => None,
4945                },
4946                _ => None,
4947            })
4948            .filter(|(original, alias)| original != alias)
4949            .collect()
4950    }
4951
4952    pub fn retain(
4953        &mut self,
4954        func: impl FnMut(&mut (ExportedName, Either<Rc<Component>, Type>)) -> bool,
4955    ) {
4956        self.components_or_types.retain_mut(func)
4957    }
4958
4959    pub(crate) fn snapshot(&self, snapshotter: &mut crate::typeloader::Snapshotter) -> Self {
4960        let components_or_types = self
4961            .components_or_types
4962            .iter()
4963            .map(|(en, either)| {
4964                let en = en.clone();
4965                let either = match either {
4966                    itertools::Either::Left(l) => itertools::Either::Left({
4967                        Weak::upgrade(&snapshotter.use_component(l))
4968                            .expect("Component should cleanly upgrade here")
4969                    }),
4970                    itertools::Either::Right(r) => itertools::Either::Right(r.clone()),
4971                };
4972                (en, either)
4973            })
4974            .collect();
4975
4976        Self { components_or_types }
4977    }
4978}
4979
4980impl std::iter::IntoIterator for Exports {
4981    type Item = (ExportedName, Either<Rc<Component>, Type>);
4982
4983    type IntoIter = std::vec::IntoIter<Self::Item>;
4984
4985    fn into_iter(self) -> Self::IntoIter {
4986        self.components_or_types.into_iter()
4987    }
4988}
4989
4990/// Re-declare constrained layout info on an injected wrapper.
4991///
4992/// Forward an existing function when the child has one. A builtin root may depend
4993/// on width without a synthetic function, so rebuild its implicit vertical info
4994/// with the wrapper's width parameter.
4995fn forward_layout_info_with_constraint(new_root: &ElementRc, old_root: &ElementRc) {
4996    let width = Expression::FunctionParameterReference { index: 0, ty: Type::LogicalLength };
4997    let body = if let Some(nr) = old_root.borrow().inherited_layout_info_v_with_constraint() {
4998        Some(Expression::FunctionCall {
4999            function: Callable::Function(NamedReference::new(old_root, nr.name().clone())),
5000            arguments: vec![width],
5001            source_location: None,
5002        })
5003    } else if old_root.borrow().is_builtin_height_for_width() {
5004        crate::layout::implicit_layout_info_call(
5005            old_root,
5006            Orientation::Vertical,
5007            crate::layout::BuiltinFilter::All,
5008            Some(width),
5009        )
5010    } else {
5011        None
5012    };
5013    if let Some(body) = body {
5014        crate::passes::lower_layout::synthesize_layoutinfo_v_with_constraint_on(
5015            new_root,
5016            old_root.borrow().to_source_location(),
5017            body,
5018        );
5019    }
5020}
5021
5022/// This function replace the root element of a repeated element. the previous root becomes the only
5023/// child of the new root element.
5024/// Note that no reference to the base component must exist outside of repeated_element.base_type
5025pub fn inject_element_as_repeated_element(repeated_element: &ElementRc, new_root: ElementRc) {
5026    let component = repeated_element.borrow().base_type.as_component().clone();
5027    // Since we're going to replace the repeated element's component, we need to assert that
5028    // outside this function no strong reference exists to it. Then we can unwrap and
5029    // replace the root element.
5030    debug_assert_eq!(Rc::strong_count(&component), 2);
5031    let old_root = &component.root_element;
5032
5033    adjust_geometry_for_injected_parent(&new_root, old_root);
5034
5035    // Any elements with a weak reference to the repeater's component will need fixing later.
5036    let mut elements_with_enclosing_component_reference = Vec::new();
5037    recurse_elem(old_root, &(), &mut |element: &ElementRc, _| {
5038        if let Some(enclosing_component) = element.borrow().enclosing_component.upgrade()
5039            && Rc::ptr_eq(&enclosing_component, &component)
5040        {
5041            elements_with_enclosing_component_reference.push(element.clone());
5042        }
5043    });
5044    elements_with_enclosing_component_reference
5045        .extend_from_slice(component.optimized_elements.borrow().as_slice());
5046    elements_with_enclosing_component_reference.push(new_root.clone());
5047
5048    new_root.borrow_mut().child_of_layout =
5049        std::mem::replace(&mut old_root.borrow_mut().child_of_layout, false);
5050    // The injected parent becomes the repeated element, so it takes over the grid cell role.
5051    new_root.borrow_mut().grid_layout_cell = old_root.borrow_mut().grid_layout_cell.take();
5052    // Likewise it takes over the flexbox cell role, so the flex item-info accessor is
5053    // generated on the wrapper the layout actually calls it on.
5054    if old_root.borrow().child_of_flexbox {
5055        new_root.borrow_mut().child_of_flexbox = true;
5056    }
5057    new_root.borrow_mut().parent_box_layout_orientation =
5058        old_root.borrow().parent_box_layout_orientation;
5059    // The item-info accessors read the per-item layout properties from the repeated
5060    // root (now the wrapper). Link them to the inner element that still carries the
5061    // bindings (and the layout's captured references keeping them alive), rather
5062    // than moving them, which would leave those references dangling.
5063    for prop in ["layout-order", "cross-axis-self-alignment"].iter() {
5064        if old_root.borrow().binding(prop).is_some() {
5065            new_root.borrow_mut().set_binding(
5066                SmolStr::new_static(prop),
5067                BindingExpression::new_two_way(
5068                    NamedReference::new(old_root, SmolStr::new_static(prop)).into(),
5069                ),
5070            );
5071        }
5072    }
5073    // Resolved through the accessor: the wrapper has no height binding of its
5074    // own, so it must inherit the choice the wrapped flex made.
5075    let layout_info_prop = {
5076        let old = old_root.borrow();
5077        old.effective_layout_info_prop(Orientation::Horizontal)
5078            .cloned()
5079            .zip(old.effective_layout_info_prop(Orientation::Vertical).cloned())
5080    }
5081    .or_else(|| {
5082        // generate the layout_info_prop that forward to the implicit layout for that item
5083        let li_v = crate::layout::create_new_prop(
5084            &new_root,
5085            SmolStr::new_static("layoutinfo-v"),
5086            crate::typeregister::layout_info_type().into(),
5087        );
5088        let li_h = crate::layout::create_new_prop(
5089            &new_root,
5090            SmolStr::new_static("layoutinfo-h"),
5091            crate::typeregister::layout_info_type().into(),
5092        );
5093        let expr_h = crate::layout::implicit_layout_info_call(
5094            old_root,
5095            Orientation::Horizontal,
5096            crate::layout::BuiltinFilter::All,
5097            None,
5098        )
5099        .unwrap();
5100        let expr_v = crate::layout::implicit_layout_info_call(
5101            old_root,
5102            Orientation::Vertical,
5103            crate::layout::BuiltinFilter::All,
5104            None,
5105        )
5106        .unwrap();
5107        let expr_v =
5108            BindingExpression::new_with_span(expr_v, old_root.borrow().to_source_location());
5109        li_v.element().borrow_mut().set_binding(li_v.name().clone(), expr_v);
5110        let expr_h =
5111            BindingExpression::new_with_span(expr_h, old_root.borrow().to_source_location());
5112        li_h.element().borrow_mut().set_binding(li_h.name().clone(), expr_h);
5113        Some((li_h.clone(), li_v.clone()))
5114    });
5115    new_root.borrow_mut().layout_info_prop = layout_info_prop;
5116    forward_layout_info_with_constraint(&new_root, old_root);
5117
5118    // Replace the repeated component's element with our shadow element. That requires a bit of reference counting
5119    // surgery and relies on nobody having a strong reference left to the component, which we take out of the Rc.
5120    drop(std::mem::take(&mut repeated_element.borrow_mut().base_type));
5121
5122    debug_assert_eq!(Rc::strong_count(&component), 1);
5123
5124    let mut component = Rc::try_unwrap(component).expect("internal compiler error: more than one strong reference left to repeated component when lowering shadow properties");
5125
5126    let old_root = std::mem::replace(&mut component.root_element, new_root.clone());
5127    new_root.borrow_mut().children.push(old_root);
5128
5129    let component = Rc::new(component);
5130    repeated_element.borrow_mut().base_type = ElementType::Component(component.clone());
5131
5132    for elem in elements_with_enclosing_component_reference {
5133        elem.borrow_mut().enclosing_component = Rc::downgrade(&component);
5134    }
5135}
5136
5137/// Make the geometry of the `injected_parent` that of the old_elem. And the old_elem
5138/// will cover the `injected_parent`
5139pub fn adjust_geometry_for_injected_parent(injected_parent: &ElementRc, old_elem: &ElementRc) {
5140    let mut injected_parent_mut = injected_parent.borrow_mut();
5141    injected_parent_mut.set_binding(
5142        "z".into(),
5143        BindingExpression::new_two_way(
5144            NamedReference::new(old_elem, SmolStr::new_static("z")).into(),
5145        ),
5146    );
5147    // (should be removed by const propagation in the llr)
5148    injected_parent_mut.property_declarations.insert(
5149        "dummy".into(),
5150        PropertyDeclaration { property_type: Type::LogicalLength, ..Default::default() },
5151    );
5152    let mut old_elem_mut = old_elem.borrow_mut();
5153    injected_parent_mut.default_fill_parent = std::mem::take(&mut old_elem_mut.default_fill_parent);
5154    injected_parent_mut.geometry_props.clone_from(&old_elem_mut.geometry_props);
5155    // The injected element takes the old element's place among the z-sorted siblings
5156    injected_parent_mut.z_order = old_elem_mut.z_order.take();
5157    drop(injected_parent_mut);
5158    old_elem_mut.geometry_props.as_mut().unwrap().x =
5159        NamedReference::new(injected_parent, SmolStr::new_static("dummy"));
5160    old_elem_mut.geometry_props.as_mut().unwrap().y =
5161        NamedReference::new(injected_parent, SmolStr::new_static("dummy"));
5162}