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