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    pub 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    fn sub_component_idx(&self, component: &Rc<Component>) -> SubComponentIdx {
217        self.sub_component_mapping[&ByAddress(component.clone())]
218    }
219
220    fn push_sub_component(&mut self, sc: LoweredSubComponent) -> SubComponentIdx {
221        self.sub_components.push_and_get_key(sc)
222    }
223}
224
225fn component_id(component: &Rc<Component>) -> SmolStr {
226    if component.is_global() {
227        component.root_element.borrow().id.clone()
228    } else if component.from_library.get() {
229        component.id.clone()
230    } else if component.id.is_empty() {
231        format_smolstr!("Component_{}", component.root_element.borrow().id)
232    } else {
233        format_smolstr!("{}_{}", component.id, component.root_element.borrow().id)
234    }
235}
236
237fn lower_sub_component(
238    component: &Rc<Component>,
239    state: &mut LoweringState,
240    parent_context: Option<&ExpressionLoweringCtxInner>,
241    compiler_config: &CompilerConfiguration,
242) -> LoweredSubComponent {
243    let mut sub_component = SubComponent {
244        name: component_id(component),
245        properties: Default::default(),
246        callbacks: Default::default(),
247        functions: Default::default(),
248        items: Default::default(),
249        repeated: Default::default(),
250        component_containers: Default::default(),
251        popup_windows: Default::default(),
252        menu_item_trees: Vec::new(),
253        timers: Default::default(),
254        sub_components: Default::default(),
255        property_init: Default::default(),
256        change_callbacks: Default::default(),
257        animations: Default::default(),
258        two_way_bindings: Default::default(),
259        const_properties: Default::default(),
260        init_code: Default::default(),
261        geometries: Default::default(),
262        // just initialize to dummy expression right now and it will be set later
263        layout_info_h: super::Expression::BoolLiteral(false).into(),
264        layout_info_v: super::Expression::BoolLiteral(false).into(),
265        child_of_layout: component.root_element.borrow().child_of_layout,
266        grid_layout_input_for_repeated: None,
267        is_repeated_row: component
268            .root_element
269            .borrow()
270            .grid_layout_cell
271            .as_ref()
272            .is_some_and(|c| c.borrow().child_items.is_some()),
273        grid_layout_children: Default::default(),
274        accessible_prop: Default::default(),
275        element_infos: Default::default(),
276        prop_analysis: Default::default(),
277    };
278    let mut mapping = LoweredSubComponentMapping::default();
279    let mut repeated = TiVec::new();
280    let mut accessible_prop = Vec::new();
281    let mut change_callbacks = Vec::new();
282
283    if let Some(parent) = component.parent_element.upgrade() {
284        // Add properties for the model data and index
285        if parent.borrow().repeated.as_ref().is_some_and(|x| !x.is_conditional_element) {
286            sub_component.properties.push(Property {
287                name: "model_data".into(),
288                ty: crate::expression_tree::Expression::RepeaterModelReference {
289                    element: component.parent_element.clone(),
290                }
291                .ty(),
292                ..Property::default()
293            });
294            sub_component.properties.push(Property {
295                name: "model_index".into(),
296                ty: Type::Int32,
297                ..Property::default()
298            });
299        }
300    };
301
302    let s: Option<ElementRc> = None;
303    let mut repeater_offset = 0;
304    crate::object_tree::recurse_elem(&component.root_element, &s, &mut |element, parent| {
305        let elem = element.borrow();
306        for (p, x) in &elem.property_declarations {
307            if x.is_alias.is_some() {
308                continue;
309            }
310            let reference = if let Type::Function(function) = &x.property_type {
311                // TODO: Function could wrap the Rc<langtype::Function>
312                //       instead of cloning the return type and args?
313                let index = sub_component.functions.push_and_get_key(Function {
314                    name: p.clone(),
315                    ret_ty: function.return_type.clone(),
316                    args: function.args.clone(),
317                    // will be replaced later
318                    code: super::Expression::CodeBlock(Vec::new()),
319                });
320                index.into()
321            } else if let Type::Callback(callback) = &x.property_type {
322                let index = sub_component.callbacks.push_and_get_key(Callback {
323                    name: format_smolstr!("{}_{}", elem.id, p),
324                    ret_ty: callback.return_type.clone(),
325                    args: callback.args.clone(),
326                    ty: Type::Callback(callback.clone()),
327                    use_count: 0.into(),
328                });
329                index.into()
330            } else {
331                let index = sub_component.properties.push_and_get_key(Property {
332                    name: format_smolstr!("{}_{}", elem.id, p),
333                    ty: x.property_type.clone(),
334                    ..Property::default()
335                });
336                index.into()
337            };
338            mapping.property_mapping.insert(
339                NamedReference::new(element, p.clone()),
340                MemberReference::Relative {
341                    parent_level: 0,
342                    local_reference: LocalMemberReference {
343                        sub_component_path: Vec::new(),
344                        reference,
345                    },
346                },
347            );
348        }
349        if elem.repeated.is_some() {
350            let parent = if elem.is_component_placeholder { parent.clone() } else { None };
351
352            mapping.element_mapping.insert(
353                element.clone().into(),
354                LoweredElement::Repeated {
355                    repeated_index: repeated.push_and_get_key((element.clone(), parent)),
356                },
357            );
358            mapping.repeater_count += 1;
359            return None;
360        }
361        match &elem.base_type {
362            ElementType::Component(comp) => {
363                let ty = state.sub_component_idx(comp);
364                let sub_component_index =
365                    sub_component.sub_components.push_and_get_key(SubComponentInstance {
366                        ty,
367                        name: elem.id.clone(),
368                        index_in_tree: *elem.item_index.get().unwrap(),
369                        index_of_first_child_in_tree: *elem
370                            .item_index_of_first_children
371                            .get()
372                            .unwrap(),
373                        repeater_offset,
374                    });
375                mapping.element_mapping.insert(
376                    element.clone().into(),
377                    LoweredElement::SubComponent { sub_component_index },
378                );
379                repeater_offset += comp.repeater_count();
380            }
381
382            ElementType::Native(n) => {
383                let item_index = sub_component.items.push_and_get_key(Item {
384                    ty: n.clone(),
385                    name: elem.id.clone(),
386                    index_in_tree: *elem.item_index.get().unwrap(),
387                });
388                mapping
389                    .element_mapping
390                    .insert(element.clone().into(), LoweredElement::NativeItem { item_index });
391            }
392            _ => unreachable!(),
393        };
394        for (key, nr) in &elem.accessibility_props.0 {
395            // TODO: we also want to split by type (role/string/...)
396            let enum_value =
397                crate::generator::to_pascal_case(key.strip_prefix("accessible-").unwrap());
398            accessible_prop.push((*elem.item_index.get().unwrap(), enum_value, nr.clone()));
399        }
400
401        for (prop, expr) in &elem.change_callbacks {
402            change_callbacks
403                .push((NamedReference::new(element, prop.clone()), expr.borrow().clone()));
404        }
405
406        if compiler_config.debug_info {
407            let element_infos = elem.element_infos();
408            if !element_infos.is_empty() {
409                sub_component.element_infos.insert(*elem.item_index.get().unwrap(), element_infos);
410            }
411        }
412
413        Some(element.clone())
414    });
415
416    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: parent_context, component };
417    let mut ctx = ExpressionLoweringCtx { inner, state };
418
419    crate::generator::handle_property_bindings_init(component, |e, p, binding| {
420        let nr = NamedReference::new(e, p.clone());
421        let prop = ctx.map_property_reference(&nr);
422
423        if let Type::Function { .. } = nr.ty() {
424            let MemberReference::Relative { parent_level, local_reference } = prop else {
425                unreachable!()
426            };
427            assert!(parent_level == 0);
428            assert!(local_reference.sub_component_path.is_empty());
429            let LocalMemberIndex::Function(function_index) = local_reference.reference else {
430                unreachable!()
431            };
432
433            sub_component.functions[function_index].code =
434                super::lower_expression::lower_expression(&binding.expression, &mut ctx);
435
436            return;
437        }
438
439        for tw in &binding.two_way_bindings {
440            sub_component.two_way_bindings.push((
441                prop.clone(),
442                ctx.map_property_reference(&tw.property),
443                tw.field_access.clone(),
444            ));
445        }
446        if !matches!(binding.expression, tree_Expression::Invalid) {
447            let expression =
448                super::lower_expression::lower_expression(&binding.expression, &mut ctx).into();
449
450            let is_constant = binding.analysis.as_ref().is_some_and(|a| a.is_const);
451            let animation = binding
452                .animation
453                .as_ref()
454                .filter(|_| !is_constant)
455                .map(|a| super::lower_expression::lower_animation(a, &mut ctx));
456
457            sub_component.prop_analysis.insert(
458                prop.clone(),
459                PropAnalysis {
460                    property_init: Some(sub_component.property_init.len()),
461                    analysis: get_property_analysis(e, p),
462                },
463            );
464
465            let is_state_info = matches!(
466                e.borrow().lookup_property(p).property_type,
467                Type::Struct(s) if matches!(s.name, StructName::BuiltinPrivate(BuiltinPrivateStruct::StateInfo))
468            );
469
470            sub_component.property_init.push((
471                prop.clone(),
472                BindingExpression {
473                    expression,
474                    animation,
475                    is_constant,
476                    is_state_info,
477                    use_count: 0.into(),
478                },
479            ));
480        }
481
482        if e.borrow()
483            .property_analysis
484            .borrow()
485            .get(p)
486            .is_none_or(|a| a.is_set || a.is_set_externally)
487            && let Some(anim) = binding.animation.as_ref()
488        {
489            match super::lower_expression::lower_animation(anim, &mut ctx) {
490                Animation::Static(anim) => {
491                    sub_component.animations.insert(prop.local(), anim);
492                }
493                Animation::Transition(_) => {
494                    // Cannot set a property with a transition anyway
495                }
496            }
497        }
498    });
499    sub_component.repeated = repeated
500        .into_iter()
501        .map(|(elem, parent)| {
502            lower_repeated_component(&elem, parent, &sub_component, &mut ctx, compiler_config)
503        })
504        .collect();
505    for s in &mut sub_component.sub_components {
506        s.repeater_offset +=
507            (sub_component.repeated.len() + sub_component.component_containers.len()) as u32;
508    }
509
510    sub_component.popup_windows = component
511        .popup_windows
512        .borrow()
513        .iter()
514        .map(|popup| lower_popup_component(popup, &mut ctx, compiler_config))
515        .collect();
516
517    sub_component.menu_item_trees = component
518        .menu_item_tree
519        .borrow()
520        .iter()
521        .map(|c| {
522            let sc = lower_sub_component(c, ctx.state, Some(&ctx.inner), compiler_config);
523            ItemTree {
524                tree: make_tree(ctx.state, &c.root_element, &sc, &[]),
525                root: ctx.state.push_sub_component(sc),
526            }
527        })
528        .collect();
529
530    sub_component.timers = component.timers.borrow().iter().map(|t| lower_timer(t, &ctx)).collect();
531
532    crate::generator::for_each_const_properties(component, |elem, n| {
533        let x = ctx.map_property_reference(&NamedReference::new(elem, n.clone()));
534        // ensure that all const properties have analysis
535        sub_component.prop_analysis.entry(x.clone()).or_insert_with(|| PropAnalysis {
536            property_init: None,
537            analysis: get_property_analysis(elem, n),
538        });
539        sub_component.const_properties.push(x.local());
540    });
541
542    sub_component.init_code = component
543        .init_code
544        .borrow()
545        .iter()
546        .map(|e| super::lower_expression::lower_expression(e, &mut ctx).into())
547        .collect();
548
549    sub_component.layout_info_h = super::lower_expression::get_layout_info(
550        &component.root_element,
551        &mut ctx,
552        &component.root_constraints.borrow(),
553        crate::layout::Orientation::Horizontal,
554    )
555    .into();
556    sub_component.layout_info_v = super::lower_expression::get_layout_info(
557        &component.root_element,
558        &mut ctx,
559        &component.root_constraints.borrow(),
560        crate::layout::Orientation::Vertical,
561    )
562    .into();
563    if let Some(grid_layout_cell) = component.root_element.borrow().grid_layout_cell.as_ref() {
564        sub_component.grid_layout_input_for_repeated = Some(
565            super::lower_expression::get_grid_layout_input_for_repeated(
566                &mut ctx,
567                &grid_layout_cell.borrow(),
568            )
569            .into(),
570        );
571        // Store constraints for children of the Row
572        let children_constraints =
573            grid_layout_cell.borrow().child_items.clone().unwrap_or_default();
574        for layout_item in children_constraints {
575            let layout_info_h = super::lower_expression::get_layout_info(
576                &layout_item.element,
577                &mut ctx,
578                &layout_item.constraints,
579                crate::layout::Orientation::Horizontal,
580            );
581            let layout_info_v = super::lower_expression::get_layout_info(
582                &layout_item.element,
583                &mut ctx,
584                &layout_item.constraints,
585                crate::layout::Orientation::Vertical,
586            );
587            sub_component.grid_layout_children.push(super::GridLayoutChildLayoutInfo {
588                layout_info_h: layout_info_h.into(),
589                layout_info_v: layout_info_v.into(),
590            });
591        }
592    }
593
594    sub_component.accessible_prop = accessible_prop
595        .into_iter()
596        .map(|(idx, key, nr)| {
597            let prop = ctx.map_property_reference(&nr);
598            let expr = match nr.ty() {
599                Type::Bool => super::Expression::Condition {
600                    condition: super::Expression::PropertyReference(prop).into(),
601                    true_expr: super::Expression::StringLiteral("true".into()).into(),
602                    false_expr: super::Expression::StringLiteral("false".into()).into(),
603                },
604                Type::Int32 | Type::Float32 => super::Expression::Cast {
605                    from: super::Expression::PropertyReference(prop).into(),
606                    to: Type::String,
607                },
608                Type::String => super::Expression::PropertyReference(prop),
609                Type::Enumeration(e) if e.name == "AccessibleRole" => {
610                    super::Expression::PropertyReference(prop)
611                }
612                Type::Callback(callback) => super::Expression::CallBackCall {
613                    callback: prop,
614                    arguments: (0..callback.args.len())
615                        .map(|index| super::Expression::FunctionParameterReference { index })
616                        .collect(),
617                },
618                _ => panic!("Invalid type for accessible property"),
619            };
620
621            ((idx, key), expr.into())
622        })
623        .collect();
624
625    sub_component.change_callbacks = change_callbacks
626        .into_iter()
627        .map(|(nr, exprs)| {
628            let prop = ctx.map_property_reference(&nr);
629            let expr = super::lower_expression::lower_expression(
630                &tree_Expression::CodeBlock(exprs),
631                &mut ctx,
632            );
633            (prop, expr.into())
634        })
635        .collect();
636
637    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |element, _| {
638        let elem = element.borrow();
639        if elem.repeated.is_some() {
640            return;
641        };
642        let Some(geom) = &elem.geometry_props else { return };
643        let item_index = *elem.item_index.get().unwrap() as usize;
644        if item_index >= sub_component.geometries.len() {
645            sub_component.geometries.resize(item_index + 1, Default::default());
646        }
647        sub_component.geometries[item_index] = Some(lower_geometry(geom, &ctx).into());
648    });
649
650    LoweredSubComponent { sub_component, mapping }
651}
652
653fn lower_geometry(
654    geom: &crate::object_tree::GeometryProps,
655    ctx: &ExpressionLoweringCtx<'_>,
656) -> super::Expression {
657    let mut fields = BTreeMap::default();
658    let mut values = BTreeMap::default();
659    for (f, v) in [("x", &geom.x), ("y", &geom.y), ("width", &geom.width), ("height", &geom.height)]
660    {
661        fields.insert(f.into(), Type::LogicalLength);
662        values
663            .insert(f.into(), super::Expression::PropertyReference(ctx.map_property_reference(v)));
664    }
665    super::Expression::Struct { ty: Rc::new(Struct { fields, name: StructName::None }), values }
666}
667
668fn get_property_analysis(elem: &ElementRc, p: &str) -> crate::object_tree::PropertyAnalysis {
669    let mut a = elem.borrow().property_analysis.borrow().get(p).cloned().unwrap_or_default();
670    let mut elem = elem.clone();
671    loop {
672        if let Some(d) = elem.borrow().property_declarations.get(p) {
673            if let Some(nr) = &d.is_alias {
674                a.merge(&get_property_analysis(&nr.element(), nr.name()));
675            }
676            return a;
677        }
678        let base = elem.borrow().base_type.clone();
679        match base {
680            ElementType::Native(n) => {
681                if n.properties.get(p).is_some_and(|p| p.is_native_output()) {
682                    a.is_set = true;
683                }
684            }
685            ElementType::Component(c) => {
686                elem = c.root_element.clone();
687                if let Some(a2) = elem.borrow().property_analysis.borrow().get(p) {
688                    a.merge_with_base(a2);
689                }
690                continue;
691            }
692            _ => (),
693        };
694        return a;
695    }
696}
697
698fn lower_repeated_component(
699    elem: &ElementRc,
700    parent_component_container: Option<ElementRc>,
701    sub_component: &SubComponent,
702    ctx: &mut ExpressionLoweringCtx,
703    compiler_config: &CompilerConfiguration,
704) -> RepeatedElement {
705    let e = elem.borrow();
706    let component = e.base_type.as_component().clone();
707    let repeated = e.repeated.as_ref().unwrap();
708
709    let sc = lower_sub_component(&component, ctx.state, Some(&ctx.inner), compiler_config);
710
711    let listview = repeated.is_listview.as_ref().map(|lv| {
712        let geom = component.root_element.borrow().geometry_props.clone().unwrap();
713        ListViewInfo {
714            viewport_y: ctx.map_property_reference(&lv.viewport_y).local().clone(),
715            viewport_height: ctx.map_property_reference(&lv.viewport_height).local().clone(),
716            viewport_width: ctx.map_property_reference(&lv.viewport_width).local().clone(),
717            listview_height: ctx.map_property_reference(&lv.listview_height).local().clone(),
718            listview_width: ctx.map_property_reference(&lv.listview_width).local().clone(),
719            prop_y: sc.mapping.map_property_reference(&geom.y, ctx.state),
720            prop_height: sc.mapping.map_property_reference(&geom.height, ctx.state),
721        }
722    });
723
724    let parent_index = parent_component_container.map(|p| *p.borrow().item_index.get().unwrap());
725    let container_item_index =
726        parent_index.and_then(|pii| sub_component.items.position(|i| i.index_in_tree == pii));
727
728    RepeatedElement {
729        model: super::lower_expression::lower_expression(&repeated.model, ctx).into(),
730        sub_tree: ItemTree {
731            tree: make_tree(ctx.state, &component.root_element, &sc, &[]),
732            root: ctx.state.push_sub_component(sc),
733        },
734        index_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_INDEX),
735        data_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_DATA),
736        index_in_tree: *e.item_index.get().unwrap(),
737        listview,
738        container_item_index,
739    }
740}
741
742fn lower_popup_component(
743    popup: &object_tree::PopupWindow,
744    ctx: &mut ExpressionLoweringCtx,
745    compiler_config: &CompilerConfiguration,
746) -> PopupWindow {
747    let sc = lower_sub_component(&popup.component, ctx.state, Some(&ctx.inner), compiler_config);
748    use super::Expression::PropertyReference as PR;
749    let position = super::lower_expression::make_struct(
750        BuiltinPublicStruct::LogicalPosition,
751        [
752            ("x", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.x, ctx.state))),
753            ("y", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.y, ctx.state))),
754        ],
755    );
756
757    let item_tree = ItemTree {
758        tree: make_tree(ctx.state, &popup.component.root_element, &sc, &[]),
759        root: ctx.state.push_sub_component(sc),
760    };
761    PopupWindow { item_tree, position: position.into() }
762}
763
764fn lower_timer(timer: &object_tree::Timer, ctx: &ExpressionLoweringCtx) -> Timer {
765    Timer {
766        interval: super::Expression::PropertyReference(ctx.map_property_reference(&timer.interval))
767            .into(),
768        running: super::Expression::PropertyReference(ctx.map_property_reference(&timer.running))
769            .into(),
770        // TODO: this calls a callback instead of inlining the callback code directly
771        triggered: super::Expression::CallBackCall {
772            callback: ctx.map_property_reference(&timer.triggered),
773            arguments: Vec::new(),
774        }
775        .into(),
776    }
777}
778
779/// Lower the globals (but not their expressions as we first need to lower all the global to get proper mapping in the state)
780fn lower_global(
781    global: &Rc<Component>,
782    global_index: GlobalIdx,
783    state: &mut LoweringState,
784) -> GlobalComponent {
785    let mut properties = TiVec::new();
786    let mut callbacks = TiVec::new();
787    let mut const_properties = TiVec::new();
788    let mut prop_analysis = TiVec::new();
789    let mut functions = TiVec::new();
790
791    for (p, x) in &global.root_element.borrow().property_declarations {
792        if x.is_alias.is_some() {
793            continue;
794        }
795        let nr = NamedReference::new(&global.root_element, p.clone());
796
797        if let Type::Function(function) = &x.property_type {
798            // TODO: wrap the Rc<langtype::Function> instead of cloning
799            let function_index: FunctionIdx = functions.push_and_get_key(Function {
800                name: p.clone(),
801                ret_ty: function.return_type.clone(),
802                args: function.args.clone(),
803                // will be replaced later
804                code: super::Expression::CodeBlock(Vec::new()),
805            });
806            state.global_properties.insert(
807                nr.clone(),
808                MemberReference::Global { global_index, member: function_index.into() },
809            );
810            continue;
811        } else if let Type::Callback(cb) = &x.property_type {
812            let callback_index: CallbackIdx = callbacks.push_and_get_key(Callback {
813                name: p.clone(),
814                ret_ty: cb.return_type.clone(),
815                args: cb.args.clone(),
816                ty: x.property_type.clone(),
817                use_count: 0.into(),
818            });
819            state.global_properties.insert(
820                nr.clone(),
821                MemberReference::Global { global_index, member: callback_index.into() },
822            );
823            continue;
824        }
825
826        let property_index: PropertyIdx = properties.push_and_get_key(Property {
827            name: p.clone(),
828            ty: x.property_type.clone(),
829            ..Property::default()
830        });
831
832        const_properties.push(nr.is_constant());
833
834        prop_analysis.push(
835            global
836                .root_element
837                .borrow()
838                .property_analysis
839                .borrow()
840                .get(p)
841                .cloned()
842                .unwrap_or_default(),
843        );
844        state.global_properties.insert(
845            nr.clone(),
846            MemberReference::Global { global_index, member: property_index.into() },
847        );
848    }
849
850    let is_builtin = if let Some(builtin) = global.root_element.borrow().native_class() {
851        // We just generate the property so we know how to address them
852        for (p, x) in &builtin.properties {
853            let property_index = properties.push_and_get_key(Property {
854                name: p.clone(),
855                ty: x.ty.clone(),
856                ..Property::default()
857            });
858            let nr = NamedReference::new(&global.root_element, p.clone());
859            state.global_properties.insert(
860                nr,
861                MemberReference::Global { global_index, member: property_index.into() },
862            );
863            prop_analysis.push(PropertyAnalysis {
864                // Assume that a builtin global property can always be set from the builtin code
865                is_set_externally: true,
866                ..global
867                    .root_element
868                    .borrow()
869                    .property_analysis
870                    .borrow()
871                    .get(p)
872                    .cloned()
873                    .unwrap_or_default()
874            });
875        }
876        true
877    } else {
878        false
879    };
880
881    GlobalComponent {
882        name: global.root_element.borrow().id.clone(),
883        init_values: BTreeMap::new(),
884        properties,
885        callbacks,
886        functions,
887        change_callbacks: BTreeMap::new(),
888        const_properties,
889        public_properties: Default::default(),
890        private_properties: global.private_properties.borrow().clone(),
891        exported: !global.exported_global_names.borrow().is_empty(),
892        aliases: global.global_aliases(),
893        is_builtin,
894        from_library: global.from_library.get(),
895        prop_analysis,
896    }
897}
898
899fn lower_global_expressions(
900    global: &Rc<Component>,
901    state: &mut LoweringState,
902    lowered: &mut GlobalComponent,
903) {
904    // Note that this mapping doesn't contain anything useful, everything is in the state
905    let mapping = LoweredSubComponentMapping::default();
906    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: None, component: global };
907    let mut ctx = ExpressionLoweringCtx { inner, state };
908
909    for (prop, binding) in &global.root_element.borrow().bindings {
910        assert!(binding.borrow().two_way_bindings.is_empty());
911        assert!(binding.borrow().animation.is_none());
912        let expression =
913            super::lower_expression::lower_expression(&binding.borrow().expression, &mut ctx);
914
915        let nr = NamedReference::new(&global.root_element, prop.clone());
916        let member_index = match &ctx.state.global_properties[&nr] {
917            MemberReference::Global {
918                member: LocalMemberIndex::Function(function_index), ..
919            } => {
920                lowered.functions[*function_index].code = expression;
921                continue;
922            }
923            MemberReference::Global { member, .. } => member.clone(),
924            _ => unreachable!(),
925        };
926        let is_constant = binding.borrow().analysis.as_ref().is_some_and(|a| a.is_const);
927        lowered.init_values.insert(
928            member_index,
929            BindingExpression {
930                expression: expression.into(),
931                animation: None,
932                is_constant,
933                is_state_info: false,
934                use_count: 0.into(),
935            },
936        );
937    }
938
939    for (prop, expr) in &global.root_element.borrow().change_callbacks {
940        let nr = NamedReference::new(&global.root_element, prop.clone());
941        let MemberReference::Global { member: LocalMemberIndex::Property(property_index), .. } =
942            ctx.state.global_properties[&nr]
943        else {
944            unreachable!()
945        };
946        let expression = super::lower_expression::lower_expression(
947            &tree_Expression::CodeBlock(expr.borrow().clone()),
948            &mut ctx,
949        );
950        lowered.change_callbacks.insert(property_index, expression.into());
951    }
952
953    if let Some(builtin) = global.root_element.borrow().native_class() {
954        if lowered.exported {
955            lowered.public_properties = builtin
956                .properties
957                .iter()
958                .map(|(p, c)| {
959                    let property_reference = mapping.map_property_reference(
960                        &NamedReference::new(&global.root_element, p.clone()),
961                        state,
962                    );
963                    PublicProperty {
964                        name: p.clone(),
965                        ty: c.ty.clone(),
966                        prop: property_reference,
967                        read_only: c.property_visibility == PropertyVisibility::Output,
968                    }
969                })
970                .collect()
971        }
972    } else {
973        lowered.public_properties = public_properties(global, &mapping, state);
974    }
975}
976
977fn make_tree(
978    state: &LoweringState,
979    element: &ElementRc,
980    component: &LoweredSubComponent,
981    sub_component_path: &[SubComponentInstanceIdx],
982) -> TreeNode {
983    let e = element.borrow();
984    let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path));
985    let repeater_count = component.mapping.repeater_count;
986    match component.mapping.element_mapping.get(&ByAddress(element.clone())).unwrap() {
987        LoweredElement::SubComponent { sub_component_index } => {
988            let sub_component = e.sub_component().unwrap();
989            let new_sub_component_path = sub_component_path
990                .iter()
991                .copied()
992                .chain(std::iter::once(*sub_component_index))
993                .collect::<Vec<_>>();
994            let mut tree_node = make_tree(
995                state,
996                &sub_component.root_element,
997                state.sub_component(sub_component),
998                &new_sub_component_path,
999            );
1000            tree_node.children.extend(children);
1001            tree_node.is_accessible |= !e.accessibility_props.0.is_empty();
1002            tree_node
1003        }
1004        LoweredElement::NativeItem { item_index } => TreeNode {
1005            is_accessible: !e.accessibility_props.0.is_empty(),
1006            sub_component_path: sub_component_path.into(),
1007            item_index: itertools::Either::Left(*item_index),
1008            children: children.collect(),
1009        },
1010        LoweredElement::Repeated { repeated_index } => TreeNode {
1011            is_accessible: false,
1012            sub_component_path: sub_component_path.into(),
1013            item_index: itertools::Either::Right(usize::from(*repeated_index) as u32),
1014            children: Vec::new(),
1015        },
1016        LoweredElement::ComponentPlaceholder { repeated_index } => TreeNode {
1017            is_accessible: false,
1018            sub_component_path: sub_component_path.into(),
1019            item_index: itertools::Either::Right(*repeated_index + repeater_count),
1020            children: Vec::new(),
1021        },
1022    }
1023}
1024
1025fn public_properties(
1026    component: &Component,
1027    mapping: &LoweredSubComponentMapping,
1028    state: &LoweringState,
1029) -> PublicProperties {
1030    component
1031        .root_element
1032        .borrow()
1033        .property_declarations
1034        .iter()
1035        .filter(|(_, c)| c.expose_in_public_api)
1036        .map(|(p, c)| {
1037            let property_reference = mapping.map_property_reference(
1038                &NamedReference::new(&component.root_element, p.clone()),
1039                state,
1040            );
1041            PublicProperty {
1042                name: p.clone(),
1043                ty: c.property_type.clone(),
1044                prop: property_reference,
1045                read_only: c.visibility == PropertyVisibility::Output,
1046            }
1047        })
1048        .collect()
1049}