Skip to main content

i_slint_compiler/llr/
lower_expression.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 std::cell::RefCell;
5use std::collections::BTreeMap;
6use std::rc::{Rc, Weak};
7use std::sync::Arc;
8
9use smol_str::SmolStr;
10
11use super::lower_layout_expression::{
12    compute_box_layout_info, compute_flexbox_layout_info, compute_grid_layout_info,
13    organize_grid_layout, solve_box_layout, solve_flexbox_layout, solve_grid_layout,
14};
15use super::lower_to_item_tree::{LoweredSubComponentMapping, LoweringState};
16use super::{Animation, LocalMemberReference, MemberReference, PropertyIdx};
17use crate::expression_tree::{
18    BuiltinFunction, Callable, Expression as tree_Expression, MouseCursorInner,
19};
20use crate::langtype::{BuiltinStruct, ConstantExpression, Struct, StructName, Type};
21use crate::llr::ArrayOutput as llr_ArrayOutput;
22use crate::llr::Expression as llr_Expression;
23use crate::namedreference::NamedReference;
24use crate::object_tree::{Component, Element, ElementRc, ElementWeak, PropertyAnimation};
25use crate::typeregister::BUILTIN;
26
27pub struct ExpressionLoweringCtxInner<'a> {
28    pub component: &'a Rc<crate::object_tree::Component>,
29    /// The mapping for the current component
30    pub mapping: &'a LoweredSubComponentMapping,
31    pub parent: Option<&'a ExpressionLoweringCtxInner<'a>>,
32}
33#[derive(derive_more::Deref)]
34pub struct ExpressionLoweringCtx<'a> {
35    pub state: &'a mut LoweringState,
36    #[deref]
37    pub inner: ExpressionLoweringCtxInner<'a>,
38}
39
40impl ExpressionLoweringCtx<'_> {
41    /// How many parent contexts up `enclosing` is, together with its lowering context.
42    fn find_component(
43        &self,
44        enclosing: &Rc<Component>,
45    ) -> (usize, &ExpressionLoweringCtxInner<'_>) {
46        let mut level = 0;
47        let mut map = &self.inner;
48        while !Rc::ptr_eq(enclosing, map.component) {
49            map = map.parent.unwrap_or_else(|| {
50                panic!(
51                    "Could not find component {:?} from component {:?}",
52                    enclosing.id, self.component.id
53                )
54            });
55            level += 1;
56        }
57        (level, map)
58    }
59
60    pub fn map_property_reference(&self, from: &NamedReference) -> MemberReference {
61        let element = from.element();
62        let enclosing = &element.borrow().enclosing_component.upgrade().unwrap();
63        let (level, map) =
64            if enclosing.is_global() { (0, &self.inner) } else { self.find_component(enclosing) };
65        let mut r = map.mapping.map_property_reference(from, self.state);
66        if let MemberReference::Relative { parent_level, .. } = &mut r {
67            *parent_level += level;
68        }
69        r
70    }
71}
72
73impl super::TypeResolutionContext for ExpressionLoweringCtx<'_> {
74    fn property_ty(&self, _: &MemberReference) -> &Type {
75        unimplemented!()
76    }
77}
78
79/// Lower a constant expression, such as the default value of a struct field
80/// (see [`crate::langtype::Struct::field_defaults`]),
81/// so that the code generators can compile it with their regular expression compilation.
82pub fn lower_constant_expression(expression: &ConstantExpression) -> llr_Expression {
83    match expression {
84        ConstantExpression::StringLiteral(s) => llr_Expression::StringLiteral(s.clone()),
85        ConstantExpression::NumberLiteral(n, _unit) => llr_Expression::NumberLiteral(*n),
86        ConstantExpression::BoolLiteral(b) => llr_Expression::BoolLiteral(*b),
87        ConstantExpression::EnumerationValue(e) => llr_Expression::EnumerationValue(e.clone()),
88        ConstantExpression::Cast { from, to } => {
89            llr_Expression::Cast { from: Box::new(lower_constant_expression(from)), to: to.clone() }
90        }
91        ConstantExpression::UnaryOp { sub, op } => {
92            llr_Expression::UnaryOp { sub: Box::new(lower_constant_expression(sub)), op: *op }
93        }
94        ConstantExpression::Struct { ty, values } => llr_Expression::Struct {
95            ty: ty.clone(),
96            values: values.iter().map(|(k, v)| (k.clone(), lower_constant_expression(v))).collect(),
97        },
98        ConstantExpression::Array { element_ty, values } => llr_Expression::Array {
99            element_ty: element_ty.clone(),
100            values: values.iter().map(lower_constant_expression).collect(),
101            output: llr_ArrayOutput::Model,
102        },
103    }
104}
105
106/// The body of every non-trivial match arm lives in its own `#[inline(never)]`
107/// helper function: this function recurses for nested expressions, and with all
108/// arm bodies inlined, its stack frame in unoptimized builds becomes so large
109/// that deeply nested expressions overflow the stack.
110pub fn lower_expression(
111    expression: &tree_Expression,
112    ctx: &mut ExpressionLoweringCtx<'_>,
113) -> llr_Expression {
114    match expression {
115        tree_Expression::Invalid => {
116            panic!("internal error, encountered invalid expression at code generation time")
117        }
118        tree_Expression::Uncompiled(_) => panic!(),
119        tree_Expression::StringLiteral(s) => llr_Expression::StringLiteral(s.clone()),
120        tree_Expression::NumberLiteral(n, _unit) => llr_Expression::NumberLiteral(*n),
121        tree_Expression::BoolLiteral(b) => llr_Expression::BoolLiteral(*b),
122        tree_Expression::PropertyReference(nr) => {
123            llr_Expression::PropertyReference(ctx.map_property_reference(nr))
124        }
125        tree_Expression::ElementReference(_) => lower_element_reference(expression, ctx),
126        tree_Expression::RepeaterIndexReference { element } => llr_Expression::PropertyReference(
127            repeater_special_property(element, ctx.component, PropertyIdx::REPEATER_INDEX),
128        ),
129        tree_Expression::RepeaterModelReference { element } => llr_Expression::PropertyReference(
130            repeater_special_property(element, ctx.component, PropertyIdx::REPEATER_DATA),
131        ),
132        tree_Expression::FunctionParameterReference { index, .. } => {
133            llr_Expression::FunctionParameterReference { index: *index }
134        }
135        tree_Expression::StoreLocalVariable { name, value } => llr_Expression::StoreLocalVariable {
136            name: name.clone(),
137            value: Box::new(lower_expression(value, ctx)),
138        },
139        tree_Expression::ReadLocalVariable { name, ty } => {
140            llr_Expression::ReadLocalVariable { name: name.clone(), ty: ty.clone() }
141        }
142        tree_Expression::StructFieldAccess { base, name } => llr_Expression::StructFieldAccess {
143            base: Box::new(lower_expression(base, ctx)),
144            name: name.clone(),
145        },
146        tree_Expression::ArrayIndex { array, index } => llr_Expression::ArrayIndex {
147            array: Box::new(lower_expression(array, ctx)),
148            index: Box::new(lower_expression(index, ctx)),
149        },
150        tree_Expression::Cast { from, to } => {
151            llr_Expression::Cast { from: Box::new(lower_expression(from, ctx)), to: to.clone() }
152        }
153        tree_Expression::CodeBlock(expr) => {
154            llr_Expression::CodeBlock(expr.iter().map(|e| lower_expression(e, ctx)).collect::<_>())
155        }
156        tree_Expression::FunctionCall { .. } => lower_function_call(expression, ctx),
157        tree_Expression::SelfAssignment { lhs, rhs, op, .. } => {
158            lower_assignment(lhs, rhs, *op, ctx)
159        }
160        tree_Expression::BinaryExpression { .. } => lower_binary_expression(expression, ctx),
161        tree_Expression::UnaryOp { sub, op } => {
162            llr_Expression::UnaryOp { sub: Box::new(lower_expression(sub, ctx)), op: *op }
163        }
164        tree_Expression::ImageReference { resource_ref, nine_slice, .. } => {
165            llr_Expression::ImageReference {
166                resource_ref: resource_ref.clone(),
167                nine_slice: *nine_slice,
168            }
169        }
170        tree_Expression::Condition { .. } => lower_condition(expression, ctx),
171        tree_Expression::Array { element_ty, values } => llr_Expression::Array {
172            element_ty: if *element_ty == Type::Void { Type::Int32 } else { element_ty.clone() },
173            values: values.iter().map(|e| lower_expression(e, ctx)).collect::<_>(),
174            output: llr_ArrayOutput::Model,
175        },
176        tree_Expression::Struct { ty, values } => llr_Expression::Struct {
177            ty: ty.clone(),
178            values: values
179                .iter()
180                .map(|(s, e)| (s.clone(), lower_expression(e, ctx)))
181                .collect::<_>(),
182        },
183        tree_Expression::PathData(data) => compile_path(data, ctx),
184        tree_Expression::EasingCurve(x) => llr_Expression::EasingCurve(x.clone()),
185        tree_Expression::MouseCursor(_) => lower_mouse_cursor(expression, ctx),
186        tree_Expression::LinearGradient { .. } => lower_linear_gradient(expression, ctx),
187        tree_Expression::RadialGradient { .. } => lower_radial_gradient(expression, ctx),
188        tree_Expression::ConicGradient { .. } => lower_conic_gradient(expression, ctx),
189        tree_Expression::EnumerationValue(e) => llr_Expression::EnumerationValue(e.clone()),
190        tree_Expression::Keys(ks) => llr_Expression::KeysLiteral(ks.clone()),
191        tree_Expression::ReturnStatement(..) => {
192            panic!("The remove return pass should have removed all return")
193        }
194        tree_Expression::LayoutCacheAccess { .. } => lower_layout_cache_access(expression, ctx),
195        tree_Expression::GridRepeaterCacheAccess { .. } => {
196            lower_grid_repeater_cache_access(expression, ctx)
197        }
198        tree_Expression::OrganizeGridLayout(l) => organize_grid_layout(l, ctx),
199        tree_Expression::ComputeBoxLayoutInfo { layout, orientation, cross_axis_size } => {
200            compute_box_layout_info(layout, *orientation, ctx, cross_axis_size.as_deref())
201        }
202        tree_Expression::ComputeGridLayoutInfo {
203            layout_organized_data_prop,
204            layout,
205            orientation,
206            cross_axis_size,
207        } => compute_grid_layout_info(
208            layout_organized_data_prop,
209            layout,
210            *orientation,
211            ctx,
212            cross_axis_size.as_deref(),
213        ),
214        tree_Expression::SolveBoxLayout(l, o) => solve_box_layout(l, *o, ctx),
215        tree_Expression::SolveGridLayout { layout_organized_data_prop, layout, orientation } => {
216            solve_grid_layout(layout_organized_data_prop, layout, *orientation, ctx)
217        }
218        tree_Expression::SolveFlexboxLayout(l) => solve_flexbox_layout(l, ctx),
219        tree_Expression::ComputeFlexboxLayoutInfo { layout, orientation, cross_axis_size } => {
220            compute_flexbox_layout_info(layout, *orientation, ctx, cross_axis_size.as_deref())
221        }
222        tree_Expression::MinMax { ty, op, lhs, rhs } => llr_Expression::MinMax {
223            ty: ty.clone(),
224            op: *op,
225            lhs: Box::new(lower_expression(lhs, ctx)),
226            rhs: Box::new(lower_expression(rhs, ctx)),
227        },
228        tree_Expression::EmptyComponentFactory => llr_Expression::EmptyComponentFactory,
229        tree_Expression::EmptyDataTransfer => llr_Expression::EmptyDataTransfer,
230        tree_Expression::DebugHook { expression, id, .. } => llr_Expression::DebugHook {
231            expression: Box::new(lower_expression(expression, ctx)),
232            id: id.clone(),
233        },
234        tree_Expression::Closure { arg_name, expression } => llr_Expression::Closure {
235            arg_name: arg_name.clone(),
236            expression: Box::new(lower_expression(expression, ctx)),
237        },
238    }
239}
240
241#[inline(never)]
242fn lower_binary_expression(
243    expression: &tree_Expression,
244    ctx: &mut ExpressionLoweringCtx<'_>,
245) -> llr_Expression {
246    // Long chains of binary operators, such as `a && b && c && ...`, nest on the
247    // left side. Iterate that spine instead of recursing into it, so that the
248    // stack depth stays bounded no matter how long the chain is.
249    let mut spine = Vec::new();
250    let mut node = expression;
251    while let tree_Expression::BinaryExpression { lhs, rhs, op, .. } = node {
252        spine.push((rhs, *op));
253        node = lhs;
254    }
255    let mut result = lower_expression(node, ctx);
256    for (rhs, op) in spine.into_iter().rev() {
257        result = llr_Expression::BinaryExpression {
258            lhs: Box::new(result),
259            rhs: Box::new(lower_expression(rhs, ctx)),
260            op,
261        };
262    }
263    result
264}
265
266#[inline(never)]
267fn lower_element_reference(
268    expression: &tree_Expression,
269    ctx: &mut ExpressionLoweringCtx<'_>,
270) -> llr_Expression {
271    let tree_Expression::ElementReference(e) = expression else { unreachable!() };
272    let elem = e.upgrade().unwrap();
273    let enclosing = elem.borrow().enclosing_component.upgrade().unwrap();
274    // When within a ShowPopupMenu builtin function, this is a reference to the root of the menu item tree
275    if Rc::ptr_eq(&elem, &enclosing.root_element)
276        && let Some(idx) =
277            ctx.component.menu_item_tree.borrow().iter().position(|c| Rc::ptr_eq(c, &enclosing))
278    {
279        return llr_Expression::NumberLiteral(idx as _);
280    }
281
282    // We map an element reference to a reference to the property "" inside that native item
283    llr_Expression::PropertyReference(
284        ctx.map_property_reference(&NamedReference::new(&elem, SmolStr::default())),
285    )
286}
287
288#[inline(never)]
289fn lower_function_call(
290    expression: &tree_Expression,
291    ctx: &mut ExpressionLoweringCtx<'_>,
292) -> llr_Expression {
293    let tree_Expression::FunctionCall { function, arguments, source_location } = expression else {
294        unreachable!()
295    };
296    match function {
297        Callable::Builtin(BuiltinFunction::RestartTimer) => lower_restart_timer(arguments, ctx),
298        Callable::Builtin(BuiltinFunction::ShowPopupWindow) => {
299            lower_show_popup_window(arguments, ctx)
300        }
301        Callable::Builtin(BuiltinFunction::ClosePopupWindow) => {
302            lower_close_popup_window(arguments, ctx)
303        }
304        Callable::Builtin(f) => {
305            let mut arguments =
306                arguments.iter().map(|e| lower_expression(e, ctx)).collect::<Vec<_>>();
307            // https://github.com/rust-lang/rust-clippy/issues/16191
308            #[allow(clippy::collapsible_if)]
309            if *f == BuiltinFunction::Translate {
310                if let llr_Expression::Array { output, .. } = &mut arguments[3] {
311                    *output = llr_ArrayOutput::Slice;
312                }
313                #[cfg(feature = "bundle-translations")]
314                if let Some(translation_builder) = ctx.state.translation_builder.as_mut() {
315                    return translation_builder.lower_translate_call(arguments);
316                }
317            }
318            if *f == BuiltinFunction::ParseMarkdown
319                && let Some(llr_Expression::Array { output, .. }) = &mut arguments.get_mut(1)
320            {
321                *output = llr_ArrayOutput::Slice;
322            }
323            llr_Expression::BuiltinFunctionCall {
324                function: f.clone(),
325                arguments,
326                source_location: source_location.clone(),
327            }
328        }
329        Callable::Callback(nr) => {
330            let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
331            llr_Expression::CallBackCall { callback: ctx.map_property_reference(nr), arguments }
332        }
333        Callable::Function(nr)
334            if nr
335                .element()
336                .borrow()
337                .native_class()
338                .is_some_and(|n| n.properties.contains_key(nr.name())) =>
339        {
340            llr_Expression::ItemMemberFunctionCall { function: ctx.map_property_reference(nr) }
341        }
342        Callable::Function(nr) => {
343            let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
344            llr_Expression::FunctionCall { function: ctx.map_property_reference(nr), arguments }
345        }
346    }
347}
348
349#[inline(never)]
350fn lower_condition(
351    expression: &tree_Expression,
352    ctx: &mut ExpressionLoweringCtx<'_>,
353) -> llr_Expression {
354    let tree_Expression::Condition { condition, true_expr, false_expr, .. } = expression else {
355        unreachable!()
356    };
357    let (true_ty, false_ty) = (true_expr.ty(), false_expr.ty());
358    llr_Expression::Condition {
359        condition: Box::new(lower_expression(condition, ctx)),
360        true_expr: Box::new(lower_expression(true_expr, ctx)),
361        false_expr: if false_ty == Type::Invalid || false_ty == Type::Void || true_ty == false_ty {
362            Box::new(lower_expression(false_expr, ctx))
363        } else {
364            // Because the type of the Condition is based on the false expression, we need to insert a cast
365            Box::new(llr_Expression::Cast {
366                from: Box::new(lower_expression(false_expr, ctx)),
367                to: Type::Void,
368            })
369        },
370    }
371}
372
373#[inline(never)]
374fn lower_mouse_cursor(
375    expression: &tree_Expression,
376    ctx: &mut ExpressionLoweringCtx<'_>,
377) -> llr_Expression {
378    let tree_Expression::MouseCursor(cursor) = expression else { unreachable!() };
379    llr_Expression::MouseCursor(match cursor {
380        MouseCursorInner::BuiltIn(expression) => {
381            crate::llr::MouseCursorInner::BuiltIn(Box::new(lower_expression(expression, ctx)))
382        }
383        MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
384            crate::llr::MouseCursorInner::CustomMouseCursor {
385                image: Box::new(lower_expression(image, ctx)),
386                hotspot_x: Box::new(lower_expression(hotspot_x, ctx)),
387                hotspot_y: Box::new(lower_expression(hotspot_y, ctx)),
388            }
389        }
390    })
391}
392
393#[inline(never)]
394fn lower_linear_gradient(
395    expression: &tree_Expression,
396    ctx: &mut ExpressionLoweringCtx<'_>,
397) -> llr_Expression {
398    let tree_Expression::LinearGradient { angle, stops } = expression else { unreachable!() };
399    llr_Expression::LinearGradient {
400        angle: Box::new(lower_expression(angle, ctx)),
401        stops: stops
402            .iter()
403            .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
404            .collect::<_>(),
405    }
406}
407
408#[inline(never)]
409fn lower_radial_gradient(
410    expression: &tree_Expression,
411    ctx: &mut ExpressionLoweringCtx<'_>,
412) -> llr_Expression {
413    let tree_Expression::RadialGradient { center, radius, stops } = expression else {
414        unreachable!()
415    };
416    llr_Expression::RadialGradient {
417        center: center.as_ref().map(|(cx, cy)| {
418            (Box::new(lower_expression(cx, ctx)), Box::new(lower_expression(cy, ctx)))
419        }),
420        radius: radius.as_ref().map(|r| Box::new(lower_expression(r, ctx))),
421        stops: stops
422            .iter()
423            .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
424            .collect::<_>(),
425    }
426}
427
428#[inline(never)]
429fn lower_conic_gradient(
430    expression: &tree_Expression,
431    ctx: &mut ExpressionLoweringCtx<'_>,
432) -> llr_Expression {
433    let tree_Expression::ConicGradient { from_angle, center, stops } = expression else {
434        unreachable!()
435    };
436    llr_Expression::ConicGradient {
437        from_angle: Box::new(lower_expression(from_angle, ctx)),
438        center: center.as_ref().map(|(cx, cy)| {
439            (Box::new(lower_expression(cx, ctx)), Box::new(lower_expression(cy, ctx)))
440        }),
441        stops: stops
442            .iter()
443            .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
444            .collect::<_>(),
445    }
446}
447
448#[inline(never)]
449fn lower_layout_cache_access(
450    expression: &tree_Expression,
451    ctx: &mut ExpressionLoweringCtx<'_>,
452) -> llr_Expression {
453    let tree_Expression::LayoutCacheAccess {
454        layout_cache_prop,
455        index,
456        repeater_index,
457        entries_per_item,
458    } = expression
459    else {
460        unreachable!()
461    };
462    llr_Expression::LayoutCacheAccess {
463        layout_cache_prop: ctx.map_property_reference(layout_cache_prop),
464        index: *index,
465        repeater_index: repeater_index.as_ref().map(|e| lower_expression(e, ctx).into()),
466        entries_per_item: *entries_per_item,
467    }
468}
469
470#[inline(never)]
471fn lower_grid_repeater_cache_access(
472    expression: &tree_Expression,
473    ctx: &mut ExpressionLoweringCtx<'_>,
474) -> llr_Expression {
475    let tree_Expression::GridRepeaterCacheAccess {
476        layout_cache_prop,
477        index,
478        repeater_index,
479        stride,
480        child_offset,
481        inner_repeater_index,
482        entries_per_item,
483    } = expression
484    else {
485        unreachable!()
486    };
487    llr_Expression::GridRepeaterCacheAccess {
488        layout_cache_prop: ctx.map_property_reference(layout_cache_prop),
489        index: *index,
490        repeater_index: lower_expression(repeater_index, ctx).into(),
491        stride: lower_expression(stride, ctx).into(),
492        child_offset: *child_offset,
493        inner_repeater_index: inner_repeater_index
494            .as_ref()
495            .map(|e| lower_expression(e, ctx).into()),
496        entries_per_item: *entries_per_item,
497    }
498}
499
500fn lower_assignment(
501    lhs: &tree_Expression,
502    rhs: &tree_Expression,
503    op: char,
504    ctx: &mut ExpressionLoweringCtx,
505) -> llr_Expression {
506    match lhs {
507        tree_Expression::PropertyReference(nr) => {
508            let rhs = lower_expression(rhs, ctx);
509            let property = ctx.map_property_reference(nr);
510            let value = if op == '=' {
511                rhs
512            } else {
513                llr_Expression::BinaryExpression {
514                    lhs: llr_Expression::PropertyReference(property.clone()).into(),
515                    rhs: rhs.into(),
516                    op,
517                }
518            }
519            .into();
520            llr_Expression::PropertyAssignment { property, value }
521        }
522        tree_Expression::StructFieldAccess { base, name } => {
523            let ty = base.ty();
524
525            let unique_name = ctx.state.unique_struct_assignment_name();
526            let s = tree_Expression::StoreLocalVariable {
527                name: unique_name.clone(),
528                value: base.clone(),
529            };
530            let lower_base =
531                tree_Expression::ReadLocalVariable { name: unique_name, ty: ty.clone() };
532            let mut values = BTreeMap::new();
533            let Type::Struct(ty) = ty else { unreachable!() };
534
535            for field in ty.fields.keys() {
536                let e = if field != name {
537                    tree_Expression::StructFieldAccess {
538                        base: lower_base.clone().into(),
539                        name: field.clone(),
540                    }
541                } else if op == '=' {
542                    rhs.clone()
543                } else {
544                    tree_Expression::BinaryExpression {
545                        lhs: tree_Expression::StructFieldAccess {
546                            base: lower_base.clone().into(),
547                            name: field.clone(),
548                        }
549                        .into(),
550                        rhs: Box::new(rhs.clone()),
551                        op,
552                        source_location: None,
553                    }
554                };
555                values.insert(field.clone(), e);
556            }
557
558            let new_value =
559                tree_Expression::CodeBlock(vec![s, tree_Expression::Struct { ty, values }]);
560            lower_assignment(base, &new_value, '=', ctx)
561        }
562        tree_Expression::RepeaterModelReference { element } => {
563            let rhs = lower_expression(rhs, ctx);
564            let prop =
565                repeater_special_property(element, ctx.component, PropertyIdx::REPEATER_DATA);
566
567            let level = match &prop {
568                MemberReference::Relative { parent_level, .. } => *parent_level,
569                _ => 0,
570            };
571
572            let value = Box::new(if op == '=' {
573                rhs
574            } else {
575                llr_Expression::BinaryExpression {
576                    lhs: llr_Expression::PropertyReference(prop).into(),
577                    rhs: rhs.into(),
578                    op,
579                }
580            });
581
582            llr_Expression::ModelDataAssignment { level, value }
583        }
584        tree_Expression::ArrayIndex { array, index } => {
585            let rhs = lower_expression(rhs, ctx);
586            let array = Box::new(lower_expression(array, ctx));
587            let index = Box::new(lower_expression(index, ctx));
588            let value = Box::new(if op == '=' {
589                rhs
590            } else {
591                // FIXME: this will compute the index and the array twice:
592                // Ideally we should store the index and the array in local variable
593                llr_Expression::BinaryExpression {
594                    lhs: llr_Expression::ArrayIndex { array: array.clone(), index: index.clone() }
595                        .into(),
596                    rhs: rhs.into(),
597                    op,
598                }
599            });
600
601            llr_Expression::ArrayIndexAssignment { array, index, value }
602        }
603        _ => panic!("not a rvalue"),
604    }
605}
606
607pub fn repeater_special_property(
608    element: &Weak<RefCell<Element>>,
609    component: &Rc<crate::object_tree::Component>,
610    property_index: PropertyIdx,
611) -> MemberReference {
612    let enclosing = element.upgrade().unwrap().borrow().enclosing_component.upgrade().unwrap();
613    let mut parent_level = 0;
614    let mut component = component.clone();
615    while !Rc::ptr_eq(&enclosing, &component) {
616        let parent_elem = component.parent_element().unwrap();
617        component = parent_elem.borrow().enclosing_component.upgrade().unwrap();
618        parent_level += 1;
619    }
620    MemberReference::Relative {
621        parent_level: parent_level - 1,
622        local_reference: LocalMemberReference {
623            sub_component_path: Vec::new(),
624            reference: property_index.into(),
625        },
626    }
627}
628
629/// Lowers to `RestartTimer(timer_reference)`.
630/// The argument is a `PropertyReference` with a [`LocalMemberIndex::Timer`]
631/// locating the timer in the component that declares it.
632fn lower_restart_timer(args: &[tree_Expression], ctx: &ExpressionLoweringCtx) -> llr_Expression {
633    if let [tree_Expression::ElementReference(e)] = args {
634        let timer_element = e.upgrade().unwrap();
635        let timer_comp = timer_element.borrow().enclosing_component.upgrade().unwrap();
636        let (parent_level, _) = ctx.find_component(&timer_comp);
637
638        let timer_list = timer_comp.timers.borrow();
639        let timer_index = timer_list
640            .iter()
641            .position(|t| Rc::ptr_eq(&t.element.upgrade().unwrap(), &timer_element))
642            .unwrap();
643
644        llr_Expression::BuiltinFunctionCall {
645            function: BuiltinFunction::RestartTimer,
646            source_location: None,
647            arguments: vec![llr_Expression::PropertyReference(MemberReference::Relative {
648                parent_level,
649                local_reference: LocalMemberReference {
650                    sub_component_path: Vec::new(),
651                    reference: crate::llr::TimerIdx::from(timer_index).into(),
652                },
653            })],
654        }
655    } else {
656        panic!("invalid arguments to RestartTimer");
657    }
658}
659
660/// Resolve the component that declares the popup referenced by `e`: a reference to its root,
661/// where the `popup_id` and scope live, and the popup's index in that component. The declaring
662/// component is the parent item's enclosing component, which is not always the parent item
663/// itself (it may be a nested sub-component instance), so this reference must be resolved
664/// separately from the parent item used for positioning.
665fn lower_popup_owner(
666    e: &ElementWeak,
667    ctx: &mut ExpressionLoweringCtx,
668) -> (Rc<Component>, llr_Expression, usize) {
669    let popup_window = e.upgrade().unwrap();
670    let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
671    let parent_elem = pop_comp.parent_element().unwrap();
672    let parent_component = parent_elem.borrow().enclosing_component.upgrade().unwrap();
673    let owner_ref = lower_expression(
674        &tree_Expression::ElementReference(Rc::downgrade(&parent_component.root_element)),
675        ctx,
676    );
677    let popup_index = parent_component
678        .popup_windows
679        .borrow()
680        .iter()
681        .position(|p| Rc::ptr_eq(&p.component, &pop_comp))
682        .unwrap();
683    (parent_component, owner_ref, popup_index)
684}
685
686fn lower_show_popup_window(
687    args: &[tree_Expression],
688    ctx: &mut ExpressionLoweringCtx,
689) -> llr_Expression {
690    if let [tree_Expression::ElementReference(e)] = args {
691        let (parent_component, owner_ref, popup_index) = lower_popup_owner(e, ctx);
692        let popup_list = parent_component.popup_windows.borrow();
693        let popup = &popup_list[popup_index];
694        let item_ref = lower_expression(
695            &tree_Expression::ElementReference(Rc::downgrade(&popup.parent_element)),
696            ctx,
697        );
698
699        let mut arguments = vec![
700            llr_Expression::NumberLiteral(popup_index as _),
701            llr_Expression::EnumerationValue(popup.close_policy.clone()),
702            owner_ref,
703            item_ref,
704        ];
705        // Map `is-open` here, at the show site, so it resolves in the same frame as `item_ref`. The
706        // popup struct is shared across all show sites and was lowered in a different frame, so this
707        // reference must not be cached on it (see the `is_open` handling in the generators).
708        if let Some(is_open) = &popup.is_open {
709            arguments.push(llr_Expression::PropertyReference(ctx.map_property_reference(is_open)));
710        }
711        llr_Expression::BuiltinFunctionCall {
712            function: BuiltinFunction::ShowPopupWindow,
713            arguments,
714            source_location: None,
715        }
716    } else {
717        panic!("invalid arguments to ShowPopupWindow");
718    }
719}
720
721fn lower_close_popup_window(
722    args: &[tree_Expression],
723    ctx: &mut ExpressionLoweringCtx,
724) -> llr_Expression {
725    if let [tree_Expression::ElementReference(e)] = args {
726        let (_, owner_ref, popup_index) = lower_popup_owner(e, ctx);
727        llr_Expression::BuiltinFunctionCall {
728            function: BuiltinFunction::ClosePopupWindow,
729            arguments: vec![llr_Expression::NumberLiteral(popup_index as _), owner_ref],
730            source_location: None,
731        }
732    } else {
733        panic!("invalid arguments to ClosePopupWindow");
734    }
735}
736
737pub fn lower_animation(a: &PropertyAnimation, ctx: &mut ExpressionLoweringCtx<'_>) -> Animation {
738    fn lower_animation_element(
739        a: &ElementRc,
740        ctx: &mut ExpressionLoweringCtx<'_>,
741    ) -> llr_Expression {
742        llr_Expression::Struct {
743            values: animation_fields()
744                .map(|(k, ty)| {
745                    let e = a.borrow().binding_cell_including_synthetic(&k).map_or_else(
746                        || {
747                            if k == "enabled" {
748                                llr_Expression::BoolLiteral(true)
749                            } else {
750                                llr_Expression::default_value_for_type(&ty).unwrap()
751                            }
752                        },
753                        |v| lower_expression(&v.borrow().expression, ctx),
754                    );
755                    (k, e)
756                })
757                .collect::<_>(),
758            ty: animation_ty(),
759        }
760    }
761
762    fn animation_fields() -> impl Iterator<Item = (SmolStr, Type)> {
763        IntoIterator::into_iter([
764            (SmolStr::new_static("duration"), Type::Int32),
765            (SmolStr::new_static("iteration-count"), Type::Float32),
766            (
767                SmolStr::new_static("direction"),
768                Type::Enumeration(BUILTIN.enums.AnimationDirection.clone()),
769            ),
770            (SmolStr::new_static("easing"), Type::Easing),
771            (SmolStr::new_static("delay"), Type::Int32),
772            (SmolStr::new_static("enabled"), Type::Bool),
773        ])
774    }
775
776    fn animation_ty() -> Arc<Struct> {
777        Arc::new(Struct::new(animation_fields().collect(), BuiltinStruct::PropertyAnimation))
778    }
779
780    match a {
781        PropertyAnimation::Static(a) => Animation::Static(lower_animation_element(a, ctx)),
782        PropertyAnimation::Transition { state_ref, animations } => {
783            let set_state = llr_Expression::StoreLocalVariable {
784                name: "state".into(),
785                value: Box::new(lower_expression(state_ref, ctx)),
786            };
787            let anim_struct_ty = animation_ty();
788            let animation_ty = Type::Struct(anim_struct_ty.clone());
789            let mut get_anim = llr_Expression::Struct {
790                ty: anim_struct_ty,
791                values: animation_fields()
792                    .map(|(k, ty)| {
793                        let e = if k == "enabled" {
794                            llr_Expression::BoolLiteral(true)
795                        } else {
796                            llr_Expression::default_value_for_type(&ty).unwrap()
797                        };
798                        (k, e)
799                    })
800                    .collect(),
801            };
802            for tr in animations.iter().rev() {
803                let condition = lower_expression(
804                    &tr.condition(tree_Expression::ReadLocalVariable {
805                        name: "state".into(),
806                        ty: state_ref.ty(),
807                    }),
808                    ctx,
809                );
810                get_anim = llr_Expression::Condition {
811                    condition: Box::new(condition),
812                    true_expr: Box::new(lower_animation_element(&tr.animation, ctx)),
813                    false_expr: Box::new(get_anim),
814                }
815            }
816            let result = llr_Expression::Struct {
817                // This is going to be a tuple
818                ty: Arc::new(Struct::new(
819                    IntoIterator::into_iter([
820                        (SmolStr::new_static("0"), animation_ty),
821                        // The type is an instant, which does not exist in our type system
822                        (SmolStr::new_static("1"), Type::Invalid),
823                    ])
824                    .collect(),
825                    StructName::None,
826                )),
827                values: IntoIterator::into_iter([
828                    (SmolStr::new_static("0"), get_anim),
829                    (
830                        SmolStr::new_static("1"),
831                        llr_Expression::StructFieldAccess {
832                            base: llr_Expression::ReadLocalVariable {
833                                name: "state".into(),
834                                ty: state_ref.ty(),
835                            }
836                            .into(),
837                            name: "change_time".into(),
838                        },
839                    ),
840                ])
841                .collect(),
842            };
843            Animation::Transition(llr_Expression::CodeBlock(vec![set_state, result]))
844        }
845    }
846}
847
848fn compile_path(
849    path: &crate::expression_tree::Path,
850    ctx: &mut ExpressionLoweringCtx,
851) -> llr_Expression {
852    fn llr_path_elements(elements: Vec<llr_Expression>) -> llr_Expression {
853        llr_Expression::Cast {
854            from: llr_Expression::Array {
855                element_ty: crate::typeregister::path_element_type(),
856                values: elements,
857                output: llr_ArrayOutput::Slice,
858            }
859            .into(),
860            to: Type::PathData,
861        }
862    }
863
864    match path {
865        crate::expression_tree::Path::Elements(elements) => {
866            let converted_elements = elements
867                .iter()
868                .map(|element| {
869                    let element_type = Arc::new(Struct::new(
870                        element
871                            .element_type
872                            .properties
873                            .iter()
874                            .map(|(k, v)| (k.clone(), v.ty.clone()))
875                            .collect(),
876                        StructName::Builtin(
877                            element
878                                .element_type
879                                .native_class
880                                .builtin_struct
881                                .clone()
882                                .expect("path elements should have a native_type"),
883                        ),
884                    ));
885
886                    llr_Expression::Struct {
887                        ty: element_type,
888                        values: element
889                            .element_type
890                            .properties
891                            .iter()
892                            .map(|(element_field_name, element_property)| {
893                                (
894                                    element_field_name.clone(),
895                                    element.bindings.get(element_field_name).map_or_else(
896                                        || {
897                                            llr_Expression::default_value_for_type(
898                                                &element_property.ty,
899                                            )
900                                            .unwrap()
901                                        },
902                                        |expr| lower_expression(&expr.borrow().expression, ctx),
903                                    ),
904                                )
905                            })
906                            .collect(),
907                    }
908                })
909                .collect();
910            llr_path_elements(converted_elements)
911        }
912        crate::expression_tree::Path::Events(events, points) => {
913            if events.is_empty() || points.is_empty() {
914                return llr_path_elements(Vec::new());
915            }
916
917            let events: Vec<_> = events.iter().map(|event| lower_expression(event, ctx)).collect();
918
919            let event_type = events.first().unwrap().ty(ctx);
920
921            let points: Vec<_> = points.iter().map(|point| lower_expression(point, ctx)).collect();
922
923            let point_type = points.first().unwrap().ty(ctx);
924
925            llr_Expression::Cast {
926                from: llr_Expression::Struct {
927                    ty: Arc::new(Struct::new(
928                        IntoIterator::into_iter([
929                            (SmolStr::new_static("events"), Type::Array(event_type.clone().into())),
930                            (SmolStr::new_static("points"), Type::Array(point_type.clone().into())),
931                        ])
932                        .collect(),
933                        StructName::None,
934                    )),
935                    values: IntoIterator::into_iter([
936                        (
937                            SmolStr::new_static("events"),
938                            llr_Expression::Array {
939                                element_ty: event_type,
940                                values: events,
941                                output: llr_ArrayOutput::Slice,
942                            },
943                        ),
944                        (
945                            SmolStr::new_static("points"),
946                            llr_Expression::Array {
947                                element_ty: point_type,
948                                values: points,
949                                output: llr_ArrayOutput::Slice,
950                            },
951                        ),
952                    ])
953                    .collect(),
954                }
955                .into(),
956                to: Type::PathData,
957            }
958        }
959        crate::expression_tree::Path::Commands(commands) => llr_Expression::Cast {
960            from: lower_expression(commands, ctx).into(),
961            to: Type::PathData,
962        },
963    }
964}
965
966pub fn make_struct(
967    name: impl Into<StructName>,
968    it: impl IntoIterator<Item = (&'static str, Type, llr_Expression)>,
969) -> llr_Expression {
970    let mut fields = BTreeMap::<SmolStr, Type>::new();
971    let mut values = BTreeMap::<SmolStr, llr_Expression>::new();
972    for (name, ty, expr) in it {
973        fields.insert(SmolStr::new(name), ty);
974        values.insert(SmolStr::new(name), expr);
975    }
976
977    llr_Expression::Struct { ty: Arc::new(Struct::new(fields, name)), values }
978}