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::{BuiltinPrivateStruct, 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 { stops } => llr_Expression::RadialGradient {
241            stops: stops
242                .iter()
243                .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
244                .collect::<_>(),
245        },
246        tree_Expression::ConicGradient { from_angle, stops } => llr_Expression::ConicGradient {
247            from_angle: Box::new(lower_expression(from_angle, ctx)),
248            stops: stops
249                .iter()
250                .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
251                .collect::<_>(),
252        },
253        tree_Expression::EnumerationValue(e) => llr_Expression::EnumerationValue(e.clone()),
254        tree_Expression::Keys(ks) => llr_Expression::KeysLiteral(ks.clone()),
255        tree_Expression::ReturnStatement(..) => {
256            panic!("The remove return pass should have removed all return")
257        }
258        tree_Expression::LayoutCacheAccess {
259            layout_cache_prop,
260            index,
261            repeater_index,
262            entries_per_item,
263        } => llr_Expression::LayoutCacheAccess {
264            layout_cache_prop: ctx.map_property_reference(layout_cache_prop),
265            index: *index,
266            repeater_index: repeater_index.as_ref().map(|e| lower_expression(e, ctx).into()),
267            entries_per_item: *entries_per_item,
268        },
269        tree_Expression::GridRepeaterCacheAccess {
270            layout_cache_prop,
271            index,
272            repeater_index,
273            stride,
274            child_offset,
275            inner_repeater_index,
276            entries_per_item,
277        } => llr_Expression::GridRepeaterCacheAccess {
278            layout_cache_prop: ctx.map_property_reference(layout_cache_prop),
279            index: *index,
280            repeater_index: lower_expression(repeater_index, ctx).into(),
281            stride: lower_expression(stride, ctx).into(),
282            child_offset: *child_offset,
283            inner_repeater_index: inner_repeater_index
284                .as_ref()
285                .map(|e| lower_expression(e, ctx).into()),
286            entries_per_item: *entries_per_item,
287        },
288        tree_Expression::OrganizeGridLayout(l) => organize_grid_layout(l, ctx),
289        tree_Expression::ComputeBoxLayoutInfo(l, o) => compute_box_layout_info(l, *o, ctx),
290        tree_Expression::ComputeGridLayoutInfo {
291            layout_organized_data_prop,
292            layout,
293            orientation,
294        } => compute_grid_layout_info(layout_organized_data_prop, layout, *orientation, ctx),
295        tree_Expression::SolveBoxLayout(l, o) => solve_box_layout(l, *o, ctx),
296        tree_Expression::SolveGridLayout { layout_organized_data_prop, layout, orientation } => {
297            solve_grid_layout(layout_organized_data_prop, layout, *orientation, ctx)
298        }
299        tree_Expression::SolveFlexboxLayout(l) => solve_flexbox_layout(l, ctx),
300        tree_Expression::ComputeFlexboxLayoutInfo(l, o) => compute_flexbox_layout_info(l, *o, ctx),
301        tree_Expression::MinMax { ty, op, lhs, rhs } => llr_Expression::MinMax {
302            ty: ty.clone(),
303            op: *op,
304            lhs: Box::new(lower_expression(lhs, ctx)),
305            rhs: Box::new(lower_expression(rhs, ctx)),
306        },
307        tree_Expression::EmptyComponentFactory => llr_Expression::EmptyComponentFactory,
308        tree_Expression::DebugHook { expression, .. } => lower_expression(expression, ctx),
309    }
310}
311
312fn lower_assignment(
313    lhs: &tree_Expression,
314    rhs: &tree_Expression,
315    op: char,
316    ctx: &mut ExpressionLoweringCtx,
317) -> llr_Expression {
318    match lhs {
319        tree_Expression::PropertyReference(nr) => {
320            let rhs = lower_expression(rhs, ctx);
321            let property = ctx.map_property_reference(nr);
322            let value = if op == '=' {
323                rhs
324            } else {
325                llr_Expression::BinaryExpression {
326                    lhs: llr_Expression::PropertyReference(property.clone()).into(),
327                    rhs: rhs.into(),
328                    op,
329                }
330            }
331            .into();
332            llr_Expression::PropertyAssignment { property, value }
333        }
334        tree_Expression::StructFieldAccess { base, name } => {
335            let ty = base.ty();
336
337            static COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
338            let unique_name = format_smolstr!(
339                "struct_assignment{}",
340                COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
341            );
342            let s = tree_Expression::StoreLocalVariable {
343                name: unique_name.clone(),
344                value: base.clone(),
345            };
346            let lower_base =
347                tree_Expression::ReadLocalVariable { name: unique_name, ty: ty.clone() };
348            let mut values = HashMap::new();
349            let Type::Struct(ty) = ty else { unreachable!() };
350
351            for field in ty.fields.keys() {
352                let e = if field != name {
353                    tree_Expression::StructFieldAccess {
354                        base: lower_base.clone().into(),
355                        name: field.clone(),
356                    }
357                } else if op == '=' {
358                    rhs.clone()
359                } else {
360                    tree_Expression::BinaryExpression {
361                        lhs: tree_Expression::StructFieldAccess {
362                            base: lower_base.clone().into(),
363                            name: field.clone(),
364                        }
365                        .into(),
366                        rhs: Box::new(rhs.clone()),
367                        op,
368                    }
369                };
370                values.insert(field.clone(), e);
371            }
372
373            let new_value =
374                tree_Expression::CodeBlock(vec![s, tree_Expression::Struct { ty, values }]);
375            lower_assignment(base, &new_value, '=', ctx)
376        }
377        tree_Expression::RepeaterModelReference { element } => {
378            let rhs = lower_expression(rhs, ctx);
379            let prop =
380                repeater_special_property(element, ctx.component, PropertyIdx::REPEATER_DATA);
381
382            let level = match &prop {
383                MemberReference::Relative { parent_level, .. } => *parent_level,
384                _ => 0,
385            };
386
387            let value = Box::new(if op == '=' {
388                rhs
389            } else {
390                llr_Expression::BinaryExpression {
391                    lhs: llr_Expression::PropertyReference(prop).into(),
392                    rhs: rhs.into(),
393                    op,
394                }
395            });
396
397            llr_Expression::ModelDataAssignment { level, value }
398        }
399        tree_Expression::ArrayIndex { array, index } => {
400            let rhs = lower_expression(rhs, ctx);
401            let array = Box::new(lower_expression(array, ctx));
402            let index = Box::new(lower_expression(index, ctx));
403            let value = Box::new(if op == '=' {
404                rhs
405            } else {
406                // FIXME: this will compute the index and the array twice:
407                // Ideally we should store the index and the array in local variable
408                llr_Expression::BinaryExpression {
409                    lhs: llr_Expression::ArrayIndex { array: array.clone(), index: index.clone() }
410                        .into(),
411                    rhs: rhs.into(),
412                    op,
413                }
414            });
415
416            llr_Expression::ArrayIndexAssignment { array, index, value }
417        }
418        _ => panic!("not a rvalue"),
419    }
420}
421
422pub fn repeater_special_property(
423    element: &Weak<RefCell<Element>>,
424    component: &Rc<crate::object_tree::Component>,
425    property_index: PropertyIdx,
426) -> MemberReference {
427    let enclosing = element.upgrade().unwrap().borrow().enclosing_component.upgrade().unwrap();
428    let mut parent_level = 0;
429    let mut component = component.clone();
430    while !Rc::ptr_eq(&enclosing, &component) {
431        let parent_elem = component.parent_element().unwrap();
432        component = parent_elem.borrow().enclosing_component.upgrade().unwrap();
433        parent_level += 1;
434    }
435    MemberReference::Relative {
436        parent_level: parent_level - 1,
437        local_reference: LocalMemberReference {
438            sub_component_path: Vec::new(),
439            reference: property_index.into(),
440        },
441    }
442}
443
444fn lower_restart_timer(args: &[tree_Expression]) -> llr_Expression {
445    if let [tree_Expression::ElementReference(e)] = args {
446        let timer_element = e.upgrade().unwrap();
447        let timer_comp = timer_element.borrow().enclosing_component.upgrade().unwrap();
448
449        let timer_list = timer_comp.timers.borrow();
450        let timer_index = timer_list
451            .iter()
452            .position(|t| Rc::ptr_eq(&t.element.upgrade().unwrap(), &timer_element))
453            .unwrap();
454
455        llr_Expression::BuiltinFunctionCall {
456            function: BuiltinFunction::RestartTimer,
457            arguments: vec![llr_Expression::NumberLiteral(timer_index as _)],
458        }
459    } else {
460        panic!("invalid arguments to RestartTimer");
461    }
462}
463
464fn lower_show_popup_window(
465    args: &[tree_Expression],
466    ctx: &mut ExpressionLoweringCtx,
467) -> llr_Expression {
468    if let [tree_Expression::ElementReference(e)] = args {
469        let popup_window = e.upgrade().unwrap();
470        let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
471        let parent_elem = pop_comp.parent_element().unwrap();
472        let parent_component = parent_elem.borrow().enclosing_component.upgrade().unwrap();
473        let popup_list = parent_component.popup_windows.borrow();
474        let (popup_index, popup) = popup_list
475            .iter()
476            .enumerate()
477            .find(|(_, p)| Rc::ptr_eq(&p.component, &pop_comp))
478            .unwrap();
479        let item_ref = lower_expression(
480            &tree_Expression::ElementReference(Rc::downgrade(&popup.parent_element)),
481            ctx,
482        );
483
484        llr_Expression::BuiltinFunctionCall {
485            function: BuiltinFunction::ShowPopupWindow,
486            arguments: vec![
487                llr_Expression::NumberLiteral(popup_index as _),
488                llr_Expression::EnumerationValue(popup.close_policy.clone()),
489                item_ref,
490            ],
491        }
492    } else {
493        panic!("invalid arguments to ShowPopupWindow");
494    }
495}
496
497fn lower_close_popup_window(
498    args: &[tree_Expression],
499    ctx: &mut ExpressionLoweringCtx,
500) -> llr_Expression {
501    if let [tree_Expression::ElementReference(e)] = args {
502        let popup_window = e.upgrade().unwrap();
503        let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
504        let parent_elem = pop_comp.parent_element().unwrap();
505        let parent_component = parent_elem.borrow().enclosing_component.upgrade().unwrap();
506        let popup_list = parent_component.popup_windows.borrow();
507        let (popup_index, popup) = popup_list
508            .iter()
509            .enumerate()
510            .find(|(_, p)| Rc::ptr_eq(&p.component, &pop_comp))
511            .unwrap();
512        let item_ref = lower_expression(
513            &tree_Expression::ElementReference(Rc::downgrade(&popup.parent_element)),
514            ctx,
515        );
516
517        llr_Expression::BuiltinFunctionCall {
518            function: BuiltinFunction::ClosePopupWindow,
519            arguments: vec![llr_Expression::NumberLiteral(popup_index as _), item_ref],
520        }
521    } else {
522        panic!("invalid arguments to ShowPopupWindow");
523    }
524}
525
526pub fn lower_animation(a: &PropertyAnimation, ctx: &mut ExpressionLoweringCtx<'_>) -> Animation {
527    fn lower_animation_element(
528        a: &ElementRc,
529        ctx: &mut ExpressionLoweringCtx<'_>,
530    ) -> llr_Expression {
531        llr_Expression::Struct {
532            values: animation_fields()
533                .map(|(k, ty)| {
534                    let e = a.borrow().bindings.get(&k).map_or_else(
535                        || llr_Expression::default_value_for_type(&ty).unwrap(),
536                        |v| lower_expression(&v.borrow().expression, ctx),
537                    );
538                    (k, e)
539                })
540                .collect::<_>(),
541            ty: animation_ty(),
542        }
543    }
544
545    fn animation_fields() -> impl Iterator<Item = (SmolStr, Type)> {
546        IntoIterator::into_iter([
547            (SmolStr::new_static("duration"), Type::Int32),
548            (SmolStr::new_static("iteration-count"), Type::Float32),
549            (
550                SmolStr::new_static("direction"),
551                Type::Enumeration(BUILTIN.with(|e| e.enums.AnimationDirection.clone())),
552            ),
553            (SmolStr::new_static("easing"), Type::Easing),
554            (SmolStr::new_static("delay"), Type::Int32),
555        ])
556    }
557
558    fn animation_ty() -> Rc<Struct> {
559        Rc::new(Struct {
560            fields: animation_fields().collect(),
561            name: BuiltinPrivateStruct::PropertyAnimation.into(),
562        })
563    }
564
565    match a {
566        PropertyAnimation::Static(a) => Animation::Static(lower_animation_element(a, ctx)),
567        PropertyAnimation::Transition { state_ref, animations } => {
568            let set_state = llr_Expression::StoreLocalVariable {
569                name: "state".into(),
570                value: Box::new(lower_expression(state_ref, ctx)),
571            };
572            let animation_ty = Type::Struct(animation_ty());
573            let mut get_anim = llr_Expression::default_value_for_type(&animation_ty).unwrap();
574            for tr in animations.iter().rev() {
575                let condition = lower_expression(
576                    &tr.condition(tree_Expression::ReadLocalVariable {
577                        name: "state".into(),
578                        ty: state_ref.ty(),
579                    }),
580                    ctx,
581                );
582                get_anim = llr_Expression::Condition {
583                    condition: Box::new(condition),
584                    true_expr: Box::new(lower_animation_element(&tr.animation, ctx)),
585                    false_expr: Box::new(get_anim),
586                }
587            }
588            let result = llr_Expression::Struct {
589                // This is going to be a tuple
590                ty: Rc::new(Struct {
591                    fields: IntoIterator::into_iter([
592                        (SmolStr::new_static("0"), animation_ty),
593                        // The type is an instant, which does not exist in our type system
594                        (SmolStr::new_static("1"), Type::Invalid),
595                    ])
596                    .collect(),
597                    name: StructName::None,
598                }),
599                values: IntoIterator::into_iter([
600                    (SmolStr::new_static("0"), get_anim),
601                    (
602                        SmolStr::new_static("1"),
603                        llr_Expression::StructFieldAccess {
604                            base: llr_Expression::ReadLocalVariable {
605                                name: "state".into(),
606                                ty: state_ref.ty(),
607                            }
608                            .into(),
609                            name: "change_time".into(),
610                        },
611                    ),
612                ])
613                .collect(),
614            };
615            Animation::Transition(llr_Expression::CodeBlock(vec![set_state, result]))
616        }
617    }
618}
619
620fn compile_path(
621    path: &crate::expression_tree::Path,
622    ctx: &mut ExpressionLoweringCtx,
623) -> llr_Expression {
624    fn llr_path_elements(elements: Vec<llr_Expression>) -> llr_Expression {
625        llr_Expression::Cast {
626            from: llr_Expression::Array {
627                element_ty: crate::typeregister::path_element_type(),
628                values: elements,
629                output: llr_ArrayOutput::Slice,
630            }
631            .into(),
632            to: Type::PathData,
633        }
634    }
635
636    match path {
637        crate::expression_tree::Path::Elements(elements) => {
638            let converted_elements = elements
639                .iter()
640                .map(|element| {
641                    let element_type = Rc::new(Struct {
642                        fields: element
643                            .element_type
644                            .properties
645                            .iter()
646                            .map(|(k, v)| (k.clone(), v.ty.clone()))
647                            .collect(),
648                        name: StructName::BuiltinPrivate(
649                            element
650                                .element_type
651                                .native_class
652                                .builtin_struct
653                                .clone()
654                                .expect("path elements should have a native_type"),
655                        ),
656                    });
657
658                    llr_Expression::Struct {
659                        ty: element_type,
660                        values: element
661                            .element_type
662                            .properties
663                            .iter()
664                            .map(|(element_field_name, element_property)| {
665                                (
666                                    element_field_name.clone(),
667                                    element.bindings.get(element_field_name).map_or_else(
668                                        || {
669                                            llr_Expression::default_value_for_type(
670                                                &element_property.ty,
671                                            )
672                                            .unwrap()
673                                        },
674                                        |expr| lower_expression(&expr.borrow().expression, ctx),
675                                    ),
676                                )
677                            })
678                            .collect(),
679                    }
680                })
681                .collect();
682            llr_path_elements(converted_elements)
683        }
684        crate::expression_tree::Path::Events(events, points) => {
685            if events.is_empty() || points.is_empty() {
686                return llr_path_elements(Vec::new());
687            }
688
689            let events: Vec<_> = events.iter().map(|event| lower_expression(event, ctx)).collect();
690
691            let event_type = events.first().unwrap().ty(ctx);
692
693            let points: Vec<_> = points.iter().map(|point| lower_expression(point, ctx)).collect();
694
695            let point_type = points.first().unwrap().ty(ctx);
696
697            llr_Expression::Cast {
698                from: llr_Expression::Struct {
699                    ty: Rc::new(Struct {
700                        fields: IntoIterator::into_iter([
701                            (SmolStr::new_static("events"), Type::Array(event_type.clone().into())),
702                            (SmolStr::new_static("points"), Type::Array(point_type.clone().into())),
703                        ])
704                        .collect(),
705                        name: StructName::None,
706                    }),
707                    values: IntoIterator::into_iter([
708                        (
709                            SmolStr::new_static("events"),
710                            llr_Expression::Array {
711                                element_ty: event_type,
712                                values: events,
713                                output: llr_ArrayOutput::Slice,
714                            },
715                        ),
716                        (
717                            SmolStr::new_static("points"),
718                            llr_Expression::Array {
719                                element_ty: point_type,
720                                values: points,
721                                output: llr_ArrayOutput::Slice,
722                            },
723                        ),
724                    ])
725                    .collect(),
726                }
727                .into(),
728                to: Type::PathData,
729            }
730        }
731        crate::expression_tree::Path::Commands(commands) => llr_Expression::Cast {
732            from: lower_expression(commands, ctx).into(),
733            to: Type::PathData,
734        },
735    }
736}
737
738pub fn make_struct(
739    name: impl Into<StructName>,
740    it: impl IntoIterator<Item = (&'static str, Type, llr_Expression)>,
741) -> llr_Expression {
742    let mut fields = BTreeMap::<SmolStr, Type>::new();
743    let mut values = BTreeMap::<SmolStr, llr_Expression>::new();
744    for (name, ty, expr) in it {
745        fields.insert(SmolStr::new(name), ty);
746        values.insert(SmolStr::new(name), expr);
747    }
748
749    llr_Expression::Struct { ty: Rc::new(Struct { fields, name: name.into() }), values }
750}