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::expression_tree::Expression as tree_Expression;
8use crate::langtype::{ElementType, Struct, Type};
9use crate::llr::item_tree::*;
10use crate::namedreference::NamedReference;
11use crate::object_tree::{self, Component, ElementRc, PropertyAnalysis, PropertyVisibility};
12use crate::CompilerConfiguration;
13use smol_str::{format_smolstr, SmolStr};
14use std::collections::{BTreeMap, HashMap};
15use std::rc::Rc;
16use typed_index_collections::TiVec;
17
18pub fn lower_to_item_tree(
19    document: &crate::object_tree::Document,
20    compiler_config: &CompilerConfiguration,
21) -> std::io::Result<CompilationUnit> {
22    let mut state = LoweringState::default();
23
24    #[cfg(feature = "bundle-translations")]
25    {
26        state.translation_builder = document.translation_builder.clone();
27    }
28
29    let mut globals = TiVec::new();
30    for g in &document.used_types.borrow().globals {
31        let count = globals.next_key();
32        globals.push(lower_global(g, count, &mut state));
33    }
34    for (g, l) in document.used_types.borrow().globals.iter().zip(&mut globals) {
35        lower_global_expressions(g, &mut state, l);
36    }
37
38    for c in &document.used_types.borrow().sub_components {
39        let sc = lower_sub_component(c, &mut state, None, compiler_config);
40        let idx = state.push_sub_component(sc);
41        state.sub_component_mapping.insert(ByAddress(c.clone()), idx);
42    }
43
44    let public_components = document
45        .exported_roots()
46        .map(|component| {
47            let mut sc = lower_sub_component(&component, &mut state, None, compiler_config);
48            let public_properties = public_properties(&component, &sc.mapping, &state);
49            sc.sub_component.name = component.id.clone();
50            let item_tree = ItemTree {
51                tree: make_tree(&state, &component.root_element, &sc, &[]),
52                root: state.push_sub_component(sc),
53                parent_context: None,
54            };
55            // For C++ codegen, the root component must have the same name as the public component
56            PublicComponent {
57                item_tree,
58                public_properties,
59                private_properties: component.private_properties.borrow().clone(),
60                name: component.id.clone(),
61            }
62        })
63        .collect();
64
65    let popup_menu = document.popup_menu_impl.as_ref().map(|c| {
66        let sc = lower_sub_component(c, &mut state, None, compiler_config);
67        let sub_menu = sc.mapping.map_property_reference(
68            &NamedReference::new(&c.root_element, SmolStr::new_static("sub-menu")),
69            &state,
70        );
71        let activated = sc.mapping.map_property_reference(
72            &NamedReference::new(&c.root_element, SmolStr::new_static("activated")),
73            &state,
74        );
75        let close = sc.mapping.map_property_reference(
76            &NamedReference::new(&c.root_element, SmolStr::new_static("close")),
77            &state,
78        );
79        let entries = sc.mapping.map_property_reference(
80            &NamedReference::new(&c.root_element, SmolStr::new_static("entries")),
81            &state,
82        );
83        let item_tree = ItemTree {
84            tree: make_tree(&state, &c.root_element, &sc, &[]),
85            root: state.push_sub_component(sc),
86            parent_context: None,
87        };
88        PopupMenu { item_tree, sub_menu, activated, close, entries }
89    });
90
91    let 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(&root);
108    Ok(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, PropertyReference>,
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    ) -> PropertyReference {
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                    return property_reference_within_sub_component(
152                        state.map_property_reference(&NamedReference::new(
153                            &base.root_element,
154                            from.name().clone(),
155                        )),
156                        *sub_component_index,
157                    );
158                }
159                unreachable!()
160            }
161            LoweredElement::NativeItem { item_index } => PropertyReference::InNativeItem {
162                sub_component_path: vec![],
163                item_index: *item_index,
164                prop_name: from.name().to_string(),
165            },
166            LoweredElement::Repeated { .. } => unreachable!(),
167            LoweredElement::ComponentPlaceholder { .. } => unreachable!(),
168        }
169    }
170}
171
172pub struct LoweredSubComponent {
173    sub_component: SubComponent,
174    mapping: LoweredSubComponentMapping,
175}
176
177#[derive(Default)]
178pub struct LoweringState {
179    global_properties: HashMap<NamedReference, PropertyReference>,
180    sub_components: TiVec<SubComponentIdx, LoweredSubComponent>,
181    pub sub_component_mapping: HashMap<ByAddress<Rc<Component>>, SubComponentIdx>,
182    #[cfg(feature = "bundle-translations")]
183    pub translation_builder: Option<crate::translations::TranslationsBuilder>,
184}
185
186impl LoweringState {
187    pub fn map_property_reference(&self, from: &NamedReference) -> PropertyReference {
188        if let Some(x) = self.global_properties.get(from) {
189            return x.clone();
190        }
191
192        let element = from.element();
193        let sc = self.sub_component(&element.borrow().enclosing_component.upgrade().unwrap());
194        sc.mapping.map_property_reference(from, self)
195    }
196
197    fn sub_component<'a>(&'a self, component: &Rc<Component>) -> &'a LoweredSubComponent {
198        &self.sub_components[self.sub_component_idx(component)]
199    }
200
201    fn sub_component_idx(&self, component: &Rc<Component>) -> SubComponentIdx {
202        self.sub_component_mapping[&ByAddress(component.clone())]
203    }
204
205    fn push_sub_component(&mut self, sc: LoweredSubComponent) -> SubComponentIdx {
206        self.sub_components.push_and_get_key(sc)
207    }
208}
209
210// Map a PropertyReference within a `sub_component` to a PropertyReference to the component containing it
211fn property_reference_within_sub_component(
212    mut prop_ref: PropertyReference,
213    sub_component: SubComponentInstanceIdx,
214) -> PropertyReference {
215    match &mut prop_ref {
216        PropertyReference::Local { sub_component_path, .. }
217        | PropertyReference::InNativeItem { sub_component_path, .. }
218        | PropertyReference::Function { sub_component_path, .. } => {
219            sub_component_path.insert(0, sub_component);
220        }
221        PropertyReference::InParent { .. } => panic!("the sub-component had no parents"),
222        PropertyReference::Global { .. } | PropertyReference::GlobalFunction { .. } => (),
223    }
224    prop_ref
225}
226
227fn component_id(component: &Rc<Component>) -> SmolStr {
228    if component.is_global() {
229        component.root_element.borrow().id.clone()
230    } else if component.from_library.get() {
231        component.id.clone()
232    } else if component.id.is_empty() {
233        format_smolstr!("Component_{}", component.root_element.borrow().id)
234    } else {
235        format_smolstr!("{}_{}", component.id, component.root_element.borrow().id)
236    }
237}
238
239fn lower_sub_component(
240    component: &Rc<Component>,
241    state: &mut LoweringState,
242    parent_context: Option<&ExpressionLoweringCtxInner>,
243    compiler_config: &CompilerConfiguration,
244) -> LoweredSubComponent {
245    let mut sub_component = SubComponent {
246        name: component_id(component),
247        properties: Default::default(),
248        functions: Default::default(),
249        items: Default::default(),
250        repeated: Default::default(),
251        component_containers: Default::default(),
252        popup_windows: Default::default(),
253        menu_item_trees: Vec::new(),
254        timers: Default::default(),
255        sub_components: Default::default(),
256        property_init: Default::default(),
257        change_callbacks: Default::default(),
258        animations: Default::default(),
259        two_way_bindings: Default::default(),
260        const_properties: Default::default(),
261        init_code: Default::default(),
262        geometries: Default::default(),
263        // just initialize to dummy expression right now and it will be set later
264        layout_info_h: super::Expression::BoolLiteral(false).into(),
265        layout_info_v: super::Expression::BoolLiteral(false).into(),
266        accessible_prop: Default::default(),
267        element_infos: Default::default(),
268        prop_analysis: Default::default(),
269    };
270    let mut mapping = LoweredSubComponentMapping::default();
271    let mut repeated = TiVec::new();
272    let mut accessible_prop = Vec::new();
273    let mut change_callbacks = Vec::new();
274
275    if let Some(parent) = component.parent_element.upgrade() {
276        // Add properties for the model data and index
277        if parent.borrow().repeated.as_ref().is_some_and(|x| !x.is_conditional_element) {
278            sub_component.properties.push(Property {
279                name: "model_data".into(),
280                ty: crate::expression_tree::Expression::RepeaterModelReference {
281                    element: component.parent_element.clone(),
282                }
283                .ty(),
284                ..Property::default()
285            });
286            sub_component.properties.push(Property {
287                name: "model_index".into(),
288                ty: Type::Int32,
289                ..Property::default()
290            });
291        }
292    };
293
294    let s: Option<ElementRc> = None;
295    let mut repeater_offset = 0;
296    crate::object_tree::recurse_elem(&component.root_element, &s, &mut |element, parent| {
297        let elem = element.borrow();
298        for (p, x) in &elem.property_declarations {
299            if x.is_alias.is_some() {
300                continue;
301            }
302            if let Type::Function(function) = &x.property_type {
303                // TODO: Function could wrap the Rc<langtype::Function>
304                //       instead of cloning the return type and args?
305                let function_index = sub_component.functions.push_and_get_key(Function {
306                    name: p.clone(),
307                    ret_ty: function.return_type.clone(),
308                    args: function.args.clone(),
309                    // will be replaced later
310                    code: super::Expression::CodeBlock(vec![]),
311                });
312                mapping.property_mapping.insert(
313                    NamedReference::new(element, p.clone()),
314                    PropertyReference::Function { sub_component_path: vec![], function_index },
315                );
316                continue;
317            }
318            let property_index = sub_component.properties.push_and_get_key(Property {
319                name: format_smolstr!("{}_{}", elem.id, p),
320                ty: x.property_type.clone(),
321                ..Property::default()
322            });
323            mapping.property_mapping.insert(
324                NamedReference::new(element, p.clone()),
325                PropertyReference::Local { sub_component_path: vec![], property_index },
326            );
327        }
328        if elem.repeated.is_some() {
329            let parent = if elem.is_component_placeholder { parent.clone() } else { None };
330
331            mapping.element_mapping.insert(
332                element.clone().into(),
333                LoweredElement::Repeated {
334                    repeated_index: repeated.push_and_get_key((element.clone(), parent)),
335                },
336            );
337            mapping.repeater_count += 1;
338            return None;
339        }
340        match &elem.base_type {
341            ElementType::Component(comp) => {
342                let ty = state.sub_component_idx(comp);
343                let sub_component_index =
344                    sub_component.sub_components.push_and_get_key(SubComponentInstance {
345                        ty,
346                        name: elem.id.clone(),
347                        index_in_tree: *elem.item_index.get().unwrap(),
348                        index_of_first_child_in_tree: *elem
349                            .item_index_of_first_children
350                            .get()
351                            .unwrap(),
352                        repeater_offset,
353                    });
354                mapping.element_mapping.insert(
355                    element.clone().into(),
356                    LoweredElement::SubComponent { sub_component_index },
357                );
358                repeater_offset += comp.repeater_count();
359            }
360
361            ElementType::Native(n) => {
362                let item_index = sub_component.items.push_and_get_key(Item {
363                    ty: n.clone(),
364                    name: elem.id.clone(),
365                    index_in_tree: *elem.item_index.get().unwrap(),
366                });
367                mapping
368                    .element_mapping
369                    .insert(element.clone().into(), LoweredElement::NativeItem { item_index });
370            }
371            _ => unreachable!(),
372        };
373        for (key, nr) in &elem.accessibility_props.0 {
374            // TODO: we also want to split by type (role/string/...)
375            let enum_value =
376                crate::generator::to_pascal_case(key.strip_prefix("accessible-").unwrap());
377            accessible_prop.push((*elem.item_index.get().unwrap(), enum_value, nr.clone()));
378        }
379
380        for (prop, expr) in &elem.change_callbacks {
381            change_callbacks
382                .push((NamedReference::new(element, prop.clone()), expr.borrow().clone()));
383        }
384
385        if compiler_config.debug_info {
386            let element_infos = elem.element_infos();
387            if !element_infos.is_empty() {
388                sub_component.element_infos.insert(*elem.item_index.get().unwrap(), element_infos);
389            }
390        }
391
392        Some(element.clone())
393    });
394
395    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: parent_context, component };
396    let mut ctx = ExpressionLoweringCtx { inner, state };
397
398    crate::generator::handle_property_bindings_init(component, |e, p, binding| {
399        let nr = NamedReference::new(e, p.clone());
400        let prop = ctx.map_property_reference(&nr);
401
402        if let Type::Function { .. } = nr.ty() {
403            if let PropertyReference::Function { sub_component_path, function_index } = prop {
404                assert!(sub_component_path.is_empty());
405                sub_component.functions[function_index].code =
406                    super::lower_expression::lower_expression(&binding.expression, &mut ctx);
407            } else {
408                unreachable!()
409            }
410            return;
411        }
412
413        for tw in &binding.two_way_bindings {
414            sub_component.two_way_bindings.push((prop.clone(), ctx.map_property_reference(tw)))
415        }
416        if !matches!(binding.expression, tree_Expression::Invalid) {
417            let expression =
418                super::lower_expression::lower_expression(&binding.expression, &mut ctx).into();
419
420            let is_constant = binding.analysis.as_ref().is_some_and(|a| a.is_const);
421            let animation = binding
422                .animation
423                .as_ref()
424                .filter(|_| !is_constant)
425                .map(|a| super::lower_expression::lower_animation(a, &mut ctx));
426
427            sub_component.prop_analysis.insert(
428                prop.clone(),
429                PropAnalysis {
430                    property_init: Some(sub_component.property_init.len()),
431                    analysis: get_property_analysis(e, p),
432                },
433            );
434
435            let is_state_info = matches!(
436                e.borrow().lookup_property(p).property_type,
437                Type::Struct(s) if s.name.as_ref().is_some_and(|name| name.ends_with("::StateInfo"))
438            );
439
440            sub_component.property_init.push((
441                prop.clone(),
442                BindingExpression {
443                    expression,
444                    animation,
445                    is_constant,
446                    is_state_info,
447                    use_count: 0.into(),
448                },
449            ));
450        }
451
452        if e.borrow()
453            .property_analysis
454            .borrow()
455            .get(p)
456            .is_none_or(|a| a.is_set || a.is_set_externally)
457        {
458            if let Some(anim) = binding.animation.as_ref() {
459                match super::lower_expression::lower_animation(anim, &mut ctx) {
460                    Animation::Static(anim) => {
461                        sub_component.animations.insert(prop, anim);
462                    }
463                    Animation::Transition(_) => {
464                        // Cannot set a property with a transition anyway
465                    }
466                }
467            }
468        }
469    });
470    sub_component.repeated = repeated
471        .into_iter()
472        .map(|(elem, parent)| {
473            lower_repeated_component(&elem, parent, &sub_component, &mut ctx, compiler_config)
474        })
475        .collect();
476    for s in &mut sub_component.sub_components {
477        s.repeater_offset +=
478            (sub_component.repeated.len() + sub_component.component_containers.len()) as u32;
479    }
480
481    sub_component.popup_windows = component
482        .popup_windows
483        .borrow()
484        .iter()
485        .map(|popup| lower_popup_component(popup, &mut ctx, compiler_config))
486        .collect();
487
488    sub_component.menu_item_trees = component
489        .menu_item_tree
490        .borrow()
491        .iter()
492        .map(|c| {
493            let sc = lower_sub_component(c, ctx.state, Some(&ctx.inner), compiler_config);
494            ItemTree {
495                tree: make_tree(ctx.state, &c.root_element, &sc, &[]),
496                root: ctx.state.push_sub_component(sc),
497                parent_context: None,
498            }
499        })
500        .collect();
501
502    sub_component.timers = component.timers.borrow().iter().map(|t| lower_timer(t, &ctx)).collect();
503
504    crate::generator::for_each_const_properties(component, |elem, n| {
505        let x = ctx.map_property_reference(&NamedReference::new(elem, n.clone()));
506        // ensure that all const properties have analysis
507        sub_component.prop_analysis.entry(x.clone()).or_insert_with(|| PropAnalysis {
508            property_init: None,
509            analysis: get_property_analysis(elem, n),
510        });
511        sub_component.const_properties.push(x);
512    });
513
514    sub_component.init_code = component
515        .init_code
516        .borrow()
517        .iter()
518        .map(|e| super::lower_expression::lower_expression(e, &mut ctx).into())
519        .collect();
520
521    sub_component.layout_info_h = super::lower_expression::get_layout_info(
522        &component.root_element,
523        &mut ctx,
524        &component.root_constraints.borrow(),
525        crate::layout::Orientation::Horizontal,
526    )
527    .into();
528    sub_component.layout_info_v = super::lower_expression::get_layout_info(
529        &component.root_element,
530        &mut ctx,
531        &component.root_constraints.borrow(),
532        crate::layout::Orientation::Vertical,
533    )
534    .into();
535
536    sub_component.accessible_prop = accessible_prop
537        .into_iter()
538        .map(|(idx, key, nr)| {
539            let prop = ctx.map_property_reference(&nr);
540            let expr = match nr.ty() {
541                Type::Bool => super::Expression::Condition {
542                    condition: super::Expression::PropertyReference(prop).into(),
543                    true_expr: super::Expression::StringLiteral("true".into()).into(),
544                    false_expr: super::Expression::StringLiteral("false".into()).into(),
545                },
546                Type::Int32 | Type::Float32 => super::Expression::Cast {
547                    from: super::Expression::PropertyReference(prop).into(),
548                    to: Type::String,
549                },
550                Type::String => super::Expression::PropertyReference(prop),
551                Type::Enumeration(e) if e.name == "AccessibleRole" => {
552                    super::Expression::PropertyReference(prop)
553                }
554                Type::Callback(callback) => super::Expression::CallBackCall {
555                    callback: prop,
556                    arguments: (0..callback.args.len())
557                        .map(|index| super::Expression::FunctionParameterReference { index })
558                        .collect(),
559                },
560                _ => panic!("Invalid type for accessible property"),
561            };
562
563            ((idx, key), expr.into())
564        })
565        .collect();
566
567    sub_component.change_callbacks = change_callbacks
568        .into_iter()
569        .map(|(nr, exprs)| {
570            let prop = ctx.map_property_reference(&nr);
571            let expr = super::lower_expression::lower_expression(
572                &tree_Expression::CodeBlock(exprs),
573                &mut ctx,
574            );
575            (prop, expr.into())
576        })
577        .collect();
578
579    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |element, _| {
580        let elem = element.borrow();
581        if elem.repeated.is_some() {
582            return;
583        };
584        let Some(geom) = &elem.geometry_props else { return };
585        let item_index = *elem.item_index.get().unwrap() as usize;
586        if item_index >= sub_component.geometries.len() {
587            sub_component.geometries.resize(item_index + 1, Default::default());
588        }
589        sub_component.geometries[item_index] = Some(lower_geometry(geom, &ctx).into());
590    });
591
592    LoweredSubComponent { sub_component, mapping }
593}
594
595fn lower_geometry(
596    geom: &crate::object_tree::GeometryProps,
597    ctx: &ExpressionLoweringCtx<'_>,
598) -> super::Expression {
599    let mut fields = BTreeMap::default();
600    let mut values = BTreeMap::default();
601    for (f, v) in [("x", &geom.x), ("y", &geom.y), ("width", &geom.width), ("height", &geom.height)]
602    {
603        fields.insert(f.into(), Type::LogicalLength);
604        values
605            .insert(f.into(), super::Expression::PropertyReference(ctx.map_property_reference(v)));
606    }
607    super::Expression::Struct {
608        ty: Rc::new(Struct { fields, name: None, node: None, rust_attributes: None }),
609        values,
610    }
611}
612
613fn get_property_analysis(elem: &ElementRc, p: &str) -> crate::object_tree::PropertyAnalysis {
614    let mut a = elem.borrow().property_analysis.borrow().get(p).cloned().unwrap_or_default();
615    let mut elem = elem.clone();
616    loop {
617        if let Some(d) = elem.borrow().property_declarations.get(p) {
618            if let Some(nr) = &d.is_alias {
619                a.merge(&get_property_analysis(&nr.element(), nr.name()));
620            }
621            return a;
622        }
623        let base = elem.borrow().base_type.clone();
624        match base {
625            ElementType::Native(n) => {
626                if n.properties.get(p).is_some_and(|p| p.is_native_output()) {
627                    a.is_set = true;
628                }
629            }
630            ElementType::Component(c) => {
631                elem = c.root_element.clone();
632                if let Some(a2) = elem.borrow().property_analysis.borrow().get(p) {
633                    a.merge_with_base(a2);
634                }
635                continue;
636            }
637            _ => (),
638        };
639        return a;
640    }
641}
642
643fn lower_repeated_component(
644    elem: &ElementRc,
645    parent_component_container: Option<ElementRc>,
646    sub_component: &SubComponent,
647    ctx: &mut ExpressionLoweringCtx,
648    compiler_config: &CompilerConfiguration,
649) -> RepeatedElement {
650    let e = elem.borrow();
651    let component = e.base_type.as_component().clone();
652    let repeated = e.repeated.as_ref().unwrap();
653
654    let sc = lower_sub_component(&component, ctx.state, Some(&ctx.inner), compiler_config);
655
656    let listview = repeated.is_listview.as_ref().map(|lv| {
657        let geom = component.root_element.borrow().geometry_props.clone().unwrap();
658        ListViewInfo {
659            viewport_y: ctx.map_property_reference(&lv.viewport_y),
660            viewport_height: ctx.map_property_reference(&lv.viewport_height),
661            viewport_width: ctx.map_property_reference(&lv.viewport_width),
662            listview_height: ctx.map_property_reference(&lv.listview_height),
663            listview_width: ctx.map_property_reference(&lv.listview_width),
664            prop_y: sc.mapping.map_property_reference(&geom.y, ctx.state),
665            prop_height: sc.mapping.map_property_reference(&geom.height, ctx.state),
666        }
667    });
668
669    let parent_index = parent_component_container.map(|p| *p.borrow().item_index.get().unwrap());
670    let container_item_index =
671        parent_index.and_then(|pii| sub_component.items.position(|i| i.index_in_tree == pii));
672
673    RepeatedElement {
674        model: super::lower_expression::lower_expression(&repeated.model, ctx).into(),
675        sub_tree: ItemTree {
676            tree: make_tree(ctx.state, &component.root_element, &sc, &[]),
677            root: ctx.state.push_sub_component(sc),
678            parent_context: Some(e.enclosing_component.upgrade().unwrap().id.clone()),
679        },
680        index_prop: (!repeated.is_conditional_element).then_some(1usize.into()),
681        data_prop: (!repeated.is_conditional_element).then_some(0usize.into()),
682        index_in_tree: *e.item_index.get().unwrap(),
683        listview,
684        container_item_index,
685    }
686}
687
688fn lower_popup_component(
689    popup: &object_tree::PopupWindow,
690    ctx: &mut ExpressionLoweringCtx,
691    compiler_config: &CompilerConfiguration,
692) -> PopupWindow {
693    let sc = lower_sub_component(&popup.component, ctx.state, Some(&ctx.inner), compiler_config);
694    use super::Expression::PropertyReference as PR;
695    let position = super::lower_expression::make_struct(
696        "LogicalPosition",
697        [
698            ("x", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.x, ctx.state))),
699            ("y", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.y, ctx.state))),
700        ],
701    );
702
703    let item_tree = ItemTree {
704        tree: make_tree(ctx.state, &popup.component.root_element, &sc, &[]),
705        root: ctx.state.push_sub_component(sc),
706        parent_context: Some(
707            popup
708                .component
709                .parent_element
710                .upgrade()
711                .unwrap()
712                .borrow()
713                .enclosing_component
714                .upgrade()
715                .unwrap()
716                .id
717                .clone(),
718        ),
719    };
720    PopupWindow { item_tree, position: position.into() }
721}
722
723fn lower_timer(timer: &object_tree::Timer, ctx: &ExpressionLoweringCtx) -> Timer {
724    Timer {
725        interval: super::Expression::PropertyReference(ctx.map_property_reference(&timer.interval))
726            .into(),
727        running: super::Expression::PropertyReference(ctx.map_property_reference(&timer.running))
728            .into(),
729        // TODO: this calls a callback instead of inlining the callback code directly
730        triggered: super::Expression::CallBackCall {
731            callback: ctx.map_property_reference(&timer.triggered),
732            arguments: vec![],
733        }
734        .into(),
735    }
736}
737
738/// Lower the globals (but not their expressions as we first need to lower all the global to get proper mapping in the state)
739fn lower_global(
740    global: &Rc<Component>,
741    global_index: GlobalIdx,
742    state: &mut LoweringState,
743) -> GlobalComponent {
744    let mut properties = TiVec::new();
745    let mut const_properties = TiVec::new();
746    let mut prop_analysis = TiVec::new();
747    let mut functions = TiVec::new();
748
749    for (p, x) in &global.root_element.borrow().property_declarations {
750        if x.is_alias.is_some() {
751            continue;
752        }
753        let nr = NamedReference::new(&global.root_element, p.clone());
754
755        if let Type::Function(function) = &x.property_type {
756            // TODO: wrap the Rc<langtype::Function> instead of cloning
757            let function_index = functions.push_and_get_key(Function {
758                name: p.clone(),
759                ret_ty: function.return_type.clone(),
760                args: function.args.clone(),
761                // will be replaced later
762                code: super::Expression::CodeBlock(vec![]),
763            });
764            state.global_properties.insert(
765                nr.clone(),
766                PropertyReference::GlobalFunction { global_index, function_index },
767            );
768            continue;
769        }
770
771        let property_index = properties.push_and_get_key(Property {
772            name: p.clone(),
773            ty: x.property_type.clone(),
774            ..Property::default()
775        });
776        if !matches!(x.property_type, Type::Callback { .. }) {
777            const_properties.push(nr.is_constant());
778        } else {
779            const_properties.push(false);
780        }
781        prop_analysis.push(
782            global
783                .root_element
784                .borrow()
785                .property_analysis
786                .borrow()
787                .get(p)
788                .cloned()
789                .unwrap_or_default(),
790        );
791        state
792            .global_properties
793            .insert(nr.clone(), PropertyReference::Global { global_index, property_index });
794    }
795
796    let is_builtin = if let Some(builtin) = global.root_element.borrow().native_class() {
797        // We just generate the property so we know how to address them
798        for (p, x) in &builtin.properties {
799            let property_index = properties.push_and_get_key(Property {
800                name: p.clone(),
801                ty: x.ty.clone(),
802                ..Property::default()
803            });
804            let nr = NamedReference::new(&global.root_element, p.clone());
805            state
806                .global_properties
807                .insert(nr, PropertyReference::Global { global_index, property_index });
808            prop_analysis.push(PropertyAnalysis {
809                // Assume that a builtin global property can always be set from the builtin code
810                is_set_externally: true,
811                ..global
812                    .root_element
813                    .borrow()
814                    .property_analysis
815                    .borrow()
816                    .get(p)
817                    .cloned()
818                    .unwrap_or_default()
819            });
820        }
821        true
822    } else {
823        false
824    };
825
826    GlobalComponent {
827        name: global.root_element.borrow().id.clone(),
828        init_values: typed_index_collections::ti_vec![None; properties.len()],
829        properties,
830        functions,
831        change_callbacks: BTreeMap::new(),
832        const_properties,
833        public_properties: Default::default(),
834        private_properties: global.private_properties.borrow().clone(),
835        exported: !global.exported_global_names.borrow().is_empty(),
836        aliases: global.global_aliases(),
837        is_builtin,
838        from_library: global.from_library.get(),
839        prop_analysis,
840    }
841}
842
843fn lower_global_expressions(
844    global: &Rc<Component>,
845    state: &mut LoweringState,
846    lowered: &mut GlobalComponent,
847) {
848    // Note that this mapping doesn't contain anything useful, everything is in the state
849    let mapping = LoweredSubComponentMapping::default();
850    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: None, component: global };
851    let mut ctx = ExpressionLoweringCtx { inner, state };
852
853    for (prop, binding) in &global.root_element.borrow().bindings {
854        assert!(binding.borrow().two_way_bindings.is_empty());
855        assert!(binding.borrow().animation.is_none());
856        let expression =
857            super::lower_expression::lower_expression(&binding.borrow().expression, &mut ctx);
858
859        let nr = NamedReference::new(&global.root_element, prop.clone());
860        let property_index = match ctx.state.global_properties[&nr] {
861            PropertyReference::Global { property_index, .. } => property_index,
862            PropertyReference::GlobalFunction { function_index, .. } => {
863                lowered.functions[function_index].code = expression;
864                continue;
865            }
866            _ => unreachable!(),
867        };
868        let is_constant = binding.borrow().analysis.as_ref().is_some_and(|a| a.is_const);
869        lowered.init_values[property_index] = Some(BindingExpression {
870            expression: expression.into(),
871            animation: None,
872            is_constant,
873            is_state_info: false,
874            use_count: 0.into(),
875        });
876    }
877
878    for (prop, expr) in &global.root_element.borrow().change_callbacks {
879        let nr = NamedReference::new(&global.root_element, prop.clone());
880        let property_index = match ctx.state.global_properties[&nr] {
881            PropertyReference::Global { property_index, .. } => property_index,
882            _ => unreachable!(),
883        };
884        let expression = super::lower_expression::lower_expression(
885            &tree_Expression::CodeBlock(expr.borrow().clone()),
886            &mut ctx,
887        );
888        lowered.change_callbacks.insert(property_index, expression.into());
889    }
890
891    lowered.public_properties = public_properties(global, &mapping, state);
892}
893
894fn make_tree(
895    state: &LoweringState,
896    element: &ElementRc,
897    component: &LoweredSubComponent,
898    sub_component_path: &[SubComponentInstanceIdx],
899) -> TreeNode {
900    let e = element.borrow();
901    let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path));
902    let repeater_count = component.mapping.repeater_count;
903    match component.mapping.element_mapping.get(&ByAddress(element.clone())).unwrap() {
904        LoweredElement::SubComponent { sub_component_index } => {
905            let sub_component = e.sub_component().unwrap();
906            let new_sub_component_path = sub_component_path
907                .iter()
908                .copied()
909                .chain(std::iter::once(*sub_component_index))
910                .collect::<Vec<_>>();
911            let mut tree_node = make_tree(
912                state,
913                &sub_component.root_element,
914                state.sub_component(sub_component),
915                &new_sub_component_path,
916            );
917            tree_node.children.extend(children);
918            tree_node.is_accessible |= !e.accessibility_props.0.is_empty();
919            tree_node
920        }
921        LoweredElement::NativeItem { item_index } => TreeNode {
922            is_accessible: !e.accessibility_props.0.is_empty(),
923            sub_component_path: sub_component_path.into(),
924            item_index: itertools::Either::Left(*item_index),
925            children: children.collect(),
926        },
927        LoweredElement::Repeated { repeated_index } => TreeNode {
928            is_accessible: false,
929            sub_component_path: sub_component_path.into(),
930            item_index: itertools::Either::Right(usize::from(*repeated_index) as u32),
931            children: vec![],
932        },
933        LoweredElement::ComponentPlaceholder { repeated_index } => TreeNode {
934            is_accessible: false,
935            sub_component_path: sub_component_path.into(),
936            item_index: itertools::Either::Right(*repeated_index + repeater_count),
937            children: vec![],
938        },
939    }
940}
941
942fn public_properties(
943    component: &Component,
944    mapping: &LoweredSubComponentMapping,
945    state: &LoweringState,
946) -> PublicProperties {
947    component
948        .root_element
949        .borrow()
950        .property_declarations
951        .iter()
952        .filter(|(_, c)| c.expose_in_public_api)
953        .map(|(p, c)| {
954            let property_reference = mapping.map_property_reference(
955                &NamedReference::new(&component.root_element, p.clone()),
956                state,
957            );
958            PublicProperty {
959                name: p.clone(),
960                ty: c.property_type.clone(),
961                prop: property_reference,
962                read_only: c.visibility == PropertyVisibility::Output,
963            }
964        })
965        .collect()
966}