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;
5
6use super::lower_expression::{ExpressionLoweringCtx, ExpressionLoweringCtxInner};
7use crate::CompilerConfiguration;
8use crate::expression_tree::Expression as tree_Expression;
9use crate::langtype::{
10    BuiltinPrivateStruct, BuiltinPublicStruct, ElementType, Struct, StructName, Type,
11};
12use crate::llr::item_tree::*;
13use crate::namedreference::NamedReference;
14use crate::object_tree::{self, Component, ElementRc, PropertyAnalysis, PropertyVisibility};
15use smol_str::{SmolStr, format_smolstr};
16use std::collections::{BTreeMap, HashMap};
17use std::rc::Rc;
18use typed_index_collections::TiVec;
19
20pub fn lower_to_item_tree(
21    document: &crate::object_tree::Document,
22    compiler_config: &CompilerConfiguration,
23) -> CompilationUnit {
24    let mut state = LoweringState::default();
25
26    #[cfg(feature = "bundle-translations")]
27    {
28        state.translation_builder = document.translation_builder.clone();
29    }
30
31    let mut globals = TiVec::new();
32    for g in &document.used_types.borrow().globals {
33        let count = globals.next_key();
34        globals.push(lower_global(g, count, &mut state));
35    }
36    for (g, l) in document.used_types.borrow().globals.iter().zip(&mut globals) {
37        lower_global_expressions(g, &mut state, l);
38    }
39
40    for c in &document.used_types.borrow().sub_components {
41        let sc = lower_sub_component(c, &mut state, None, compiler_config);
42        let idx = state.push_sub_component(sc);
43        state.sub_component_mapping.insert(ByAddress(c.clone()), idx);
44    }
45
46    let public_components = document
47        .exported_roots()
48        .map(|component| {
49            let mut sc = lower_sub_component(&component, &mut state, None, compiler_config);
50            let public_properties = public_properties(&component, &sc.mapping, &state);
51            sc.sub_component.name = component.id.clone();
52            let item_tree = ItemTree {
53                tree: make_tree(&state, &component.root_element, &sc, &[]),
54                root: state.push_sub_component(sc),
55            };
56            // For C++ codegen, the root component must have the same name as the public component
57            PublicComponent {
58                item_tree,
59                public_properties,
60                private_properties: component.private_properties.borrow().clone(),
61                name: component.id.clone(),
62            }
63        })
64        .collect();
65
66    let popup_menu = document.popup_menu_impl.as_ref().map(|c| {
67        let sc = lower_sub_component(c, &mut state, None, compiler_config);
68        let sub_menu = sc.mapping.map_property_reference(
69            &NamedReference::new(&c.root_element, SmolStr::new_static("sub-menu")),
70            &state,
71        );
72        let activated = sc.mapping.map_property_reference(
73            &NamedReference::new(&c.root_element, SmolStr::new_static("activated")),
74            &state,
75        );
76        let close = sc.mapping.map_property_reference(
77            &NamedReference::new(&c.root_element, SmolStr::new_static("close")),
78            &state,
79        );
80        let entries = sc.mapping.map_property_reference(
81            &NamedReference::new(&c.root_element, SmolStr::new_static("entries")),
82            &state,
83        );
84        let item_tree = ItemTree {
85            tree: make_tree(&state, &c.root_element, &sc, &[]),
86            root: state.push_sub_component(sc),
87        };
88        PopupMenu { item_tree, sub_menu, activated, close, entries }
89    });
90
91    let mut root = CompilationUnit {
92        public_components,
93        globals,
94        sub_components: state.sub_components.into_iter().map(|sc| sc.sub_component).collect(),
95        used_sub_components: document
96            .used_types
97            .borrow()
98            .sub_components
99            .iter()
100            .map(|tree_sub_compo| state.sub_component_mapping[&ByAddress(tree_sub_compo.clone())])
101            .collect(),
102        has_debug_info: compiler_config.debug_info,
103        popup_menu,
104        #[cfg(feature = "bundle-translations")]
105        translations: state.translation_builder.map(|x| x.result()),
106    };
107    super::optim_passes::run_passes(&mut root);
108    root
109}
110
111#[derive(Debug, Clone)]
112pub enum LoweredElement {
113    SubComponent { sub_component_index: SubComponentInstanceIdx },
114    NativeItem { item_index: ItemInstanceIdx },
115    Repeated { repeated_index: RepeatedElementIdx },
116    ComponentPlaceholder { repeated_index: u32 },
117}
118
119#[derive(Default, Debug, Clone)]
120pub struct LoweredSubComponentMapping {
121    pub element_mapping: HashMap<ByAddress<ElementRc>, LoweredElement>,
122    pub property_mapping: HashMap<NamedReference, MemberReference>,
123    pub repeater_count: u32,
124    pub container_count: u32,
125}
126
127impl LoweredSubComponentMapping {
128    pub fn map_property_reference(
129        &self,
130        from: &NamedReference,
131        state: &LoweringState,
132    ) -> MemberReference {
133        if let Some(x) = self.property_mapping.get(from) {
134            return x.clone();
135        }
136        if let Some(x) = state.global_properties.get(from) {
137            return x.clone();
138        }
139        let element = from.element();
140        if let Some(alias) = element
141            .borrow()
142            .property_declarations
143            .get(from.name())
144            .and_then(|x| x.is_alias.as_ref())
145        {
146            return self.map_property_reference(alias, state);
147        }
148        match self.element_mapping.get(&element.clone().into()).unwrap() {
149            LoweredElement::SubComponent { sub_component_index } => {
150                if let ElementType::Component(base) = &element.borrow().base_type {
151                    let mut prop_ref = state.map_property_reference(&NamedReference::new(
152                        &base.root_element,
153                        from.name().clone(),
154                    ));
155                    if let MemberReference::Relative { parent_level, local_reference } =
156                        &mut prop_ref
157                    {
158                        assert_eq!(*parent_level, 0, "the sub-component had no parents");
159                        local_reference.sub_component_path.insert(0, *sub_component_index);
160                    }
161                    return prop_ref;
162                }
163                unreachable!()
164            }
165            LoweredElement::NativeItem { item_index } => MemberReference::Relative {
166                parent_level: 0,
167                local_reference: LocalMemberReference {
168                    sub_component_path: Vec::new(),
169                    reference: LocalMemberIndex::Native {
170                        item_index: *item_index,
171                        prop_name: from.name().clone(),
172                    },
173                },
174            },
175            LoweredElement::Repeated { .. } => {
176                panic!(
177                    "Trying to map property {from:?} on a repeated element {} of type {:?}",
178                    element.borrow().id,
179                    element.borrow().base_type
180                );
181            }
182            LoweredElement::ComponentPlaceholder { .. } => unreachable!(),
183        }
184    }
185}
186
187pub struct LoweredSubComponent {
188    sub_component: SubComponent,
189    mapping: LoweredSubComponentMapping,
190}
191
192#[derive(Default)]
193pub struct LoweringState {
194    global_properties: HashMap<NamedReference, MemberReference>,
195    sub_components: TiVec<SubComponentIdx, LoweredSubComponent>,
196    sub_component_mapping: HashMap<ByAddress<Rc<Component>>, SubComponentIdx>,
197    #[cfg(feature = "bundle-translations")]
198    pub translation_builder: Option<crate::translations::TranslationsBuilder>,
199}
200
201impl LoweringState {
202    pub fn map_property_reference(&self, from: &NamedReference) -> MemberReference {
203        if let Some(x) = self.global_properties.get(from) {
204            return x.clone();
205        }
206
207        let element = from.element();
208        let sc = self.sub_component(&element.borrow().enclosing_component.upgrade().unwrap());
209        sc.mapping.map_property_reference(from, self)
210    }
211
212    fn sub_component<'a>(&'a self, component: &Rc<Component>) -> &'a LoweredSubComponent {
213        &self.sub_components[self.sub_component_idx(component)]
214    }
215
216    /// Returns the `row_child_templates` from an already-lowered sub-component.
217    /// Used by the parent's layout expression lowering to read template info
218    /// from a repeated Row that was lowered earlier.
219    pub fn row_child_templates(
220        &self,
221        component: &Rc<Component>,
222    ) -> Option<Vec<super::RowChildTemplateInfo>> {
223        self.sub_components[self.sub_component_idx(component)]
224            .sub_component
225            .row_child_templates
226            .clone()
227    }
228
229    fn sub_component_idx(&self, component: &Rc<Component>) -> SubComponentIdx {
230        *self.sub_component_mapping.get(&ByAddress(component.clone())).unwrap_or_else(|| {
231            debug_assert!(
232                false,
233                "no entry found for key: component id='{}', available keys: {:?}",
234                component.id,
235                self.sub_component_mapping.keys().map(|k| k.0.id.clone()).collect::<Vec<_>>()
236            );
237            unreachable!(
238                "component must be registered before querying sub_component_idx: '{}'",
239                component.id
240            )
241        })
242    }
243
244    fn push_sub_component(&mut self, sc: LoweredSubComponent) -> SubComponentIdx {
245        self.sub_components.push_and_get_key(sc)
246    }
247}
248
249fn component_id(component: &Rc<Component>) -> SmolStr {
250    if component.is_global() {
251        component.root_element.borrow().id.clone()
252    } else if component.from_library.get() {
253        component.id.clone()
254    } else if component.id.is_empty() {
255        format_smolstr!("Component_{}", component.root_element.borrow().id)
256    } else {
257        format_smolstr!("{}_{}", component.id, component.root_element.borrow().id)
258    }
259}
260
261fn lower_sub_component(
262    component: &Rc<Component>,
263    state: &mut LoweringState,
264    parent_context: Option<&ExpressionLoweringCtxInner>,
265    compiler_config: &CompilerConfiguration,
266) -> LoweredSubComponent {
267    let mut sub_component = SubComponent {
268        name: component_id(component),
269        properties: Default::default(),
270        callbacks: Default::default(),
271        functions: Default::default(),
272        items: Default::default(),
273        repeated: Default::default(),
274        component_containers: Default::default(),
275        popup_windows: Default::default(),
276        menu_item_trees: Vec::new(),
277        timers: Default::default(),
278        sub_components: Default::default(),
279        property_init: Default::default(),
280        change_callbacks: Default::default(),
281        animations: Default::default(),
282        two_way_bindings: Default::default(),
283        const_properties: Default::default(),
284        init_code: Default::default(),
285        geometries: Default::default(),
286        // just initialize to dummy expression right now and it will be set later
287        layout_info_h: super::Expression::BoolLiteral(false).into(),
288        layout_info_v: super::Expression::BoolLiteral(false).into(),
289        child_of_layout: component.root_element.borrow().child_of_layout,
290        grid_layout_input_for_repeated: None,
291        flexbox_layout_item_info_for_repeated: None,
292        is_repeated_row: component
293            .root_element
294            .borrow()
295            .grid_layout_cell
296            .as_ref()
297            .is_some_and(|c| c.borrow().child_items.is_some()),
298        grid_layout_children: Default::default(),
299        row_child_templates: None,
300        accessible_prop: Default::default(),
301        element_infos: Default::default(),
302        prop_analysis: Default::default(),
303    };
304    let mut mapping = LoweredSubComponentMapping::default();
305    let mut repeated = TiVec::new();
306    let mut accessible_prop = Vec::new();
307    let mut change_callbacks = Vec::new();
308
309    if let Some(parent) = component.parent_element() {
310        // Add properties for the model data and index
311        if parent.borrow().repeated.as_ref().is_some_and(|x| !x.is_conditional_element) {
312            sub_component.properties.push(Property {
313                name: "model_data".into(),
314                ty: crate::expression_tree::Expression::RepeaterModelReference {
315                    element: component.parent_element.borrow().clone(),
316                }
317                .ty(),
318                ..Property::default()
319            });
320            sub_component.properties.push(Property {
321                name: "model_index".into(),
322                ty: Type::Int32,
323                ..Property::default()
324            });
325        }
326    };
327
328    let s: Option<ElementRc> = None;
329    let mut repeater_offset = 0;
330    crate::object_tree::recurse_elem(&component.root_element, &s, &mut |element, parent| {
331        let elem = element.borrow();
332        for (p, x) in &elem.property_declarations {
333            if x.is_alias.is_some() {
334                continue;
335            }
336            let reference = if let Type::Function(function) = &x.property_type {
337                // TODO: Function could wrap the Rc<langtype::Function>
338                //       instead of cloning the return type and args?
339                let index = sub_component.functions.push_and_get_key(Function {
340                    name: p.clone(),
341                    ret_ty: function.return_type.clone(),
342                    args: function.args.clone(),
343                    // will be replaced later
344                    code: super::Expression::CodeBlock(Vec::new()),
345                });
346                index.into()
347            } else if let Type::Callback(callback) = &x.property_type {
348                let index = sub_component.callbacks.push_and_get_key(Callback {
349                    name: format_smolstr!("{}_{}", elem.id, p),
350                    ret_ty: callback.return_type.clone(),
351                    args: callback.args.clone(),
352                    ty: Type::Callback(callback.clone()),
353                    use_count: 0.into(),
354                });
355                index.into()
356            } else {
357                let index = sub_component.properties.push_and_get_key(Property {
358                    name: format_smolstr!("{}_{}", elem.id, p),
359                    ty: x.property_type.clone(),
360                    ..Property::default()
361                });
362                index.into()
363            };
364            mapping.property_mapping.insert(
365                NamedReference::new(element, p.clone()),
366                MemberReference::Relative {
367                    parent_level: 0,
368                    local_reference: LocalMemberReference {
369                        sub_component_path: Vec::new(),
370                        reference,
371                    },
372                },
373            );
374        }
375        if elem.repeated.is_some() {
376            let parent = if elem.is_component_placeholder { parent.clone() } else { None };
377
378            mapping.element_mapping.insert(
379                element.clone().into(),
380                LoweredElement::Repeated {
381                    repeated_index: repeated.push_and_get_key((element.clone(), parent)),
382                },
383            );
384            mapping.repeater_count += 1;
385            return None;
386        }
387        match &elem.base_type {
388            ElementType::Component(comp) => {
389                let ty = state.sub_component_idx(comp);
390                let sub_component_index =
391                    sub_component.sub_components.push_and_get_key(SubComponentInstance {
392                        ty,
393                        name: elem.id.clone(),
394                        index_in_tree: *elem.item_index.get().unwrap(),
395                        index_of_first_child_in_tree: *elem
396                            .item_index_of_first_children
397                            .get()
398                            .unwrap(),
399                        repeater_offset,
400                    });
401                mapping.element_mapping.insert(
402                    element.clone().into(),
403                    LoweredElement::SubComponent { sub_component_index },
404                );
405                repeater_offset += comp.repeater_count();
406            }
407
408            ElementType::Native(n) => {
409                let item_index = sub_component.items.push_and_get_key(Item {
410                    ty: n.clone(),
411                    name: elem.id.clone(),
412                    index_in_tree: *elem.item_index.get().unwrap(),
413                });
414                mapping
415                    .element_mapping
416                    .insert(element.clone().into(), LoweredElement::NativeItem { item_index });
417            }
418            _ => unreachable!(),
419        };
420        for (key, nr) in &elem.accessibility_props.0 {
421            // TODO: we also want to split by type (role/string/...)
422            let enum_value =
423                crate::generator::to_pascal_case(key.strip_prefix("accessible-").unwrap());
424            accessible_prop.push((*elem.item_index.get().unwrap(), enum_value, nr.clone()));
425        }
426
427        for (prop, expr) in &elem.change_callbacks {
428            change_callbacks
429                .push((NamedReference::new(element, prop.clone()), expr.borrow().clone()));
430        }
431
432        if compiler_config.debug_info {
433            let element_infos = elem.element_infos();
434            if !element_infos.is_empty() {
435                sub_component.element_infos.insert(*elem.item_index.get().unwrap(), element_infos);
436            }
437        }
438
439        Some(element.clone())
440    });
441
442    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: parent_context, component };
443    let mut ctx = ExpressionLoweringCtx { inner, state };
444
445    // Lower repeated components first, so their sub-components (e.g. Row) are available
446    // when lowering layout expressions that need to read row_child_templates.
447    sub_component.repeated = repeated
448        .into_iter()
449        .map(|(elem, parent)| {
450            lower_repeated_component(&elem, parent, &sub_component, &mut ctx, compiler_config)
451        })
452        .collect();
453    for s in &mut sub_component.sub_components {
454        s.repeater_offset +=
455            (sub_component.repeated.len() + sub_component.component_containers.len()) as u32;
456    }
457
458    crate::generator::handle_property_bindings_init(component, |e, p, binding| {
459        let nr = NamedReference::new(e, p.clone());
460        let prop = ctx.map_property_reference(&nr);
461
462        if let Type::Function { .. } = nr.ty() {
463            let MemberReference::Relative { parent_level, local_reference } = prop else {
464                unreachable!()
465            };
466            assert!(parent_level == 0);
467            assert!(local_reference.sub_component_path.is_empty());
468            let LocalMemberIndex::Function(function_index) = local_reference.reference else {
469                unreachable!()
470            };
471
472            sub_component.functions[function_index].code =
473                super::lower_expression::lower_expression(&binding.expression, &mut ctx);
474
475            return;
476        }
477
478        for tw in &binding.two_way_bindings {
479            sub_component.two_way_bindings.push((
480                prop.clone(),
481                ctx.map_property_reference(&tw.property),
482                tw.field_access.clone(),
483            ));
484        }
485        if !matches!(binding.expression, tree_Expression::Invalid) {
486            let expression =
487                super::lower_expression::lower_expression(&binding.expression, &mut ctx).into();
488
489            let is_constant = binding.analysis.as_ref().is_some_and(|a| a.is_const);
490            let animation = binding
491                .animation
492                .as_ref()
493                .filter(|_| !is_constant)
494                .map(|a| super::lower_expression::lower_animation(a, &mut ctx));
495
496            sub_component.prop_analysis.insert(
497                prop.clone(),
498                PropAnalysis {
499                    property_init: Some(sub_component.property_init.len()),
500                    analysis: get_property_analysis(e, p),
501                },
502            );
503
504            let is_state_info = matches!(
505                e.borrow().lookup_property(p).property_type,
506                Type::Struct(s) if matches!(s.name, StructName::BuiltinPrivate(BuiltinPrivateStruct::StateInfo))
507            );
508
509            sub_component.property_init.push((
510                prop.clone(),
511                BindingExpression {
512                    expression,
513                    animation,
514                    is_constant,
515                    is_state_info,
516                    use_count: 0.into(),
517                },
518            ));
519        }
520
521        if e.borrow()
522            .property_analysis
523            .borrow()
524            .get(p)
525            .is_none_or(|a| a.is_set || a.is_set_externally)
526            && let Some(anim) = binding.animation.as_ref()
527        {
528            match super::lower_expression::lower_animation(anim, &mut ctx) {
529                Animation::Static(anim) => {
530                    sub_component.animations.insert(prop.local(), anim);
531                }
532                Animation::Transition(_) => {
533                    // Cannot set a property with a transition anyway
534                }
535            }
536        }
537    });
538
539    sub_component.popup_windows = component
540        .popup_windows
541        .borrow()
542        .iter()
543        .map(|popup| lower_popup_component(popup, &mut ctx, compiler_config))
544        .collect();
545
546    sub_component.menu_item_trees = component
547        .menu_item_tree
548        .borrow()
549        .iter()
550        .map(|c| {
551            let sc = lower_sub_component(c, ctx.state, Some(&ctx.inner), compiler_config);
552            ItemTree {
553                tree: make_tree(ctx.state, &c.root_element, &sc, &[]),
554                root: ctx.state.push_sub_component(sc),
555            }
556        })
557        .collect();
558
559    sub_component.timers = component.timers.borrow().iter().map(|t| lower_timer(t, &ctx)).collect();
560
561    crate::generator::for_each_const_properties(component, |elem, n| {
562        let x = ctx.map_property_reference(&NamedReference::new(elem, n.clone()));
563        // ensure that all const properties have analysis
564        sub_component.prop_analysis.entry(x.clone()).or_insert_with(|| PropAnalysis {
565            property_init: None,
566            analysis: get_property_analysis(elem, n),
567        });
568        sub_component.const_properties.push(x.local());
569    });
570
571    sub_component.init_code = component
572        .init_code
573        .borrow()
574        .iter()
575        .map(|e| super::lower_expression::lower_expression(e, &mut ctx).into())
576        .collect();
577
578    sub_component.layout_info_h = super::lower_layout_expression::get_layout_info(
579        &component.root_element,
580        &mut ctx,
581        &component.root_constraints.borrow(),
582        crate::layout::Orientation::Horizontal,
583    )
584    .into();
585    sub_component.layout_info_v = super::lower_layout_expression::get_layout_info(
586        &component.root_element,
587        &mut ctx,
588        &component.root_constraints.borrow(),
589        crate::layout::Orientation::Vertical,
590    )
591    .into();
592    // For repeated elements in a FlexboxLayout, generate code to read flex properties
593    if sub_component.child_of_layout {
594        let root_elem = &component.root_element;
595        let has_flex_binding =
596            ["flex-grow", "flex-shrink", "flex-basis", "flex-align-self", "flex-order"]
597                .iter()
598                .any(|name| crate::layout::binding_reference(root_elem, name).is_some());
599        if has_flex_binding {
600            sub_component.flexbox_layout_item_info_for_repeated = Some(
601                super::lower_layout_expression::get_flexbox_layout_item_info_for_repeated(
602                    &mut ctx, root_elem,
603                )
604                .into(),
605            );
606        }
607    }
608
609    if let Some(grid_layout_cell) = component.root_element.borrow().grid_layout_cell.as_ref() {
610        let grid_cell_ref = grid_layout_cell.borrow();
611        sub_component.grid_layout_input_for_repeated = Some(
612            super::lower_layout_expression::get_grid_layout_input_for_repeated(
613                &mut ctx,
614                &grid_cell_ref,
615            )
616            .into(),
617        );
618
619        // Store constraints for children of the Row
620        if let Some(children_constraints) = grid_cell_ref.child_items.as_ref() {
621            let mut row_child_templates = Vec::new();
622            for child_template in children_constraints.iter() {
623                match child_template {
624                    crate::layout::RowChildTemplate::Static(layout_item) => {
625                        let layout_info_h = super::lower_layout_expression::get_layout_info(
626                            &layout_item.element,
627                            &mut ctx,
628                            &layout_item.constraints,
629                            crate::layout::Orientation::Horizontal,
630                        );
631                        let layout_info_v = super::lower_layout_expression::get_layout_info(
632                            &layout_item.element,
633                            &mut ctx,
634                            &layout_item.constraints,
635                            crate::layout::Orientation::Vertical,
636                        );
637                        let child_index = sub_component.grid_layout_children.push_and_get_key(
638                            super::GridLayoutChildLayoutInfo {
639                                layout_info_h: layout_info_h.into(),
640                                layout_info_v: layout_info_v.into(),
641                            },
642                        );
643                        row_child_templates
644                            .push(super::RowChildTemplateInfo::Static { child_index });
645                    }
646                    crate::layout::RowChildTemplate::Repeated { repeated_element, .. } => {
647                        // Inner repeater: layout_info is computed at runtime per instance.
648                        if let Some(super::lower_to_item_tree::LoweredElement::Repeated {
649                            repeated_index,
650                        }) = mapping.element_mapping.get(&repeated_element.clone().into())
651                        {
652                            row_child_templates.push(super::RowChildTemplateInfo::Repeated {
653                                repeater_index: *repeated_index,
654                            });
655                        }
656                    }
657                }
658            }
659            // Always set row_child_templates (even if empty) to mark this as a Row sub-component.
660            // An empty row_child_templates (Some([])) means 0 cells per sub-component — correct for empty Rows.
661            // Leaving it as None would incorrectly treat it as a column-repeater (1 cell per sub-component).
662            sub_component.row_child_templates = Some(row_child_templates);
663        }
664    }
665
666    sub_component.accessible_prop = accessible_prop
667        .into_iter()
668        .map(|(idx, key, nr)| {
669            let prop = ctx.map_property_reference(&nr);
670            let expr = match nr.ty() {
671                Type::Bool => super::Expression::Condition {
672                    condition: super::Expression::PropertyReference(prop).into(),
673                    true_expr: super::Expression::StringLiteral("true".into()).into(),
674                    false_expr: super::Expression::StringLiteral("false".into()).into(),
675                },
676                Type::Int32 | Type::Float32 => super::Expression::Cast {
677                    from: super::Expression::PropertyReference(prop).into(),
678                    to: Type::String,
679                },
680                Type::String => super::Expression::PropertyReference(prop),
681                Type::Enumeration(e) if e.name == "AccessibleRole" => {
682                    super::Expression::PropertyReference(prop)
683                }
684                Type::Callback(callback) => super::Expression::CallBackCall {
685                    callback: prop,
686                    arguments: (0..callback.args.len())
687                        .map(|index| super::Expression::FunctionParameterReference { index })
688                        .collect(),
689                },
690                _ => panic!("Invalid type for accessible property"),
691            };
692
693            ((idx, key), expr.into())
694        })
695        .collect();
696
697    sub_component.change_callbacks = change_callbacks
698        .into_iter()
699        .map(|(nr, exprs)| {
700            let prop = ctx.map_property_reference(&nr);
701            let expr = super::lower_expression::lower_expression(
702                &tree_Expression::CodeBlock(exprs),
703                &mut ctx,
704            );
705            (prop, expr.into())
706        })
707        .collect();
708
709    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |element, _| {
710        let elem = element.borrow();
711        if elem.repeated.is_some() {
712            return;
713        };
714        let Some(geom) = &elem.geometry_props else { return };
715        let item_index = *elem.item_index.get().unwrap() as usize;
716        if item_index >= sub_component.geometries.len() {
717            sub_component.geometries.resize(item_index + 1, Default::default());
718        }
719        sub_component.geometries[item_index] = Some(lower_geometry(geom, &ctx).into());
720    });
721
722    LoweredSubComponent { sub_component, mapping }
723}
724
725fn lower_geometry(
726    geom: &crate::object_tree::GeometryProps,
727    ctx: &ExpressionLoweringCtx<'_>,
728) -> super::Expression {
729    let mut fields = BTreeMap::default();
730    let mut values = BTreeMap::default();
731    for (f, v) in [("x", &geom.x), ("y", &geom.y), ("width", &geom.width), ("height", &geom.height)]
732    {
733        fields.insert(f.into(), Type::LogicalLength);
734        values
735            .insert(f.into(), super::Expression::PropertyReference(ctx.map_property_reference(v)));
736    }
737    super::Expression::Struct { ty: Rc::new(Struct { fields, name: StructName::None }), values }
738}
739
740fn get_property_analysis(elem: &ElementRc, p: &str) -> crate::object_tree::PropertyAnalysis {
741    let mut a = elem.borrow().property_analysis.borrow().get(p).cloned().unwrap_or_default();
742    let mut elem = elem.clone();
743    loop {
744        if let Some(d) = elem.borrow().property_declarations.get(p) {
745            if let Some(nr) = &d.is_alias {
746                a.merge(&get_property_analysis(&nr.element(), nr.name()));
747            }
748            return a;
749        }
750        let base = elem.borrow().base_type.clone();
751        match base {
752            ElementType::Native(n) if n.properties.get(p).is_some_and(|p| p.is_native_output()) => {
753                a.is_set = true;
754            }
755            ElementType::Component(c) => {
756                elem = c.root_element.clone();
757                if let Some(a2) = elem.borrow().property_analysis.borrow().get(p) {
758                    a.merge_with_base(a2);
759                }
760                continue;
761            }
762            _ => (),
763        };
764        return a;
765    }
766}
767
768fn lower_repeated_component(
769    elem: &ElementRc,
770    parent_component_container: Option<ElementRc>,
771    sub_component: &SubComponent,
772    ctx: &mut ExpressionLoweringCtx,
773    compiler_config: &CompilerConfiguration,
774) -> RepeatedElement {
775    let e = elem.borrow();
776    let component = e.base_type.as_component().clone();
777    let repeated = e.repeated.as_ref().unwrap();
778
779    let sc = lower_sub_component(&component, ctx.state, Some(&ctx.inner), compiler_config);
780
781    let listview = repeated.is_listview.as_ref().map(|lv| {
782        let geom = component.root_element.borrow().geometry_props.clone().unwrap();
783        ListViewInfo {
784            viewport_y: ctx.map_property_reference(&lv.viewport_y),
785            viewport_height: ctx.map_property_reference(&lv.viewport_height),
786            viewport_width: ctx.map_property_reference(&lv.viewport_width),
787            listview_height: ctx.map_property_reference(&lv.listview_height),
788            listview_width: ctx.map_property_reference(&lv.listview_width),
789            prop_y: sc.mapping.map_property_reference(&geom.y, ctx.state),
790            prop_height: sc.mapping.map_property_reference(&geom.height, ctx.state),
791        }
792    });
793
794    let parent_index = parent_component_container.map(|p| *p.borrow().item_index.get().unwrap());
795    let container_item_index =
796        parent_index.and_then(|pii| sub_component.items.position(|i| i.index_in_tree == pii));
797
798    let tree = make_tree(ctx.state, &component.root_element, &sc, &[]);
799    let root = ctx.state.push_sub_component(sc);
800    // Register the repeated component in the mapping so it can be looked up
801    ctx.state.sub_component_mapping.insert(ByAddress(component.clone()), root);
802
803    RepeatedElement {
804        model: super::lower_expression::lower_expression(&repeated.model, ctx).into(),
805        sub_tree: ItemTree { tree, root },
806        index_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_INDEX),
807        data_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_DATA),
808        index_in_tree: *e.item_index.get().unwrap(),
809        listview,
810        container_item_index,
811    }
812}
813
814fn lower_popup_component(
815    popup: &object_tree::PopupWindow,
816    ctx: &mut ExpressionLoweringCtx,
817    compiler_config: &CompilerConfiguration,
818) -> PopupWindow {
819    let sc = lower_sub_component(&popup.component, ctx.state, Some(&ctx.inner), compiler_config);
820    use super::Expression::PropertyReference as PR;
821    let position = super::lower_expression::make_struct(
822        BuiltinPublicStruct::LogicalPosition,
823        [
824            ("x", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.x, ctx.state))),
825            ("y", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.y, ctx.state))),
826        ],
827    );
828
829    let item_tree = ItemTree {
830        tree: make_tree(ctx.state, &popup.component.root_element, &sc, &[]),
831        root: ctx.state.push_sub_component(sc),
832    };
833    PopupWindow { item_tree, position: position.into() }
834}
835
836fn lower_timer(timer: &object_tree::Timer, ctx: &ExpressionLoweringCtx) -> Timer {
837    Timer {
838        interval: super::Expression::PropertyReference(ctx.map_property_reference(&timer.interval))
839            .into(),
840        running: super::Expression::PropertyReference(ctx.map_property_reference(&timer.running))
841            .into(),
842        // TODO: this calls a callback instead of inlining the callback code directly
843        triggered: super::Expression::CallBackCall {
844            callback: ctx.map_property_reference(&timer.triggered),
845            arguments: Vec::new(),
846        }
847        .into(),
848    }
849}
850
851/// Lower the globals (but not their expressions as we first need to lower all the global to get proper mapping in the state)
852fn lower_global(
853    global: &Rc<Component>,
854    global_index: GlobalIdx,
855    state: &mut LoweringState,
856) -> GlobalComponent {
857    let mut properties = TiVec::new();
858    let mut callbacks = TiVec::new();
859    let mut const_properties = TiVec::new();
860    let mut prop_analysis = TiVec::new();
861    let mut functions = TiVec::new();
862
863    for (p, x) in &global.root_element.borrow().property_declarations {
864        if x.is_alias.is_some() {
865            continue;
866        }
867        let nr = NamedReference::new(&global.root_element, p.clone());
868
869        if let Type::Function(function) = &x.property_type {
870            // TODO: wrap the Rc<langtype::Function> instead of cloning
871            let function_index: FunctionIdx = functions.push_and_get_key(Function {
872                name: p.clone(),
873                ret_ty: function.return_type.clone(),
874                args: function.args.clone(),
875                // will be replaced later
876                code: super::Expression::CodeBlock(Vec::new()),
877            });
878            state.global_properties.insert(
879                nr.clone(),
880                MemberReference::Global { global_index, member: function_index.into() },
881            );
882            continue;
883        } else if let Type::Callback(cb) = &x.property_type {
884            let callback_index: CallbackIdx = callbacks.push_and_get_key(Callback {
885                name: p.clone(),
886                ret_ty: cb.return_type.clone(),
887                args: cb.args.clone(),
888                ty: x.property_type.clone(),
889                use_count: 0.into(),
890            });
891            state.global_properties.insert(
892                nr.clone(),
893                MemberReference::Global { global_index, member: callback_index.into() },
894            );
895            continue;
896        }
897
898        let property_index: PropertyIdx = properties.push_and_get_key(Property {
899            name: p.clone(),
900            ty: x.property_type.clone(),
901            ..Property::default()
902        });
903
904        const_properties.push(nr.is_constant());
905
906        prop_analysis.push(
907            global
908                .root_element
909                .borrow()
910                .property_analysis
911                .borrow()
912                .get(p)
913                .cloned()
914                .unwrap_or_default(),
915        );
916        state.global_properties.insert(
917            nr.clone(),
918            MemberReference::Global { global_index, member: property_index.into() },
919        );
920    }
921
922    let is_builtin = if let Some(builtin) = global.root_element.borrow().native_class() {
923        // We just generate the property so we know how to address them
924        for (p, x) in &builtin.properties {
925            let property_index = properties.push_and_get_key(Property {
926                name: p.clone(),
927                ty: x.ty.clone(),
928                ..Property::default()
929            });
930            let nr = NamedReference::new(&global.root_element, p.clone());
931            state.global_properties.insert(
932                nr,
933                MemberReference::Global { global_index, member: property_index.into() },
934            );
935            prop_analysis.push(PropertyAnalysis {
936                // Assume that a builtin global property can always be set from the builtin code
937                is_set_externally: true,
938                ..global
939                    .root_element
940                    .borrow()
941                    .property_analysis
942                    .borrow()
943                    .get(p)
944                    .cloned()
945                    .unwrap_or_default()
946            });
947        }
948        true
949    } else {
950        false
951    };
952
953    GlobalComponent {
954        name: global.root_element.borrow().id.clone(),
955        init_values: BTreeMap::new(),
956        properties,
957        callbacks,
958        functions,
959        change_callbacks: BTreeMap::new(),
960        const_properties,
961        public_properties: Default::default(),
962        private_properties: global.private_properties.borrow().clone(),
963        exported: !global.exported_global_names.borrow().is_empty(),
964        aliases: global.global_aliases(),
965        is_builtin,
966        from_library: global.from_library.get(),
967        prop_analysis,
968    }
969}
970
971fn lower_global_expressions(
972    global: &Rc<Component>,
973    state: &mut LoweringState,
974    lowered: &mut GlobalComponent,
975) {
976    // Note that this mapping doesn't contain anything useful, everything is in the state
977    let mapping = LoweredSubComponentMapping::default();
978    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: None, component: global };
979    let mut ctx = ExpressionLoweringCtx { inner, state };
980
981    for (prop, binding) in &global.root_element.borrow().bindings {
982        assert!(binding.borrow().two_way_bindings.is_empty());
983        assert!(binding.borrow().animation.is_none());
984        let expression =
985            super::lower_expression::lower_expression(&binding.borrow().expression, &mut ctx);
986
987        let nr = NamedReference::new(&global.root_element, prop.clone());
988        let member_index = match &ctx.state.global_properties[&nr] {
989            MemberReference::Global {
990                member: LocalMemberIndex::Function(function_index), ..
991            } => {
992                lowered.functions[*function_index].code = expression;
993                continue;
994            }
995            MemberReference::Global { member, .. } => member.clone(),
996            _ => unreachable!(),
997        };
998        let is_constant = binding.borrow().analysis.as_ref().is_some_and(|a| a.is_const);
999        lowered.init_values.insert(
1000            member_index,
1001            BindingExpression {
1002                expression: expression.into(),
1003                animation: None,
1004                is_constant,
1005                is_state_info: false,
1006                use_count: 0.into(),
1007            },
1008        );
1009    }
1010
1011    for (prop, expr) in &global.root_element.borrow().change_callbacks {
1012        let nr = NamedReference::new(&global.root_element, prop.clone());
1013        let MemberReference::Global { member: LocalMemberIndex::Property(property_index), .. } =
1014            ctx.state.global_properties[&nr]
1015        else {
1016            unreachable!()
1017        };
1018        let expression = super::lower_expression::lower_expression(
1019            &tree_Expression::CodeBlock(expr.borrow().clone()),
1020            &mut ctx,
1021        );
1022        lowered.change_callbacks.insert(property_index, expression.into());
1023    }
1024
1025    if let Some(builtin) = global.root_element.borrow().native_class() {
1026        if lowered.exported {
1027            lowered.public_properties = builtin
1028                .properties
1029                .iter()
1030                .map(|(p, c)| {
1031                    let property_reference = mapping.map_property_reference(
1032                        &NamedReference::new(&global.root_element, p.clone()),
1033                        state,
1034                    );
1035                    PublicProperty {
1036                        name: p.clone(),
1037                        ty: c.ty.clone(),
1038                        prop: property_reference,
1039                        read_only: c.property_visibility == PropertyVisibility::Output,
1040                    }
1041                })
1042                .collect()
1043        }
1044    } else {
1045        lowered.public_properties = public_properties(global, &mapping, state);
1046    }
1047}
1048
1049fn make_tree(
1050    state: &LoweringState,
1051    element: &ElementRc,
1052    component: &LoweredSubComponent,
1053    sub_component_path: &[SubComponentInstanceIdx],
1054) -> TreeNode {
1055    let e = element.borrow();
1056    let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path));
1057    let repeater_count = component.mapping.repeater_count;
1058    match component.mapping.element_mapping.get(&ByAddress(element.clone())).unwrap() {
1059        LoweredElement::SubComponent { sub_component_index } => {
1060            let sub_component = e.sub_component().unwrap();
1061            let new_sub_component_path = sub_component_path
1062                .iter()
1063                .copied()
1064                .chain(std::iter::once(*sub_component_index))
1065                .collect::<Vec<_>>();
1066            let mut tree_node = make_tree(
1067                state,
1068                &sub_component.root_element,
1069                state.sub_component(sub_component),
1070                &new_sub_component_path,
1071            );
1072            tree_node.children.extend(children);
1073            tree_node.is_accessible |= !e.accessibility_props.0.is_empty();
1074            tree_node
1075        }
1076        LoweredElement::NativeItem { item_index } => TreeNode {
1077            is_accessible: !e.accessibility_props.0.is_empty(),
1078            sub_component_path: sub_component_path.into(),
1079            item_index: itertools::Either::Left(*item_index),
1080            children: children.collect(),
1081        },
1082        LoweredElement::Repeated { repeated_index } => TreeNode {
1083            is_accessible: false,
1084            sub_component_path: sub_component_path.into(),
1085            item_index: itertools::Either::Right(usize::from(*repeated_index) as u32),
1086            children: Vec::new(),
1087        },
1088        LoweredElement::ComponentPlaceholder { repeated_index } => TreeNode {
1089            is_accessible: false,
1090            sub_component_path: sub_component_path.into(),
1091            item_index: itertools::Either::Right(*repeated_index + repeater_count),
1092            children: Vec::new(),
1093        },
1094    }
1095}
1096
1097fn public_properties(
1098    component: &Component,
1099    mapping: &LoweredSubComponentMapping,
1100    state: &LoweringState,
1101) -> PublicProperties {
1102    component
1103        .root_element
1104        .borrow()
1105        .property_declarations
1106        .iter()
1107        .filter(|(_, c)| c.expose_in_public_api)
1108        .map(|(p, c)| {
1109            let property_reference = mapping.map_property_reference(
1110                &NamedReference::new(&component.root_element, p.clone()),
1111                state,
1112            );
1113            PublicProperty {
1114                name: p.clone(),
1115                ty: c.property_type.clone(),
1116                prop: property_reference,
1117                read_only: c.visibility == PropertyVisibility::Output,
1118            }
1119        })
1120        .collect()
1121}