Skip to main content

i_slint_compiler/llr/
lower_to_item_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
4use by_address::ByAddress;
5use itertools::Either;
6use std::sync::Arc;
7
8use super::lower_expression::{ExpressionLoweringCtx, ExpressionLoweringCtxInner};
9use crate::CompilerConfiguration;
10use crate::expression_tree::Expression as tree_Expression;
11use crate::langtype::{BuiltinStruct, ElementType, PropertyLookupMode, Struct, StructName, Type};
12use crate::llr::item_tree::*;
13use crate::namedreference::NamedReference;
14use crate::object_tree::{self, Component, ElementRc, PropertyAnalysis};
15use smol_str::{SmolStr, format_smolstr};
16use std::collections::{BTreeMap, HashMap};
17use std::rc::Rc;
18use typed_index_collections::TiVec;
19
20/// The types the generated module re-exports: every declared struct/enum under its own
21/// name (deprecated when not reachable from the public API), the renamed export aliases,
22/// and the deprecated pre-rename names. Collision-renamed types are omitted — they were
23/// never public.
24fn type_exports(document: &object_tree::Document) -> Vec<TypeExport> {
25    let used_types = document.used_types.borrow();
26    let public = public_facing_type_names(document);
27    let mut list = Vec::new();
28    for ty in &used_types.structs_and_enums {
29        let name = match ty {
30            Type::Struct(s) => match &s.name {
31                StructName::User { name, .. } => Some(name.clone()),
32                _ => None,
33            },
34            Type::Enumeration(en) => Some(en.name.clone()),
35            _ => None,
36        };
37        let Some(name) = name else { continue };
38        if used_types.collision_renamed_names.contains(&name) {
39            continue;
40        }
41        let deprecated = !public.contains(&name);
42        list.push(TypeExport { exported_name: name.clone(), internal_name: name, deprecated });
43    }
44    for (internal_name, exported_name) in document.exports.named_type_aliases() {
45        list.push(TypeExport { exported_name, internal_name, deprecated: false });
46    }
47    for (old_name, new_name) in &used_types.deprecated_type_aliases {
48        list.push(TypeExport {
49            exported_name: old_name.clone(),
50            internal_name: new_name.clone(),
51            deprecated: true,
52        });
53    }
54    list
55}
56
57/// The names of the structs and enums reachable from the public API: explicitly exported,
58/// or used on a public member of an exported component. A type used only privately is not
59/// part of the public namespace.
60fn public_facing_type_names(
61    document: &object_tree::Document,
62) -> std::collections::HashSet<SmolStr> {
63    let mut public = std::collections::HashSet::new();
64    let mut collect = |ty: &Type| {
65        crate::langtype::visit_declared_types(ty, &mut |name, _| {
66            public.insert(name.clone());
67        })
68    };
69    for (_, export) in document.exports.iter() {
70        match export {
71            Either::Right(ty) => collect(ty),
72            Either::Left(component) => {
73                for pd in component.root_element.borrow().property_declarations.values() {
74                    if pd.visibility != object_tree::PropertyVisibility::Private {
75                        collect(&pd.property_type);
76                    }
77                }
78            }
79        }
80    }
81    public
82}
83
84pub fn lower_to_item_tree(
85    document: &crate::object_tree::Document,
86    compiler_config: &CompilerConfiguration,
87) -> CompilationUnit {
88    let mut state = LoweringState::default();
89
90    #[cfg(feature = "bundle-translations")]
91    {
92        state.translation_builder = document.translation_builder.clone();
93    }
94
95    let mut globals = TiVec::new();
96    for g in &document.used_types.borrow().globals {
97        let count = globals.next_key();
98        globals.push(lower_global(g, count, &mut state));
99    }
100    for (g, l) in document.used_types.borrow().globals.iter().zip(&mut globals) {
101        lower_global_expressions(g, &mut state, l);
102    }
103
104    for c in &document.used_types.borrow().sub_components {
105        let sc = lower_sub_component(c, &mut state, None, compiler_config);
106        let idx = state.push_sub_component(sc);
107        state.sub_component_mapping.insert(ByAddress(c.clone()), idx);
108    }
109
110    let public_components = document
111        .exported_roots()
112        .map(|component| {
113            let top_level_type = if component.inherits_system_tray_icon() {
114                TopLevelComponentType::SystemTrayIcon
115            } else {
116                TopLevelComponentType::Window
117            };
118            let mut sc = lower_sub_component(&component, &mut state, None, compiler_config);
119            let public_properties = public_properties(&component, &sc.mapping, &state);
120            sc.sub_component.name = component.id.clone();
121            let item_tree = ItemTree {
122                tree: make_tree(&state, &component.root_element, &sc, &[]),
123                root: state.push_sub_component(sc),
124            };
125            // For C++ codegen, the root component must have the same name as the public component
126            PublicComponent {
127                item_tree,
128                public_properties,
129                private_properties: component.private_properties.borrow().clone(),
130                name: component.id.clone(),
131                top_level_type,
132            }
133        })
134        .collect();
135
136    let popup_menu = document.popup_menu_impl.as_ref().map(|c| {
137        let sc = lower_sub_component(c, &mut state, None, compiler_config);
138        let sub_menu = sc.mapping.map_property_reference(
139            &NamedReference::new(&c.root_element, SmolStr::new_static("sub-menu")),
140            &state,
141        );
142        let activated = sc.mapping.map_property_reference(
143            &NamedReference::new(&c.root_element, SmolStr::new_static("activated")),
144            &state,
145        );
146        let close = sc.mapping.map_property_reference(
147            &NamedReference::new(&c.root_element, SmolStr::new_static("close-popup")),
148            &state,
149        );
150        let entries = sc.mapping.map_property_reference(
151            &NamedReference::new(&c.root_element, SmolStr::new_static("entries")),
152            &state,
153        );
154        let item_tree = ItemTree {
155            tree: make_tree(&state, &c.root_element, &sc, &[]),
156            root: state.push_sub_component(sc),
157        };
158        PopupMenu { item_tree, sub_menu, activated, close, entries }
159    });
160
161    let mut root = CompilationUnit {
162        public_components,
163        globals,
164        sub_components: state.sub_components.into_iter().map(|sc| sc.sub_component).collect(),
165        used_sub_components: document
166            .used_types
167            .borrow()
168            .sub_components
169            .iter()
170            .map(|tree_sub_compo| state.sub_component_mapping[&ByAddress(tree_sub_compo.clone())])
171            .collect(),
172        has_debug_info: compiler_config.debug_info,
173        popup_menu,
174        type_exports: type_exports(document),
175        #[cfg(feature = "bundle-translations")]
176        translations: state.translation_builder.map(|x| x.result()),
177    };
178    super::optim_passes::run_passes(&mut root);
179    root
180}
181
182#[derive(Debug, Clone)]
183pub enum LoweredElement {
184    SubComponent { sub_component_index: SubComponentInstanceIdx },
185    NativeItem { item_index: ItemInstanceIdx },
186    Repeated { repeated_index: RepeatedElementIdx },
187    ComponentPlaceholder { repeated_index: u32 },
188}
189
190#[derive(Default, Debug, Clone)]
191pub struct LoweredSubComponentMapping {
192    pub element_mapping: HashMap<ByAddress<ElementRc>, LoweredElement>,
193    pub property_mapping: HashMap<NamedReference, MemberReference>,
194    pub repeater_count: u32,
195    pub container_count: u32,
196}
197
198impl LoweredSubComponentMapping {
199    pub fn map_property_reference(
200        &self,
201        from: &NamedReference,
202        state: &LoweringState,
203    ) -> MemberReference {
204        if let Some(x) = self.property_mapping.get(from) {
205            return x.clone();
206        }
207        if let Some(x) = state.global_properties.get(from) {
208            return x.clone();
209        }
210        let element = from.element();
211        if let Some(alias) = element
212            .borrow()
213            .property_declarations
214            .get(from.name())
215            .and_then(|x| x.is_alias.as_ref())
216        {
217            return self.map_property_reference(alias, state);
218        }
219        match self.element_mapping.get(&element.clone().into()).unwrap() {
220            LoweredElement::SubComponent { sub_component_index } => {
221                if let ElementType::Component(base) = &element.borrow().base_type {
222                    let mut prop_ref = state.map_property_reference(&NamedReference::new(
223                        &base.root_element,
224                        from.name().clone(),
225                    ));
226                    if let MemberReference::Relative { parent_level, local_reference } =
227                        &mut prop_ref
228                    {
229                        assert_eq!(*parent_level, 0, "the sub-component had no parents");
230                        local_reference.sub_component_path.insert(0, *sub_component_index);
231                    }
232                    return prop_ref;
233                }
234                unreachable!()
235            }
236            LoweredElement::NativeItem { item_index } => {
237                // The element's declared property type is authoritative:
238                // probing the rtti tables by name at install time would pick
239                // the wrong side when a name exists in both the property and
240                // callback tables.
241                let kind = match element
242                    .borrow()
243                    .lookup_property(from.name(), PropertyLookupMode::InternalName)
244                    .property_type
245                {
246                    Type::Callback(..) => super::NativeMemberKind::Callback,
247                    Type::Function(..) => super::NativeMemberKind::Function,
248                    _ => super::NativeMemberKind::Property,
249                };
250                MemberReference::Relative {
251                    parent_level: 0,
252                    local_reference: LocalMemberReference {
253                        sub_component_path: Vec::new(),
254                        reference: LocalMemberIndex::Native {
255                            item_index: *item_index,
256                            prop_name: from.name().clone(),
257                            kind,
258                        },
259                    },
260                }
261            }
262            LoweredElement::Repeated { .. } => {
263                panic!(
264                    "Trying to map property {from:?} on a repeated element {} of type {:?}",
265                    element.borrow().id,
266                    element.borrow().base_type
267                );
268            }
269            LoweredElement::ComponentPlaceholder { .. } => unreachable!(),
270        }
271    }
272}
273
274pub struct LoweredSubComponent {
275    sub_component: SubComponent,
276    mapping: LoweredSubComponentMapping,
277}
278
279#[derive(Default)]
280pub struct LoweringState {
281    global_properties: HashMap<NamedReference, MemberReference>,
282    sub_components: TiVec<SubComponentIdx, LoweredSubComponent>,
283    sub_component_mapping: HashMap<ByAddress<Rc<Component>>, SubComponentIdx>,
284    #[cfg(feature = "bundle-translations")]
285    pub translation_builder: Option<crate::translations::TranslationsBuilder>,
286    /// Counter for the unique `struct_assignment{n}` local variable names. Local
287    /// to one lowering (a fresh `LoweringState` is created per backend), so the
288    /// numbering is deterministic regardless of how many backends run.
289    struct_assignment_count: usize,
290}
291
292impl LoweringState {
293    /// Return a fresh unique name for a temporary used when lowering an
294    /// assignment to a struct field.
295    pub fn unique_struct_assignment_name(&mut self) -> SmolStr {
296        let n = self.struct_assignment_count;
297        self.struct_assignment_count += 1;
298        format_smolstr!("struct_assignment{n}")
299    }
300
301    pub fn map_property_reference(&self, from: &NamedReference) -> MemberReference {
302        if let Some(x) = self.global_properties.get(from) {
303            return x.clone();
304        }
305
306        let element = from.element();
307        let sc = self.sub_component(&element.borrow().enclosing_component.upgrade().unwrap());
308        sc.mapping.map_property_reference(from, self)
309    }
310
311    fn sub_component<'a>(&'a self, component: &Rc<Component>) -> &'a LoweredSubComponent {
312        &self.sub_components[self.sub_component_idx(component)]
313    }
314
315    /// Returns the `row_child_templates` from an already-lowered sub-component.
316    /// Used by the parent's layout expression lowering to read template info
317    /// from a repeated Row that was lowered earlier.
318    pub fn row_child_templates(
319        &self,
320        component: &Rc<Component>,
321    ) -> Option<Vec<super::RowChildTemplateInfo>> {
322        self.sub_components[self.sub_component_idx(component)]
323            .sub_component
324            .row_child_templates
325            .clone()
326    }
327
328    fn sub_component_idx(&self, component: &Rc<Component>) -> SubComponentIdx {
329        *self.sub_component_mapping.get(&ByAddress(component.clone())).unwrap_or_else(|| {
330            debug_assert!(
331                false,
332                "no entry found for key: component id='{}', available keys: {:?}",
333                component.id,
334                self.sub_component_mapping.keys().map(|k| k.0.id.clone()).collect::<Vec<_>>()
335            );
336            unreachable!(
337                "component must be registered before querying sub_component_idx: '{}'",
338                component.id
339            )
340        })
341    }
342
343    fn push_sub_component(&mut self, sc: LoweredSubComponent) -> SubComponentIdx {
344        self.sub_components.push_and_get_key(sc)
345    }
346}
347
348fn component_id(component: &Rc<Component>) -> SmolStr {
349    if component.is_global() {
350        component.root_element.borrow().id.clone()
351    } else if component.from_library.get() {
352        component.id.clone()
353    } else if component.id.is_empty() {
354        format_smolstr!("Component_{}", component.root_element.borrow().id)
355    } else {
356        format_smolstr!("{}_{}", component.id, component.root_element.borrow().id)
357    }
358}
359
360fn lower_sub_component(
361    component: &Rc<Component>,
362    state: &mut LoweringState,
363    parent_context: Option<&ExpressionLoweringCtxInner>,
364    compiler_config: &CompilerConfiguration,
365) -> LoweredSubComponent {
366    let mut sub_component = SubComponent {
367        name: component_id(component),
368        properties: Default::default(),
369        callbacks: Default::default(),
370        functions: Default::default(),
371        items: Default::default(),
372        repeated: Default::default(),
373        component_containers: Default::default(),
374        popup_windows: Default::default(),
375        menu_item_trees: Vec::new(),
376        timers: Default::default(),
377        sub_components: Default::default(),
378        property_init: Default::default(),
379        change_callbacks: Default::default(),
380        animations: Default::default(),
381        two_way_bindings: Default::default(),
382        const_properties: Default::default(),
383        pre_init_code: Default::default(),
384        init_code: Default::default(),
385        geometries: Default::default(),
386        // just initialize to dummy expression right now and it will be set later
387        layout_info_h: super::Expression::BoolLiteral(false).into(),
388        layout_info_v: super::Expression::BoolLiteral(false).into(),
389        child_of_layout: component.root_element.borrow().child_of_layout,
390        grid_layout_input_for_repeated: None,
391        flexbox_layout_item_info_for_repeated: None,
392        cross_axis_self_alignment_for_repeated: None,
393        layout_order_for_repeated: None,
394        layout_info_v_constrained_for_repeated: None,
395        layout_info_v_at_cross_width_for_repeated: None,
396        grid_row_child_cross_width: None,
397        is_repeated_row: component
398            .root_element
399            .borrow()
400            .grid_layout_cell
401            .as_ref()
402            .is_some_and(|c| c.borrow().child_items.is_some()),
403        grid_layout_children: Default::default(),
404        row_child_templates: None,
405        accessible_prop: Default::default(),
406        element_infos: Default::default(),
407        prop_analysis: Default::default(),
408        debug_info: compiler_config.debug_info.then(|| super::debug_info::SubComponentDebugInfo {
409            source_location: crate::diagnostics::Spanned::to_source_location(
410                &*component.root_element.borrow(),
411            ),
412            items: Default::default(),
413            sub_component_use_sites: Default::default(),
414        }),
415    };
416    let mut mapping = LoweredSubComponentMapping::default();
417    let mut repeated = TiVec::new();
418    let mut accessible_prop = Vec::new();
419    let mut change_callbacks = Vec::new();
420
421    if let Some(parent) = component.parent_element() {
422        // Add properties for the model data and index
423        if parent.borrow().repeated.as_ref().is_some_and(|x| !x.is_conditional_element) {
424            sub_component.properties.push(Property {
425                name: "model_data".into(),
426                ty: crate::expression_tree::Expression::RepeaterModelReference {
427                    element: component.parent_element.borrow().clone(),
428                }
429                .ty(),
430                ..Property::default()
431            });
432            sub_component.properties.push(Property {
433                name: "model_index".into(),
434                ty: Type::Int32,
435                ..Property::default()
436            });
437        }
438    };
439
440    let s: Option<ElementRc> = None;
441    let mut repeater_offset = 0;
442    crate::object_tree::recurse_elem(&component.root_element, &s, &mut |element, parent| {
443        let elem = element.borrow();
444        for (p, x) in &elem.property_declarations {
445            if x.is_alias.is_some() {
446                continue;
447            }
448            let reference = if let Type::Function(function) = &x.property_type {
449                // TODO: Function could wrap the Rc<langtype::Function>
450                //       instead of cloning the return type and args?
451                let index = sub_component.functions.push_and_get_key(Function {
452                    name: p.clone(),
453                    ret_ty: function.return_type.clone(),
454                    args: function.args.clone(),
455                    // will be replaced later
456                    code: super::Expression::CodeBlock(Vec::new()).into(),
457                    use_count: Default::default(),
458                });
459                index.into()
460            } else if let Type::Callback(callback) = &x.property_type {
461                let index = sub_component.callbacks.push_and_get_key(Callback {
462                    name: format_smolstr!("{}_{}", elem.id, p),
463                    ret_ty: callback.return_type.clone(),
464                    args: callback.args.clone(),
465                    ty: Type::Callback(callback.clone()),
466                    use_count: 0.into(),
467                    needs_tracker: x.expose_in_public_api,
468                });
469                index.into()
470            } else {
471                let index = sub_component.properties.push_and_get_key(Property {
472                    name: format_smolstr!("{}_{}", elem.id, p),
473                    ty: x.property_type.clone(),
474                    ..Property::default()
475                });
476                index.into()
477            };
478            mapping.property_mapping.insert(
479                NamedReference::new(element, p.clone()),
480                MemberReference::Relative {
481                    parent_level: 0,
482                    local_reference: LocalMemberReference {
483                        sub_component_path: Vec::new(),
484                        reference,
485                    },
486                },
487            );
488        }
489        if elem.repeated.is_some() {
490            let parent = if elem.is_component_placeholder { parent.clone() } else { None };
491
492            mapping.element_mapping.insert(
493                element.clone().into(),
494                LoweredElement::Repeated {
495                    repeated_index: repeated.push_and_get_key((element.clone(), parent)),
496                },
497            );
498            mapping.repeater_count += 1;
499            return None;
500        }
501        match &elem.base_type {
502            ElementType::Component(comp) => {
503                let ty = state.sub_component_idx(comp);
504                let sub_component_index =
505                    sub_component.sub_components.push_and_get_key(SubComponentInstance {
506                        ty,
507                        name: elem.id.clone(),
508                        index_in_tree: *elem.item_index.get().unwrap(),
509                        index_of_first_child_in_tree: *elem
510                            .item_index_of_first_children
511                            .get()
512                            .unwrap(),
513                        repeater_offset,
514                    });
515                if let Some(debug_info) = sub_component.debug_info.as_mut() {
516                    let added_index = debug_info
517                        .sub_component_use_sites
518                        .push_and_get_key(crate::diagnostics::Spanned::to_source_location(&*elem));
519                    debug_assert_eq!(added_index, sub_component_index);
520                }
521                mapping.element_mapping.insert(
522                    element.clone().into(),
523                    LoweredElement::SubComponent { sub_component_index },
524                );
525                repeater_offset += comp.repeater_count();
526            }
527
528            ElementType::Native(n) => {
529                let item_index = sub_component.items.push_and_get_key(Item {
530                    ty: n.clone(),
531                    name: elem.id.clone(),
532                    index_in_tree: *elem.item_index.get().unwrap(),
533                });
534                if let Some(debug_info) = sub_component.debug_info.as_mut() {
535                    let primary = elem.debug.first();
536                    let source_location = crate::diagnostics::Spanned::to_source_location(&*elem);
537                    let added_index =
538                        debug_info.items.push_and_get_key(super::debug_info::ItemDebugInfo {
539                            source_location,
540                            qualified_id: primary.and_then(|d| d.qualified_id.clone()),
541                            element_hash: primary.map(|d| d.element_hash).unwrap_or_default(),
542                            is_injected_wrapper_element: elem.is_injected_wrapper_element,
543                        });
544                    debug_assert_eq!(added_index, item_index);
545                }
546                mapping
547                    .element_mapping
548                    .insert(element.clone().into(), LoweredElement::NativeItem { item_index });
549            }
550            _ => unreachable!(),
551        };
552        for (key, nr) in &elem.accessibility_props.0 {
553            // TODO: we also want to split by type (role/string/...)
554            let enum_value =
555                crate::generator::to_pascal_case(key.strip_prefix("accessible-").unwrap());
556            accessible_prop.push((*elem.item_index.get().unwrap(), enum_value, nr.clone()));
557        }
558
559        for (prop, expr) in &elem.change_callbacks {
560            change_callbacks
561                .push((NamedReference::new(element, prop.clone()), expr.borrow().clone()));
562        }
563
564        if compiler_config.debug_info {
565            let element_infos = elem.element_infos();
566            if !element_infos.is_empty() {
567                sub_component.element_infos.insert(*elem.item_index.get().unwrap(), element_infos);
568            }
569        }
570
571        Some(element.clone())
572    });
573
574    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: parent_context, component };
575    let mut ctx = ExpressionLoweringCtx { inner, state };
576
577    // Lower repeated components first, so their sub-components (e.g. Row) are available
578    // when lowering layout expressions that need to read row_child_templates.
579    sub_component.repeated = repeated
580        .into_iter()
581        .map(|(elem, parent)| {
582            lower_repeated_component(&elem, parent, &sub_component, &mut ctx, compiler_config)
583        })
584        .collect();
585    for s in &mut sub_component.sub_components {
586        s.repeater_offset +=
587            (sub_component.repeated.len() + sub_component.component_containers.len()) as u32;
588    }
589
590    crate::generator::handle_property_bindings_init(component, |e, p, binding| {
591        let nr = NamedReference::new(e, p.clone());
592        let prop = ctx.map_property_reference(&nr);
593
594        if let Type::Function { .. } = nr.ty() {
595            let MemberReference::Relative { parent_level, local_reference } = prop else {
596                unreachable!()
597            };
598            assert!(parent_level == 0);
599            assert!(local_reference.sub_component_path.is_empty());
600            let LocalMemberIndex::Function(function_index) = local_reference.reference else {
601                unreachable!()
602            };
603
604            sub_component.functions[function_index]
605                .code
606                .replace(super::lower_expression::lower_expression(&binding.expression, &mut ctx));
607
608            return;
609        }
610
611        for tw in &binding.two_way_bindings {
612            sub_component.two_way_bindings.push(match tw {
613                crate::expression_tree::TwoWayBinding::Property { property, field_access } => {
614                    TwoWayBinding {
615                        prop1: prop.local(),
616                        prop2: ctx.map_property_reference(property),
617                        field_access: field_access.clone(),
618                        is_model: None,
619                    }
620                }
621                crate::expression_tree::TwoWayBinding::ModelData {
622                    repeated_element,
623                    field_access,
624                } => TwoWayBinding {
625                    prop1: prop.local(),
626                    prop2: super::lower_expression::repeater_special_property(
627                        repeated_element,
628                        component,
629                        PropertyIdx::REPEATER_DATA,
630                    ),
631                    field_access: field_access.clone(),
632                    is_model: Some(PropertyIdx::REPEATER_INDEX),
633                },
634            });
635        }
636        if !matches!(binding.expression, tree_Expression::Invalid) {
637            let expression =
638                super::lower_expression::lower_expression(&binding.expression, &mut ctx).into();
639
640            let is_constant = binding.analysis.as_ref().is_some_and(|a| a.is_const);
641            let animation = binding
642                .animation
643                .as_ref()
644                .filter(|_| !is_constant)
645                .map(|a| super::lower_expression::lower_animation(a, &mut ctx));
646
647            sub_component.prop_analysis.insert(
648                prop.clone(),
649                PropAnalysis {
650                    property_init: Some(sub_component.property_init.len()),
651                    analysis: get_property_analysis(e, p),
652                },
653            );
654
655            let kind = if matches!(
656                e.borrow().lookup_property(p, PropertyLookupMode::InternalName).property_type,
657                Type::Struct(s) if matches!(s.name, StructName::Builtin(BuiltinStruct::StateInfo))
658            ) {
659                BindingKind::State
660            } else if is_constant {
661                BindingKind::Constant
662            } else {
663                BindingKind::Normal
664            };
665
666            sub_component.property_init.push((
667                prop.clone(),
668                BindingExpression { expression, animation, kind, use_count: 0.into() },
669            ));
670        }
671
672        if e.borrow()
673            .property_analysis
674            .borrow()
675            .get(p)
676            .is_none_or(|a| a.is_set || a.is_set_externally)
677            && let Some(anim) = binding.animation.as_ref()
678        {
679            match super::lower_expression::lower_animation(anim, &mut ctx) {
680                Animation::Static(anim) => {
681                    sub_component.animations.insert(prop.local(), anim);
682                }
683                Animation::Transition(_) => {
684                    // Cannot set a property with a transition anyway
685                }
686            }
687        }
688    });
689
690    sub_component.popup_windows = component
691        .popup_windows
692        .borrow()
693        .iter()
694        .map(|popup| lower_popup_component(popup, &mut ctx, compiler_config))
695        .collect();
696
697    sub_component.menu_item_trees = component
698        .menu_item_tree
699        .borrow()
700        .iter()
701        .map(|c| {
702            let sc = lower_sub_component(c, ctx.state, Some(&ctx.inner), compiler_config);
703            ItemTree {
704                tree: make_tree(ctx.state, &c.root_element, &sc, &[]),
705                root: ctx.state.push_sub_component(sc),
706            }
707        })
708        .collect();
709
710    sub_component.timers = component.timers.borrow().iter().map(|t| lower_timer(t, &ctx)).collect();
711
712    crate::generator::for_each_const_properties(component, |elem, n| {
713        let x = ctx.map_property_reference(&NamedReference::new(elem, n.clone()));
714        // ensure that all const properties have analysis
715        sub_component.prop_analysis.entry(x.clone()).or_insert_with(|| PropAnalysis {
716            property_init: None,
717            analysis: get_property_analysis(elem, n),
718        });
719        sub_component.const_properties.push(x.local());
720    });
721
722    sub_component.pre_init_code = component
723        .init_code
724        .borrow()
725        .font_registration_code
726        .iter()
727        .map(|e| super::lower_expression::lower_expression(e, &mut ctx).into())
728        .collect();
729
730    sub_component.init_code = component
731        .init_code
732        .borrow()
733        .iter_without_font_registration()
734        .map(|e| super::lower_expression::lower_expression(e, &mut ctx).into())
735        .collect();
736
737    sub_component.layout_info_h = super::lower_layout_expression::get_layout_info(
738        &component.root_element,
739        &mut ctx,
740        &component.root_constraints.borrow(),
741        crate::layout::Orientation::Horizontal,
742        None,
743    )
744    .into();
745    // Measure the root's height for its preferred width, not an unbounded one, so a
746    // height-for-width Image doesn't report infinite height (mirrors the interpreter).
747    let v_cross_constraint = component
748        .root_element
749        .borrow()
750        .layout_info_v_with_constraint
751        .is_some()
752        .then(|| {
753            super::lower_layout_expression::default_cross_axis_constraint(&component.root_element)
754        })
755        .flatten();
756    sub_component.layout_info_v = super::lower_layout_expression::get_layout_info(
757        &component.root_element,
758        &mut ctx,
759        &component.root_constraints.borrow(),
760        crate::layout::Orientation::Vertical,
761        v_cross_constraint,
762    )
763    .into();
764    if component.root_element.borrow().child_of_flexbox {
765        let root_elem = &component.root_element;
766        let has_flex_binding = ["cross-axis-self-alignment", "layout-order"]
767            .iter()
768            .any(|name| crate::layout::binding_reference(root_elem, name).is_some());
769        let v_constrained =
770            super::lower_layout_expression::get_layout_info_v_constrained_for_repeated(
771                &mut ctx,
772                root_elem,
773                &component.root_constraints.borrow(),
774            );
775        // Generate the flex item-info accessor when the element sets flex
776        // properties, or when it is a height-for-width instance in a column
777        // flex, which needs the constrained vertical info.
778        if has_flex_binding || v_constrained.is_some() {
779            sub_component.flexbox_layout_item_info_for_repeated = Some(
780                super::lower_layout_expression::get_flexbox_layout_item_info_for_repeated(
781                    &mut ctx, root_elem,
782                )
783                .into(),
784            );
785        }
786        sub_component.layout_info_v_constrained_for_repeated = v_constrained.map(Into::into);
787        sub_component.layout_info_v_at_cross_width_for_repeated =
788            super::lower_layout_expression::get_layout_info_v_at_cross_width_for_repeated(
789                &mut ctx,
790                root_elem,
791                &component.root_constraints.borrow(),
792                true,
793            )
794            .map(Into::into);
795    } else if let Some(box_orientation) =
796        component.root_element.borrow().parent_box_layout_orientation
797    {
798        // A repeated element in a box layout returns its `cross-axis-self-alignment`
799        // through the generated `layout_item_info`, on the cross axis only, and its
800        // `layout-order` on the main axis only.
801        if let Some(nr) =
802            crate::layout::binding_reference(&component.root_element, "cross-axis-self-alignment")
803        {
804            sub_component.cross_axis_self_alignment_for_repeated = Some((
805                box_orientation.orthogonal(),
806                super::Expression::PropertyReference(ctx.map_property_reference(&nr)).into(),
807            ));
808        }
809        if let Some(nr) = crate::layout::binding_reference(&component.root_element, "layout-order")
810        {
811            sub_component.layout_order_for_repeated = Some((
812                box_orientation,
813                super::Expression::PropertyReference(ctx.map_property_reference(&nr)).into(),
814            ));
815        }
816        // The parent box layout measures a height-for-width instance at the
817        // width it lays it out at, through `layout_item_info_at_cross_width` —
818        // the plain `layout_info` measures at the instance's preferred width.
819        // A vertical layout queries it from its main-axis pass, a horizontal
820        // one from its ortho measure pass.
821        sub_component.layout_info_v_at_cross_width_for_repeated =
822            super::lower_layout_expression::get_layout_info_v_at_cross_width_for_repeated(
823                &mut ctx,
824                &component.root_element,
825                &component.root_constraints.borrow(),
826                false,
827            )
828            .map(Into::into);
829    }
830
831    if component.root_element.borrow().grid_layout_cell.is_some() {
832        // A GridLayout measures a height-for-width instance at the column width
833        // it assigns, through `layout_item_info_at_cross_width`; the plain
834        // `layout_info` measures at the instance's preferred width.
835        sub_component.layout_info_v_at_cross_width_for_repeated =
836            super::lower_layout_expression::get_layout_info_v_at_cross_width_for_repeated(
837                &mut ctx,
838                &component.root_element,
839                &component.root_constraints.borrow(),
840                false,
841            )
842            .map(Into::into);
843    }
844
845    if let Some(grid_layout_cell) = component.root_element.borrow().grid_layout_cell.as_ref() {
846        let grid_cell_ref = grid_layout_cell.borrow();
847        sub_component.grid_layout_input_for_repeated = Some(
848            super::lower_layout_expression::get_grid_layout_input_for_repeated(
849                &mut ctx,
850                &grid_cell_ref,
851            )
852            .into(),
853        );
854
855        // Store constraints for children of the Row
856        if let Some(children_constraints) = grid_cell_ref.child_items.as_ref() {
857            let mut row_child_templates = Vec::new();
858            for child_template in children_constraints.iter() {
859                match child_template {
860                    crate::layout::RowChildTemplate::Static(layout_item) => {
861                        let layout_info_h = super::lower_layout_expression::get_layout_info(
862                            &layout_item.element,
863                            &mut ctx,
864                            &layout_item.constraints,
865                            crate::layout::Orientation::Horizontal,
866                            None,
867                        );
868                        let layout_info_v = super::lower_layout_expression::get_layout_info(
869                            &layout_item.element,
870                            &mut ctx,
871                            &layout_item.constraints,
872                            crate::layout::Orientation::Vertical,
873                            None,
874                        );
875                        let child_index = sub_component.grid_layout_children.push_and_get_key(
876                            super::GridLayoutChildLayoutInfo {
877                                layout_info_h: layout_info_h.into(),
878                                layout_info_v: layout_info_v.into(),
879                            },
880                        );
881                        row_child_templates
882                            .push(super::RowChildTemplateInfo::Static { child_index });
883                    }
884                    crate::layout::RowChildTemplate::Repeated { repeated_element, .. } => {
885                        // Measure this child at the column width the grid
886                        // assigns it. The expression is the same for every
887                        // child of the Row, so only the first one that needs
888                        // it builds it.
889                        let cross_width = super::lower_layout_expression::grid_measure_cross_width(
890                            &mut ctx,
891                            repeated_element,
892                            super::lower_layout_expression::GridMeasureIndex::RowChild,
893                        );
894                        let measure_at_cross_width = cross_width.is_some();
895                        if sub_component.grid_row_child_cross_width.is_none() {
896                            sub_component.grid_row_child_cross_width = cross_width.map(Into::into);
897                        }
898                        // Inner repeater: layout_info is computed at runtime per instance.
899                        if let Some(super::lower_to_item_tree::LoweredElement::Repeated {
900                            repeated_index,
901                        }) = mapping.element_mapping.get(&repeated_element.clone().into())
902                        {
903                            row_child_templates.push(super::RowChildTemplateInfo::Repeated {
904                                repeater_index: *repeated_index,
905                                measure_at_cross_width,
906                            });
907                        }
908                    }
909                }
910            }
911            // Always set row_child_templates (even if empty) to mark this as a Row sub-component.
912            // An empty row_child_templates (Some([])) means 0 cells per sub-component — correct for empty Rows.
913            // Leaving it as None would incorrectly treat it as a column-repeater (1 cell per sub-component).
914            sub_component.row_child_templates = Some(row_child_templates);
915        }
916    }
917
918    sub_component.accessible_prop = accessible_prop
919        .into_iter()
920        .map(|(idx, key, nr)| {
921            let prop = ctx.map_property_reference(&nr);
922            let expr = match nr.ty() {
923                Type::Bool => super::Expression::Condition {
924                    condition: super::Expression::PropertyReference(prop).into(),
925                    true_expr: super::Expression::StringLiteral("true".into()).into(),
926                    false_expr: super::Expression::StringLiteral("false".into()).into(),
927                },
928                Type::Int32 | Type::Float32 => super::Expression::Cast {
929                    from: super::Expression::PropertyReference(prop).into(),
930                    to: Type::String,
931                },
932                Type::String => super::Expression::PropertyReference(prop),
933                Type::Enumeration(ref e) if e.name == "AccessibleRole" => {
934                    super::Expression::PropertyReference(prop)
935                }
936                Type::Enumeration(_) => super::Expression::Cast {
937                    from: super::Expression::PropertyReference(prop).into(),
938                    to: Type::String,
939                },
940                Type::Callback(callback) => super::Expression::CallBackCall {
941                    callback: prop,
942                    arguments: (0..callback.args.len())
943                        .map(|index| super::Expression::FunctionParameterReference { index })
944                        .collect(),
945                },
946                _ => panic!("Invalid type for accessible property"),
947            };
948
949            ((idx, key), expr.into())
950        })
951        .collect();
952
953    sub_component.change_callbacks = change_callbacks
954        .into_iter()
955        .map(|(nr, exprs)| {
956            let prop = ctx.map_property_reference(&nr);
957            let expr = super::lower_expression::lower_expression(
958                &tree_Expression::CodeBlock(exprs),
959                &mut ctx,
960            );
961            (prop, expr.into())
962        })
963        .collect();
964
965    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |element, _| {
966        let elem = element.borrow();
967        if elem.repeated.is_some() {
968            return;
969        };
970        let Some(geom) = &elem.geometry_props else { return };
971        let item_index = *elem.item_index.get().unwrap() as usize;
972        if item_index >= sub_component.geometries.len() {
973            sub_component.geometries.resize(item_index + 1, Default::default());
974        }
975        sub_component.geometries[item_index] = Some(lower_geometry(geom, &ctx).into());
976    });
977
978    LoweredSubComponent { sub_component, mapping }
979}
980
981fn lower_geometry(
982    geom: &crate::object_tree::GeometryProps,
983    ctx: &ExpressionLoweringCtx<'_>,
984) -> super::Expression {
985    let mut fields = BTreeMap::default();
986    let mut values = BTreeMap::default();
987    for (f, v) in [("x", &geom.x), ("y", &geom.y), ("width", &geom.width), ("height", &geom.height)]
988    {
989        fields.insert(f.into(), Type::LogicalLength);
990        values
991            .insert(f.into(), super::Expression::PropertyReference(ctx.map_property_reference(v)));
992    }
993    super::Expression::Struct { ty: Arc::new(Struct::new(fields, StructName::None)), values }
994}
995
996fn get_property_analysis(elem: &ElementRc, p: &str) -> crate::object_tree::PropertyAnalysis {
997    let mut a = elem.borrow().property_analysis.borrow().get(p).cloned().unwrap_or_default();
998    let mut elem = elem.clone();
999    loop {
1000        if let Some(d) = elem.borrow().property_declarations.get(p) {
1001            if let Some(nr) = &d.is_alias {
1002                a.merge(&get_property_analysis(&nr.element(), nr.name()));
1003            }
1004            return a;
1005        }
1006        let base = elem.borrow().base_type.clone();
1007        match base {
1008            ElementType::Native(n) if n.properties.get(p).is_some_and(|p| p.is_native_output()) => {
1009                a.is_set = true;
1010            }
1011            ElementType::Component(c) => {
1012                elem = c.root_element.clone();
1013                if let Some(a2) = elem.borrow().property_analysis.borrow().get(p) {
1014                    a.merge_with_base(a2);
1015                }
1016                continue;
1017            }
1018            _ => (),
1019        };
1020        return a;
1021    }
1022}
1023
1024fn lower_repeated_component(
1025    elem: &ElementRc,
1026    parent_component_container: Option<ElementRc>,
1027    sub_component: &SubComponent,
1028    ctx: &mut ExpressionLoweringCtx,
1029    compiler_config: &CompilerConfiguration,
1030) -> RepeatedElement {
1031    let e = elem.borrow();
1032    let component = e.base_type.as_component().clone();
1033    let repeated = e.repeated.as_ref().unwrap();
1034
1035    let sc = lower_sub_component(&component, ctx.state, Some(&ctx.inner), compiler_config);
1036
1037    let listview = repeated.is_listview.as_ref().map(|lv| {
1038        let geom = component.root_element.borrow().geometry_props.clone().unwrap();
1039        ListViewInfo {
1040            content_y: ctx.map_property_reference(&lv.content_y),
1041            content_height: lv
1042                .content_height
1043                .as_ref()
1044                .map(|content_height| ctx.map_property_reference(content_height)),
1045            content_width: lv
1046                .content_width
1047                .as_ref()
1048                .map(|content_width| ctx.map_property_reference(content_width)),
1049            listview_height: ctx.map_property_reference(&lv.listview_height),
1050            listview_width: ctx.map_property_reference(&lv.listview_width),
1051            prop_y: sc.mapping.map_property_reference(&geom.y, ctx.state),
1052            prop_height: sc.mapping.map_property_reference(&geom.height, ctx.state),
1053        }
1054    });
1055
1056    let parent_index = parent_component_container.map(|p| *p.borrow().item_index.get().unwrap());
1057    let container_item_index =
1058        parent_index.and_then(|pii| sub_component.items.position(|i| i.index_in_tree == pii));
1059
1060    let dynamic_z = match &e.z_order {
1061        Some(object_tree::ZOrder::PerInstance(nr)) => {
1062            Some(sc.mapping.map_property_reference(nr, ctx.state))
1063        }
1064        _ => None,
1065    };
1066
1067    let tree = make_tree(ctx.state, &component.root_element, &sc, &[]);
1068    let root = ctx.state.push_sub_component(sc);
1069    // Register the repeated component in the mapping so it can be looked up
1070    ctx.state.sub_component_mapping.insert(ByAddress(component.clone()), root);
1071
1072    RepeatedElement {
1073        model: super::lower_expression::lower_expression(&repeated.model, ctx).into(),
1074        sub_tree: ItemTree { tree, root },
1075        index_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_INDEX),
1076        data_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_DATA),
1077        dynamic_z,
1078        index_in_tree: *e.item_index.get().unwrap(),
1079        listview,
1080        container_item_index,
1081    }
1082}
1083
1084fn lower_popup_component(
1085    popup: &object_tree::PopupWindow,
1086    ctx: &mut ExpressionLoweringCtx,
1087    compiler_config: &CompilerConfiguration,
1088) -> PopupWindow {
1089    let sc = lower_sub_component(&popup.component, ctx.state, Some(&ctx.inner), compiler_config);
1090    use super::Expression::PropertyReference as PR;
1091    let position = super::lower_expression::make_struct(
1092        BuiltinStruct::LogicalPosition,
1093        [
1094            ("x", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.x, ctx.state))),
1095            ("y", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.y, ctx.state))),
1096        ],
1097    );
1098
1099    let item_tree = ItemTree {
1100        tree: make_tree(ctx.state, &popup.component.root_element, &sc, &[]),
1101        root: ctx.state.push_sub_component(sc),
1102    };
1103    PopupWindow { item_tree, position: position.into(), is_tooltip: popup.is_tooltip }
1104}
1105
1106fn lower_timer(timer: &object_tree::Timer, ctx: &ExpressionLoweringCtx) -> Timer {
1107    Timer {
1108        interval: super::Expression::PropertyReference(ctx.map_property_reference(&timer.interval))
1109            .into(),
1110        running: super::Expression::PropertyReference(ctx.map_property_reference(&timer.running))
1111            .into(),
1112        // TODO: this calls a callback instead of inlining the callback code directly
1113        triggered: super::Expression::CallBackCall {
1114            callback: ctx.map_property_reference(&timer.triggered),
1115            arguments: Vec::new(),
1116        }
1117        .into(),
1118    }
1119}
1120
1121/// Lower the globals (but not their expressions as we first need to lower all the global to get proper mapping in the state)
1122fn lower_global(
1123    global: &Rc<Component>,
1124    global_index: GlobalIdx,
1125    state: &mut LoweringState,
1126) -> GlobalComponent {
1127    let mut properties = TiVec::new();
1128    let mut callbacks = TiVec::new();
1129    let mut const_properties = TiVec::new();
1130    let mut prop_analysis = TiVec::new();
1131    let mut functions = TiVec::new();
1132
1133    for (p, x) in &global.root_element.borrow().property_declarations {
1134        if x.is_alias.is_some() {
1135            continue;
1136        }
1137        let nr = NamedReference::new(&global.root_element, p.clone());
1138
1139        if let Type::Function(function) = &x.property_type {
1140            // TODO: wrap the Rc<langtype::Function> instead of cloning
1141            let function_index: FunctionIdx = functions.push_and_get_key(Function {
1142                name: p.clone(),
1143                ret_ty: function.return_type.clone(),
1144                args: function.args.clone(),
1145                // will be replaced later
1146                code: super::Expression::CodeBlock(Vec::new()).into(),
1147                use_count: Default::default(),
1148            });
1149            state.global_properties.insert(
1150                nr.clone(),
1151                MemberReference::Global { global_index, member: function_index.into() },
1152            );
1153            continue;
1154        } else if let Type::Callback(cb) = &x.property_type {
1155            let callback_index: CallbackIdx = callbacks.push_and_get_key(Callback {
1156                name: p.clone(),
1157                ret_ty: cb.return_type.clone(),
1158                args: cb.args.clone(),
1159                ty: x.property_type.clone(),
1160                use_count: 0.into(),
1161                needs_tracker: x.expose_in_public_api,
1162            });
1163            state.global_properties.insert(
1164                nr.clone(),
1165                MemberReference::Global { global_index, member: callback_index.into() },
1166            );
1167            continue;
1168        }
1169
1170        let property_index: PropertyIdx = properties.push_and_get_key(Property {
1171            name: p.clone(),
1172            ty: x.property_type.clone(),
1173            ..Property::default()
1174        });
1175
1176        const_properties.push(nr.is_constant());
1177
1178        prop_analysis.push(
1179            global
1180                .root_element
1181                .borrow()
1182                .property_analysis
1183                .borrow()
1184                .get(p)
1185                .cloned()
1186                .unwrap_or_default(),
1187        );
1188        state.global_properties.insert(
1189            nr.clone(),
1190            MemberReference::Global { global_index, member: property_index.into() },
1191        );
1192    }
1193
1194    let is_builtin = if let Some(builtin) = global.root_element.borrow().native_class() {
1195        // We just generate the property so we know how to address them
1196        for (p, x) in &builtin.properties {
1197            let property_index = properties.push_and_get_key(Property {
1198                name: p.clone(),
1199                ty: x.ty.clone(),
1200                ..Property::default()
1201            });
1202            let nr = NamedReference::new(&global.root_element, p.clone());
1203            state.global_properties.insert(
1204                nr,
1205                MemberReference::Global { global_index, member: property_index.into() },
1206            );
1207            prop_analysis.push(PropertyAnalysis {
1208                // Assume that a builtin global property can always be set from the builtin code
1209                is_set_externally: true,
1210                ..global
1211                    .root_element
1212                    .borrow()
1213                    .property_analysis
1214                    .borrow()
1215                    .get(p)
1216                    .cloned()
1217                    .unwrap_or_default()
1218            });
1219        }
1220        true
1221    } else {
1222        false
1223    };
1224
1225    GlobalComponent {
1226        name: global.root_element.borrow().id.clone(),
1227        init_values: BTreeMap::new(),
1228        properties,
1229        callbacks,
1230        functions,
1231        change_callbacks: BTreeMap::new(),
1232        const_properties,
1233        public_properties: Default::default(),
1234        private_properties: global.private_properties.borrow().clone(),
1235        exported: !global.exported_global_names.borrow().is_empty(),
1236        aliases: global.global_aliases(),
1237        is_builtin,
1238        from_library: global.from_library.get(),
1239        prop_analysis,
1240    }
1241}
1242
1243fn lower_global_expressions(
1244    global: &Rc<Component>,
1245    state: &mut LoweringState,
1246    lowered: &mut GlobalComponent,
1247) {
1248    // Note that this mapping doesn't contain anything useful, everything is in the state
1249    let mapping = LoweredSubComponentMapping::default();
1250    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: None, component: global };
1251    let mut ctx = ExpressionLoweringCtx { inner, state };
1252
1253    for (prop, binding) in global.root_element.borrow().bindings_including_synthetic() {
1254        assert!(binding.borrow().two_way_bindings.is_empty());
1255        assert!(binding.borrow().animation.is_none());
1256        let expression =
1257            super::lower_expression::lower_expression(&binding.borrow().expression, &mut ctx);
1258
1259        let nr = NamedReference::new(&global.root_element, prop.clone());
1260        let member_index = match &ctx.state.global_properties[&nr] {
1261            MemberReference::Global {
1262                member: LocalMemberIndex::Function(function_index), ..
1263            } => {
1264                lowered.functions[*function_index].code.replace(expression);
1265                continue;
1266            }
1267            MemberReference::Global { member, .. } => member.clone(),
1268            _ => unreachable!(),
1269        };
1270        let is_constant = binding.borrow().analysis.as_ref().is_some_and(|a| a.is_const);
1271        lowered.init_values.insert(
1272            member_index,
1273            BindingExpression {
1274                expression: expression.into(),
1275                animation: None,
1276                kind: if is_constant { BindingKind::Constant } else { BindingKind::Normal },
1277                use_count: 0.into(),
1278            },
1279        );
1280    }
1281
1282    for (prop, expr) in &global.root_element.borrow().change_callbacks {
1283        let nr = NamedReference::new(&global.root_element, prop.clone());
1284        let MemberReference::Global { member: LocalMemberIndex::Property(property_index), .. } =
1285            ctx.state.global_properties[&nr]
1286        else {
1287            unreachable!()
1288        };
1289        let expression = super::lower_expression::lower_expression(
1290            &tree_Expression::CodeBlock(expr.borrow().clone()),
1291            &mut ctx,
1292        );
1293        lowered.change_callbacks.insert(property_index, expression.into());
1294    }
1295
1296    if let Some(builtin) = global.root_element.borrow().native_class() {
1297        if lowered.exported {
1298            lowered.public_properties = builtin
1299                .properties
1300                .iter()
1301                .map(|(p, c)| {
1302                    let property_reference = mapping.map_property_reference(
1303                        &NamedReference::new(&global.root_element, p.clone()),
1304                        state,
1305                    );
1306                    (
1307                        p.clone(),
1308                        PublicProperty {
1309                            display_name: p.clone(),
1310                            ty: c.ty.clone(),
1311                            prop: property_reference,
1312                            visibility: c.property_visibility,
1313                        },
1314                    )
1315                })
1316                .collect()
1317        }
1318    } else {
1319        lowered.public_properties = public_properties(global, &mapping, state);
1320    }
1321}
1322
1323fn make_tree(
1324    state: &LoweringState,
1325    element: &ElementRc,
1326    component: &LoweredSubComponent,
1327    sub_component_path: &[SubComponentInstanceIdx],
1328) -> TreeNode {
1329    let e = element.borrow();
1330    let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path));
1331    let repeater_count = component.mapping.repeater_count;
1332
1333    let zero = || ZSource::Expression(super::Expression::NumberLiteral(0.).into());
1334    let z_sort_order_property = if e.has_dynamic_z_order() {
1335        use crate::object_tree::ZOrder;
1336        let mut z_sources: Vec<ZSource> = Vec::with_capacity(e.children.len());
1337        for child in e.children.iter() {
1338            let child_z = child.borrow().z_order.clone();
1339            match child_z {
1340                Some(ZOrder::Constant(val)) => {
1341                    z_sources.push(ZSource::Expression(
1342                        super::Expression::NumberLiteral(val as f64).into(),
1343                    ));
1344                }
1345                Some(ZOrder::PerInstance(_)) => z_sources.push(ZSource::RepeaterInstances),
1346                Some(ZOrder::Dynamic(ref nr)) => {
1347                    match component.mapping.map_property_reference(nr, state) {
1348                        MemberReference::Relative { parent_level, local_reference } => {
1349                            // The per-visit sort evaluates in this item tree's own context.
1350                            debug_assert_eq!(
1351                                parent_level, 0,
1352                                "z reference resolved outside the item tree"
1353                            );
1354                            // Store the path from the tree root so that consumers don't
1355                            // need the node's sub_component_path to resolve the reference.
1356                            let mut full_path = sub_component_path.to_vec();
1357                            full_path.extend_from_slice(&local_reference.sub_component_path);
1358                            z_sources.push(ZSource::Expression(
1359                                super::Expression::PropertyReference(
1360                                    LocalMemberReference {
1361                                        sub_component_path: full_path,
1362                                        reference: local_reference.reference,
1363                                    }
1364                                    .into(),
1365                                )
1366                                .into(),
1367                            ));
1368                        }
1369                        global @ MemberReference::Global { .. } => {
1370                            z_sources.push(ZSource::Expression(
1371                                super::Expression::PropertyReference(global).into(),
1372                            ));
1373                        }
1374                    }
1375                }
1376                None => z_sources.push(zero()),
1377            }
1378        }
1379        Some(z_sources)
1380    } else {
1381        None
1382    };
1383
1384    match component.mapping.element_mapping.get(&ByAddress(element.clone())).unwrap() {
1385        LoweredElement::SubComponent { sub_component_index } => {
1386            let sub_component = e.sub_component().unwrap();
1387            let new_sub_component_path = sub_component_path
1388                .iter()
1389                .copied()
1390                .chain(std::iter::once(*sub_component_index))
1391                .collect::<Vec<_>>();
1392            let mut tree_node = make_tree(
1393                state,
1394                &sub_component.root_element,
1395                state.sub_component(sub_component),
1396                &new_sub_component_path,
1397            );
1398            // The children are the sub-component's own plus the instantiating element's,
1399            // and either side may z-sort, so merge the z sources, padding the other with 0.
1400            let inner_count = tree_node.children.len();
1401            tree_node.children.extend(children);
1402            if z_sort_order_property.is_some() || tree_node.z_sort_order_property.is_some() {
1403                let mut merged = tree_node
1404                    .z_sort_order_property
1405                    .take()
1406                    .unwrap_or_else(|| (0..inner_count).map(|_| zero()).collect());
1407                let outer_count = tree_node.children.len() - inner_count;
1408                merged.extend(
1409                    z_sort_order_property
1410                        .unwrap_or_else(|| (0..outer_count).map(|_| zero()).collect()),
1411                );
1412                tree_node.z_sort_order_property = Some(merged);
1413            }
1414            tree_node.is_accessible |= !e.accessibility_props.0.is_empty();
1415            tree_node
1416        }
1417        LoweredElement::NativeItem { item_index } => TreeNode {
1418            is_accessible: !e.accessibility_props.0.is_empty(),
1419            sub_component_path: sub_component_path.into(),
1420            item_index: itertools::Either::Left(*item_index),
1421            children: children.collect(),
1422            z_sort_order_property,
1423        },
1424        LoweredElement::Repeated { repeated_index } => TreeNode {
1425            is_accessible: false,
1426            sub_component_path: sub_component_path.into(),
1427            item_index: itertools::Either::Right(usize::from(*repeated_index) as u32),
1428            children: Vec::new(),
1429            z_sort_order_property: None,
1430        },
1431        LoweredElement::ComponentPlaceholder { repeated_index } => TreeNode {
1432            is_accessible: false,
1433            sub_component_path: sub_component_path.into(),
1434            item_index: itertools::Either::Right(*repeated_index + repeater_count),
1435            children: Vec::new(),
1436            z_sort_order_property: None,
1437        },
1438    }
1439}
1440
1441fn public_properties(
1442    component: &Component,
1443    mapping: &LoweredSubComponentMapping,
1444    state: &LoweringState,
1445) -> PublicProperties {
1446    component
1447        .root_element
1448        .borrow()
1449        .property_declarations
1450        .iter()
1451        .filter(|(_, c)| c.expose_in_public_api)
1452        .map(|(p, c)| {
1453            let property_reference = mapping.map_property_reference(
1454                &NamedReference::new(&component.root_element, p.clone()),
1455                state,
1456            );
1457            // Recover the source-form identifier from the declaration node
1458            // (preserves dashes and the original casing). Fall back to the
1459            // normalized key if no node is attached.
1460            let display_name = c
1461                .node
1462                .as_ref()
1463                .and_then(|n| {
1464                    n.child_node(crate::parser::SyntaxKind::DeclaredIdentifier)
1465                        .and_then(|n| n.child_token(crate::parser::SyntaxKind::Identifier))
1466                })
1467                .map(|tok| SmolStr::new(tok.text()))
1468                .unwrap_or_else(|| c.declared_name(p).clone());
1469            (
1470                // A shadowing declaration is exposed under the name it was written with,
1471                // not the internal name it is stored under
1472                c.declared_name(p).clone(),
1473                PublicProperty {
1474                    display_name,
1475                    ty: c.property_type.clone(),
1476                    prop: property_reference,
1477                    visibility: c.visibility,
1478                },
1479            )
1480        })
1481        .collect()
1482}