Skip to main content

i_slint_compiler/
expression_tree.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
5use crate::langtype::{
6    BuiltinElement, BuiltinPublicStruct, EnumerationValue, Function, Struct, Type,
7};
8use crate::layout::Orientation;
9use crate::lookup::LookupCtx;
10use crate::object_tree::*;
11use crate::parser::{NodeOrToken, SyntaxNode};
12use crate::typeregister;
13use core::cell::RefCell;
14use smol_str::{SmolStr, format_smolstr};
15use std::cell::Cell;
16use std::collections::HashMap;
17use std::rc::{Rc, Weak};
18
19// FIXME remove the pub
20pub use crate::namedreference::NamedReference;
21pub use crate::passes::resolving;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24/// A function built into the run-time
25pub enum BuiltinFunction {
26    GetWindowScaleFactor,
27    GetWindowDefaultFontSize,
28    AnimationTick,
29    Debug,
30    Mod,
31    Round,
32    Ceil,
33    Floor,
34    Abs,
35    Sqrt,
36    Cos,
37    Sin,
38    Tan,
39    ACos,
40    ASin,
41    ATan,
42    ATan2,
43    Log,
44    Ln,
45    Pow,
46    Exp,
47    ToFixed,
48    ToPrecision,
49    SetFocusItem,
50    ClearFocusItem,
51    ShowPopupWindow,
52    ClosePopupWindow,
53    /// Show a context popup menu.
54    /// Arguments are `(parent, menu_tree, position)`
55    ///
56    /// The first argument (parent) is a reference to the `ContextMenu` native item
57    /// The second argument (menu_tree) is a ElementReference to the root of the tree,
58    /// and in the LLR, a NumberLiteral to an index in  [`crate::llr::SubComponent::menu_item_trees`]
59    ShowPopupMenu,
60    /// Show a context popup menu from a list of entries.
61    /// Arguments are `(parent, entries, position)`
62    /// The entries argument is an array of MenuEntry
63    ShowPopupMenuInternal,
64    SetSelectionOffsets,
65    ItemFontMetrics,
66    /// the "42".to_float()
67    StringToFloat,
68    /// the "42".is_float()
69    StringIsFloat,
70    /// the "42".is_empty
71    StringIsEmpty,
72    /// the "42".length
73    StringCharacterCount,
74    StringToLowercase,
75    StringToUppercase,
76    ColorRgbaStruct,
77    ColorHsvaStruct,
78    ColorOklchStruct,
79    ColorBrighter,
80    ColorDarker,
81    ColorTransparentize,
82    ColorMix,
83    ColorWithAlpha,
84    ImageSize,
85    ArrayLength,
86    Rgb,
87    Hsv,
88    Oklch,
89    ColorScheme,
90    SupportsNativeMenuBar,
91    /// Setup the menu bar
92    ///
93    /// arguments are: `(ref entries, ref sub-menu, ref activated, item_tree_root, no_native_menu_bar, <condition>)`
94    /// `item_tree_root` is a reference to the MenuItem tree root (just like in the [`Self::ShowPopupMenu`] call).
95    /// `no_native_menu_bar` is a boolean literal that is true when we shouldn't try to setup the native menu bar.
96    /// `condition` is an optional expression that is the expression to `if condition : MenuBar { ... }` for optional menu
97    SetupMenuBar,
98    Use24HourFormat,
99    MonthDayCount,
100    MonthOffset,
101    FormatDate,
102    DateNow,
103    ValidDate,
104    ParseDate,
105    TextInputFocused,
106    SetTextInputFocused,
107    ImplicitLayoutInfo(Orientation),
108    ItemAbsolutePosition,
109    RegisterCustomFontByPath,
110    RegisterCustomFontByMemory,
111    RegisterBitmapFont,
112    Translate,
113    UpdateTimers,
114    DetectOperatingSystem,
115    StartTimer,
116    StopTimer,
117    RestartTimer,
118    ParseMarkdown,
119    EscapeMarkdown,
120}
121
122#[derive(Debug, Clone)]
123/// A builtin function which is handled by the compiler pass
124///
125/// Builtin function expect their arguments in one and a specific type, so that's easier
126/// for the generator. Macro however can do some transformation on their argument.
127///
128pub enum BuiltinMacroFunction {
129    /// Transform `min(a, b, c, ..., z)` into a series of conditional expression and comparisons
130    Min,
131    /// Transform `max(a, b, c, ..., z)` into  a series of conditional expression and comparisons
132    Max,
133    /// Transforms `clamp(v, min, max)` into a series of min/max calls
134    Clamp,
135    /// Add the right conversion operations so that the return type is the same as the argument type
136    Mod,
137    /// Add the right conversion operations so that the return type is the same as the argument type
138    Abs,
139    /// Equivalent to `x < 0 ? -1 : 1`
140    Sign,
141    CubicBezier,
142    /// The argument can be r,g,b,a or r,g,b and they can be percentages or integer.
143    /// transform the argument so it is always rgb(r, g, b, a) with r, g, b between 0 and 255.
144    Rgb,
145    Hsv,
146    Oklch,
147    /// transform `debug(a, b, c)` into debug `a + " " + b + " " + c`
148    Debug,
149}
150
151macro_rules! declare_builtin_function_types {
152    ($( $Name:ident $(($Pattern:tt))? : ($( $Arg:expr ),*) -> $ReturnType:expr $(,)? )*) => {
153        #[allow(non_snake_case)]
154        pub struct BuiltinFunctionTypes {
155            $(pub $Name : Rc<Function>),*
156        }
157        impl BuiltinFunctionTypes {
158            pub fn new() -> Self {
159                Self {
160                    $($Name : Rc::new(Function{
161                        args: vec![$($Arg),*],
162                        return_type: $ReturnType,
163                        arg_names: Vec::new(),
164                    })),*
165                }
166            }
167
168            pub fn ty(&self, function: &BuiltinFunction) -> Rc<Function> {
169                match function {
170                    $(BuiltinFunction::$Name $(($Pattern))? => self.$Name.clone()),*
171                }
172            }
173        }
174    };
175}
176
177declare_builtin_function_types!(
178    GetWindowScaleFactor: () -> Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)]),
179    GetWindowDefaultFontSize: () -> Type::LogicalLength,
180    AnimationTick: () -> Type::Duration,
181    Debug: (Type::String) -> Type::Void,
182    Mod: (Type::Int32, Type::Int32) -> Type::Int32,
183    Round: (Type::Float32) -> Type::Int32,
184    Ceil: (Type::Float32) -> Type::Int32,
185    Floor: (Type::Float32) -> Type::Int32,
186    Sqrt: (Type::Float32) -> Type::Float32,
187    Abs: (Type::Float32) -> Type::Float32,
188    Cos: (Type::Angle) -> Type::Float32,
189    Sin: (Type::Angle) -> Type::Float32,
190    Tan: (Type::Angle) -> Type::Float32,
191    ACos: (Type::Float32) -> Type::Angle,
192    ASin: (Type::Float32) -> Type::Angle,
193    ATan: (Type::Float32) -> Type::Angle,
194    ATan2: (Type::Float32, Type::Float32) -> Type::Angle,
195    Log: (Type::Float32, Type::Float32) -> Type::Float32,
196    Ln: (Type::Float32) -> Type::Float32,
197    Pow: (Type::Float32, Type::Float32) -> Type::Float32,
198    Exp: (Type::Float32) -> Type::Float32,
199    ToFixed: (Type::Float32, Type::Int32) -> Type::String,
200    ToPrecision: (Type::Float32, Type::Int32) -> Type::String,
201    SetFocusItem: (Type::ElementReference) -> Type::Void,
202    ClearFocusItem: (Type::ElementReference) -> Type::Void,
203    ShowPopupWindow: (Type::ElementReference) -> Type::Void,
204    ClosePopupWindow: (Type::ElementReference) -> Type::Void,
205    ShowPopupMenu: (Type::ElementReference, Type::ElementReference, typeregister::logical_point_type().into()) -> Type::Void,
206    ShowPopupMenuInternal: (Type::ElementReference, Type::Model, typeregister::logical_point_type().into()) -> Type::Void,
207    SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void,
208    ItemFontMetrics: (Type::ElementReference) -> typeregister::font_metrics_type(),
209    StringToFloat: (Type::String) -> Type::Float32,
210    StringIsFloat: (Type::String) -> Type::Bool,
211    StringIsEmpty: (Type::String) -> Type::Bool,
212    StringCharacterCount: (Type::String) -> Type::Int32,
213    StringToLowercase: (Type::String) -> Type::String,
214    StringToUppercase: (Type::String) -> Type::String,
215    ImplicitLayoutInfo(..): (Type::ElementReference) -> typeregister::layout_info_type().into(),
216    ColorRgbaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct {
217        fields: IntoIterator::into_iter([
218            (SmolStr::new_static("red"), Type::Int32),
219            (SmolStr::new_static("green"), Type::Int32),
220            (SmolStr::new_static("blue"), Type::Int32),
221            (SmolStr::new_static("alpha"), Type::Int32),
222        ])
223        .collect(),
224        name: BuiltinPublicStruct::Color.into(),
225    })),
226    ColorHsvaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct {
227        fields: IntoIterator::into_iter([
228            (SmolStr::new_static("hue"), Type::Float32),
229            (SmolStr::new_static("saturation"), Type::Float32),
230            (SmolStr::new_static("value"), Type::Float32),
231            (SmolStr::new_static("alpha"), Type::Float32),
232        ])
233        .collect(),
234        name: BuiltinPublicStruct::Color.into(),
235    })),
236    ColorOklchStruct: (Type::Color) -> Type::Struct(Rc::new(Struct {
237        fields: IntoIterator::into_iter([
238            (SmolStr::new_static("lightness"), Type::Float32),
239            (SmolStr::new_static("chroma"), Type::Float32),
240            (SmolStr::new_static("hue"), Type::Float32),
241            (SmolStr::new_static("alpha"), Type::Float32),
242        ])
243        .collect(),
244        name: BuiltinPublicStruct::Color.into(),
245    })),
246    ColorBrighter: (Type::Brush, Type::Float32) -> Type::Brush,
247    ColorDarker: (Type::Brush, Type::Float32) -> Type::Brush,
248    ColorTransparentize: (Type::Brush, Type::Float32) -> Type::Brush,
249    ColorWithAlpha: (Type::Brush, Type::Float32) -> Type::Brush,
250    ColorMix: (Type::Color, Type::Color, Type::Float32) -> Type::Color,
251    ImageSize: (Type::Image) -> Type::Struct(Rc::new(Struct {
252        fields: IntoIterator::into_iter([
253            (SmolStr::new_static("width"), Type::Int32),
254            (SmolStr::new_static("height"), Type::Int32),
255        ])
256        .collect(),
257        name: crate::langtype::BuiltinPrivateStruct::Size.into(),
258    })),
259    ArrayLength: (Type::Model) -> Type::Int32,
260    Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color,
261    Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
262    Oklch: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
263    ColorScheme: () -> Type::Enumeration(
264        typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone()),
265    ),
266    SupportsNativeMenuBar: () -> Type::Bool,
267    // entries, sub-menu, activate. But the types here are not accurate.
268    SetupMenuBar: (Type::Model, typeregister::noarg_callback_type(), typeregister::noarg_callback_type()) -> Type::Void,
269    MonthDayCount: (Type::Int32, Type::Int32) -> Type::Int32,
270    MonthOffset: (Type::Int32, Type::Int32) -> Type::Int32,
271    FormatDate: (Type::String, Type::Int32, Type::Int32, Type::Int32) -> Type::String,
272    TextInputFocused: () -> Type::Bool,
273    DateNow: () -> Type::Array(Rc::new(Type::Int32)),
274    ValidDate: (Type::String, Type::String) -> Type::Bool,
275    ParseDate: (Type::String, Type::String) -> Type::Array(Rc::new(Type::Int32)),
276    SetTextInputFocused: (Type::Bool) -> Type::Void,
277    ItemAbsolutePosition: (Type::ElementReference) -> typeregister::logical_point_type().into(),
278    RegisterCustomFontByPath: (Type::String) -> Type::Void,
279    RegisterCustomFontByMemory: (Type::Int32) -> Type::Void,
280    RegisterBitmapFont: (Type::Int32) -> Type::Void,
281    // original, context, domain, args
282    Translate: (Type::String, Type::String, Type::String, Type::Array(Type::String.into())) -> Type::String,
283    Use24HourFormat: () -> Type::Bool,
284    UpdateTimers: () -> Type::Void,
285    DetectOperatingSystem: () -> Type::Enumeration(
286        typeregister::BUILTIN.with(|e| e.enums.OperatingSystemType.clone()),
287    ),
288    StartTimer: (Type::ElementReference) -> Type::Void,
289    StopTimer: (Type::ElementReference) -> Type::Void,
290    RestartTimer: (Type::ElementReference) -> Type::Void,
291    EscapeMarkdown: (Type::String) -> Type::String,
292    ParseMarkdown: (Type::String) -> Type::StyledText
293);
294
295impl BuiltinFunction {
296    pub fn ty(&self) -> Rc<Function> {
297        thread_local! {
298            static TYPES: BuiltinFunctionTypes = BuiltinFunctionTypes::new();
299        }
300        TYPES.with(|types| types.ty(self))
301    }
302
303    /// It is const if the return value only depends on its argument and has no side effect
304    fn is_const(&self, global_analysis: Option<&crate::passes::GlobalAnalysis>) -> bool {
305        match self {
306            BuiltinFunction::GetWindowScaleFactor => {
307                global_analysis.is_some_and(|x| x.const_scale_factor.is_some())
308            }
309            BuiltinFunction::GetWindowDefaultFontSize => {
310                global_analysis.is_some_and(|x| x.default_font_size.is_const())
311            }
312            BuiltinFunction::AnimationTick => false,
313            BuiltinFunction::ColorScheme => false,
314            BuiltinFunction::SupportsNativeMenuBar => false,
315            BuiltinFunction::SetupMenuBar => false,
316            BuiltinFunction::MonthDayCount => false,
317            BuiltinFunction::MonthOffset => false,
318            BuiltinFunction::FormatDate => false,
319            BuiltinFunction::DateNow => false,
320            BuiltinFunction::ValidDate => false,
321            BuiltinFunction::ParseDate => false,
322            // Even if it is not pure, we optimize it away anyway
323            BuiltinFunction::Debug => true,
324            BuiltinFunction::Mod
325            | BuiltinFunction::Round
326            | BuiltinFunction::Ceil
327            | BuiltinFunction::Floor
328            | BuiltinFunction::Abs
329            | BuiltinFunction::Sqrt
330            | BuiltinFunction::Cos
331            | BuiltinFunction::Sin
332            | BuiltinFunction::Tan
333            | BuiltinFunction::ACos
334            | BuiltinFunction::ASin
335            | BuiltinFunction::Log
336            | BuiltinFunction::Ln
337            | BuiltinFunction::Pow
338            | BuiltinFunction::Exp
339            | BuiltinFunction::ATan
340            | BuiltinFunction::ATan2
341            | BuiltinFunction::ToFixed
342            | BuiltinFunction::ToPrecision => true,
343            BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
344            BuiltinFunction::ShowPopupWindow
345            | BuiltinFunction::ClosePopupWindow
346            | BuiltinFunction::ShowPopupMenu
347            | BuiltinFunction::ShowPopupMenuInternal => false,
348            BuiltinFunction::SetSelectionOffsets => false,
349            BuiltinFunction::ItemFontMetrics => false, // depends also on Window's font properties
350            BuiltinFunction::StringToFloat
351            | BuiltinFunction::StringIsFloat
352            | BuiltinFunction::StringIsEmpty
353            | BuiltinFunction::StringCharacterCount
354            | BuiltinFunction::StringToLowercase
355            | BuiltinFunction::StringToUppercase => true,
356            BuiltinFunction::ColorRgbaStruct
357            | BuiltinFunction::ColorHsvaStruct
358            | BuiltinFunction::ColorOklchStruct
359            | BuiltinFunction::ColorBrighter
360            | BuiltinFunction::ColorDarker
361            | BuiltinFunction::ColorTransparentize
362            | BuiltinFunction::ColorMix
363            | BuiltinFunction::ColorWithAlpha => true,
364            // ImageSize is pure, except when loading images via the network. Then the initial size will be 0/0 and
365            // we need to make sure that calls to this function stay within a binding, so that the property
366            // notification when updating kicks in. Only SlintPad (wasm-interpreter) loads images via the network,
367            // which is when this code is targeting wasm.
368            #[cfg(not(target_arch = "wasm32"))]
369            BuiltinFunction::ImageSize => true,
370            #[cfg(target_arch = "wasm32")]
371            BuiltinFunction::ImageSize => false,
372            BuiltinFunction::ArrayLength => true,
373            BuiltinFunction::Rgb => true,
374            BuiltinFunction::Hsv => true,
375            BuiltinFunction::Oklch => true,
376            BuiltinFunction::SetTextInputFocused => false,
377            BuiltinFunction::TextInputFocused => false,
378            BuiltinFunction::ImplicitLayoutInfo(_) => false,
379            BuiltinFunction::ItemAbsolutePosition => true,
380            BuiltinFunction::RegisterCustomFontByPath
381            | BuiltinFunction::RegisterCustomFontByMemory
382            | BuiltinFunction::RegisterBitmapFont => false,
383            BuiltinFunction::Translate => false,
384            BuiltinFunction::Use24HourFormat => false,
385            BuiltinFunction::UpdateTimers => false,
386            BuiltinFunction::DetectOperatingSystem => true,
387            BuiltinFunction::StartTimer => false,
388            BuiltinFunction::StopTimer => false,
389            BuiltinFunction::RestartTimer => false,
390            BuiltinFunction::ParseMarkdown | BuiltinFunction::EscapeMarkdown => false,
391        }
392    }
393
394    // It is pure if it has no side effect
395    pub fn is_pure(&self) -> bool {
396        match self {
397            BuiltinFunction::GetWindowScaleFactor => true,
398            BuiltinFunction::GetWindowDefaultFontSize => true,
399            BuiltinFunction::AnimationTick => true,
400            BuiltinFunction::ColorScheme => true,
401            BuiltinFunction::SupportsNativeMenuBar => true,
402            BuiltinFunction::SetupMenuBar => false,
403            BuiltinFunction::MonthDayCount => true,
404            BuiltinFunction::MonthOffset => true,
405            BuiltinFunction::FormatDate => true,
406            BuiltinFunction::DateNow => true,
407            BuiltinFunction::ValidDate => true,
408            BuiltinFunction::ParseDate => true,
409            // Even if it has technically side effect, we still consider it as pure for our purpose
410            BuiltinFunction::Debug => true,
411            BuiltinFunction::Mod
412            | BuiltinFunction::Round
413            | BuiltinFunction::Ceil
414            | BuiltinFunction::Floor
415            | BuiltinFunction::Abs
416            | BuiltinFunction::Sqrt
417            | BuiltinFunction::Cos
418            | BuiltinFunction::Sin
419            | BuiltinFunction::Tan
420            | BuiltinFunction::ACos
421            | BuiltinFunction::ASin
422            | BuiltinFunction::Log
423            | BuiltinFunction::Ln
424            | BuiltinFunction::Pow
425            | BuiltinFunction::Exp
426            | BuiltinFunction::ATan
427            | BuiltinFunction::ATan2
428            | BuiltinFunction::ToFixed
429            | BuiltinFunction::ToPrecision => true,
430            BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
431            BuiltinFunction::ShowPopupWindow
432            | BuiltinFunction::ClosePopupWindow
433            | BuiltinFunction::ShowPopupMenu
434            | BuiltinFunction::ShowPopupMenuInternal => false,
435            BuiltinFunction::SetSelectionOffsets => false,
436            BuiltinFunction::ItemFontMetrics => true,
437            BuiltinFunction::StringToFloat
438            | BuiltinFunction::StringIsFloat
439            | BuiltinFunction::StringIsEmpty
440            | BuiltinFunction::StringCharacterCount
441            | BuiltinFunction::StringToLowercase
442            | BuiltinFunction::StringToUppercase => true,
443            BuiltinFunction::ColorRgbaStruct
444            | BuiltinFunction::ColorHsvaStruct
445            | BuiltinFunction::ColorOklchStruct
446            | BuiltinFunction::ColorBrighter
447            | BuiltinFunction::ColorDarker
448            | BuiltinFunction::ColorTransparentize
449            | BuiltinFunction::ColorMix
450            | BuiltinFunction::ColorWithAlpha => true,
451            BuiltinFunction::ImageSize => true,
452            BuiltinFunction::ArrayLength => true,
453            BuiltinFunction::Rgb => true,
454            BuiltinFunction::Hsv => true,
455            BuiltinFunction::Oklch => true,
456            BuiltinFunction::ImplicitLayoutInfo(_) => true,
457            BuiltinFunction::ItemAbsolutePosition => true,
458            BuiltinFunction::SetTextInputFocused => false,
459            BuiltinFunction::TextInputFocused => true,
460            BuiltinFunction::RegisterCustomFontByPath
461            | BuiltinFunction::RegisterCustomFontByMemory
462            | BuiltinFunction::RegisterBitmapFont => false,
463            BuiltinFunction::Translate => true,
464            BuiltinFunction::Use24HourFormat => true,
465            BuiltinFunction::UpdateTimers => false,
466            BuiltinFunction::DetectOperatingSystem => true,
467            BuiltinFunction::StartTimer => false,
468            BuiltinFunction::StopTimer => false,
469            BuiltinFunction::RestartTimer => false,
470            BuiltinFunction::ParseMarkdown | BuiltinFunction::EscapeMarkdown => true,
471        }
472    }
473}
474
475/// The base of a Expression::FunctionCall
476#[derive(Debug, Clone)]
477pub enum Callable {
478    Callback(NamedReference),
479    Function(NamedReference),
480    Builtin(BuiltinFunction),
481}
482impl Callable {
483    pub fn ty(&self) -> Type {
484        match self {
485            Callable::Callback(nr) => nr.ty(),
486            Callable::Function(nr) => nr.ty(),
487            Callable::Builtin(b) => Type::Function(b.ty()),
488        }
489    }
490}
491impl From<BuiltinFunction> for Callable {
492    fn from(function: BuiltinFunction) -> Self {
493        Self::Builtin(function)
494    }
495}
496
497#[derive(Debug, Clone, Eq, PartialEq)]
498pub enum OperatorClass {
499    ComparisonOp,
500    LogicalOp,
501    ArithmeticOp,
502}
503
504/// the class of for this (binary) operation
505pub fn operator_class(op: char) -> OperatorClass {
506    match op {
507        '=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp,
508        '&' | '|' => OperatorClass::LogicalOp,
509        '+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp,
510        _ => panic!("Invalid operator {op:?}"),
511    }
512}
513
514macro_rules! declare_units {
515    ($( $(#[$m:meta])* $ident:ident = $string:literal -> $ty:ident $(* $factor:expr)? ,)*) => {
516        /// The units that can be used after numbers in the language
517        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
518        pub enum Unit {
519            $($(#[$m])* $ident,)*
520        }
521
522        impl std::fmt::Display for Unit {
523            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524                match self {
525                    $(Self::$ident => write!(f, $string), )*
526                }
527            }
528        }
529
530        impl std::str::FromStr for Unit {
531            type Err = ();
532            fn from_str(s: &str) -> Result<Self, Self::Err> {
533                match s {
534                    $($string => Ok(Self::$ident), )*
535                    _ => Err(())
536                }
537            }
538        }
539
540        impl Unit {
541            pub fn ty(self) -> Type {
542                match self {
543                    $(Self::$ident => Type::$ty, )*
544                }
545            }
546
547            pub fn normalize(self, x: f64) -> f64 {
548                match self {
549                    $(Self::$ident => x $(* $factor as f64)?, )*
550                }
551            }
552
553        }
554    };
555}
556
557declare_units! {
558    /// No unit was given
559    None = "" -> Float32,
560    /// Percent value
561    Percent = "%" -> Percent,
562
563    // Lengths or Coord
564
565    /// Physical pixels
566    Phx = "phx" -> PhysicalLength,
567    /// Logical pixels
568    Px = "px" -> LogicalLength,
569    /// Centimeters
570    Cm = "cm" -> LogicalLength * 37.8,
571    /// Millimeters
572    Mm = "mm" -> LogicalLength * 3.78,
573    /// inches
574    In = "in" -> LogicalLength * 96,
575    /// Points
576    Pt = "pt" -> LogicalLength * 96./72.,
577    /// Logical pixels multiplied with the window's default-font-size
578    Rem = "rem" -> Rem,
579
580    // durations
581
582    /// Seconds
583    S = "s" -> Duration * 1000,
584    /// Milliseconds
585    Ms = "ms" -> Duration,
586
587    // angles
588
589    /// Degree
590    Deg = "deg" -> Angle,
591    /// Gradians
592    Grad = "grad" -> Angle * 360./180.,
593    /// Turns
594    Turn = "turn" -> Angle * 360.,
595    /// Radians
596    Rad = "rad" -> Angle * 360./std::f32::consts::TAU,
597}
598
599impl Default for Unit {
600    fn default() -> Self {
601        Self::None
602    }
603}
604
605#[derive(Debug, Clone, Copy)]
606pub enum MinMaxOp {
607    Min,
608    Max,
609}
610
611/// The Expression is held by properties, so it should not hold any strong references to node from the object_tree
612#[derive(Debug, Clone, Default)]
613pub enum Expression {
614    /// Something went wrong (and an error will be reported)
615    #[default]
616    Invalid,
617    /// We haven't done the lookup yet
618    Uncompiled(SyntaxNode),
619
620    /// A string literal. The .0 is the content of the string, without the quotes
621    StringLiteral(SmolStr),
622    /// Number
623    NumberLiteral(f64, Unit),
624    /// Bool
625    BoolLiteral(bool),
626
627    /// Reference to the property
628    PropertyReference(NamedReference),
629
630    /// A reference to a specific element. This isn't possible to create in .slint syntax itself, but intermediate passes may generate this
631    /// type of expression.
632    ElementReference(Weak<RefCell<Element>>),
633
634    /// Reference to the index variable of a repeater
635    ///
636    /// Example: `idx`  in `for xxx[idx] in ...`.   The element is the reference to the
637    /// element that is repeated
638    RepeaterIndexReference {
639        element: Weak<RefCell<Element>>,
640    },
641
642    /// Reference to the model variable of a repeater
643    ///
644    /// Example: `xxx`  in `for xxx[idx] in ...`.   The element is the reference to the
645    /// element that is repeated
646    RepeaterModelReference {
647        element: Weak<RefCell<Element>>,
648    },
649
650    /// Reference the parameter at the given index of the current function.
651    FunctionParameterReference {
652        index: usize,
653        ty: Type,
654    },
655
656    /// Should be directly within a CodeBlock expression, and store the value of the expression in a local variable
657    StoreLocalVariable {
658        name: SmolStr,
659        value: Box<Expression>,
660    },
661
662    /// a reference to the local variable with the given name. The type system should ensure that a variable has been stored
663    /// with this name and this type before in one of the statement of an enclosing codeblock
664    ReadLocalVariable {
665        name: SmolStr,
666        ty: Type,
667    },
668
669    /// Access to a field of the given name within a struct.
670    StructFieldAccess {
671        /// This expression should have [`Type::Struct`] type
672        base: Box<Expression>,
673        name: SmolStr,
674    },
675
676    /// Access to a index within an array.
677    ArrayIndex {
678        /// This expression should have [`Type::Array`] type
679        array: Box<Expression>,
680        index: Box<Expression>,
681    },
682
683    /// Cast an expression to the given type
684    Cast {
685        from: Box<Expression>,
686        to: Type,
687    },
688
689    /// a code block with different expression
690    CodeBlock(Vec<Expression>),
691
692    /// A function call
693    FunctionCall {
694        function: Callable,
695        arguments: Vec<Expression>,
696        source_location: Option<SourceLocation>,
697    },
698
699    /// A SelfAssignment or an Assignment.  When op is '=' this is a simple assignment.
700    SelfAssignment {
701        lhs: Box<Expression>,
702        rhs: Box<Expression>,
703        /// '+', '-', '/', '*', or '='
704        op: char,
705        node: Option<NodeOrToken>,
706    },
707
708    BinaryExpression {
709        lhs: Box<Expression>,
710        rhs: Box<Expression>,
711        /// '+', '-', '/', '*', '=', '!', '<', '>', '≤', '≥', '&', '|'
712        op: char,
713    },
714
715    UnaryOp {
716        sub: Box<Expression>,
717        /// '+', '-', '!'
718        op: char,
719    },
720
721    ImageReference {
722        resource_ref: ImageReference,
723        source_location: Option<SourceLocation>,
724        nine_slice: Option<[u16; 4]>,
725    },
726
727    Condition {
728        condition: Box<Expression>,
729        true_expr: Box<Expression>,
730        false_expr: Box<Expression>,
731    },
732
733    Array {
734        element_ty: Type,
735        values: Vec<Expression>,
736    },
737    Struct {
738        ty: Rc<Struct>,
739        values: HashMap<SmolStr, Expression>,
740    },
741
742    PathData(Path),
743
744    EasingCurve(EasingCurve),
745
746    LinearGradient {
747        angle: Box<Expression>,
748        /// First expression in the tuple is a color, second expression is the stop position
749        stops: Vec<(Expression, Expression)>,
750    },
751
752    RadialGradient {
753        /// First expression in the tuple is a color, second expression is the stop position
754        stops: Vec<(Expression, Expression)>,
755    },
756
757    ConicGradient {
758        /// The starting angle (rotation) of the gradient, corresponding to CSS `from <angle>`
759        from_angle: Box<Expression>,
760        /// First expression in the tuple is a color, second expression is the stop angle
761        stops: Vec<(Expression, Expression)>,
762    },
763
764    EnumerationValue(EnumerationValue),
765
766    ReturnStatement(Option<Box<Expression>>),
767
768    LayoutCacheAccess {
769        /// This property holds an array of entries
770        layout_cache_prop: NamedReference,
771        /// The index into that array. If repeater_index is None, then the code will be `layout_cache_prop[index]`
772        index: usize,
773        /// When set, this is the index within a repeater, and the index is then the location of another offset.
774        /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index * entries_per_item]`
775        repeater_index: Option<Box<Expression>>,
776        /// The number of entries to skip per repeater iteration.
777        /// This is only used when repeater_index is set
778        /// This is `base_entries_per_item * step` where base is 2 for LayoutCache or 4 for GridLayoutInputData,
779        /// and step is the number of children in a repeated Row (1 for non-Row elements)
780        entries_per_item: usize,
781    },
782
783    /// Organize a grid layout, i.e. decide what goes where
784    OrganizeGridLayout(crate::layout::GridLayout),
785
786    /// Compute the LayoutInfo for the given layout.
787    /// The orientation is the orientation of the cache, not the orientation of the layout
788    ComputeLayoutInfo(crate::layout::Layout, crate::layout::Orientation),
789    ComputeGridLayoutInfo {
790        layout_organized_data_prop: NamedReference,
791        layout: crate::layout::GridLayout,
792        orientation: crate::layout::Orientation,
793    },
794    SolveLayout(crate::layout::Layout, crate::layout::Orientation),
795    SolveGridLayout {
796        layout_organized_data_prop: NamedReference,
797        layout: crate::layout::GridLayout,
798        orientation: crate::layout::Orientation,
799    },
800
801    MinMax {
802        ty: Type,
803        op: MinMaxOp,
804        lhs: Box<Expression>,
805        rhs: Box<Expression>,
806    },
807
808    DebugHook {
809        expression: Box<Expression>,
810        id: SmolStr,
811    },
812
813    EmptyComponentFactory,
814}
815
816impl Expression {
817    /// Return the type of this property
818    pub fn ty(&self) -> Type {
819        match self {
820            Expression::Invalid => Type::Invalid,
821            Expression::Uncompiled(_) => Type::Invalid,
822            Expression::StringLiteral(_) => Type::String,
823            Expression::NumberLiteral(_, unit) => unit.ty(),
824            Expression::BoolLiteral(_) => Type::Bool,
825            Expression::PropertyReference(nr) => nr.ty(),
826            Expression::ElementReference(_) => Type::ElementReference,
827            Expression::RepeaterIndexReference { .. } => Type::Int32,
828            Expression::RepeaterModelReference { element } => element
829                .upgrade()
830                .unwrap()
831                .borrow()
832                .repeated
833                .as_ref()
834                .map_or(Type::Invalid, |e| model_inner_type(&e.model)),
835            Expression::FunctionParameterReference { ty, .. } => ty.clone(),
836            Expression::StructFieldAccess { base, name } => match base.ty() {
837                Type::Struct(s) => s.fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone(),
838                _ => Type::Invalid,
839            },
840            Expression::ArrayIndex { array, .. } => match array.ty() {
841                Type::Array(ty) => (*ty).clone(),
842                _ => Type::Invalid,
843            },
844            Expression::Cast { to, .. } => to.clone(),
845            Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
846            Expression::FunctionCall { function, .. } => match function.ty() {
847                Type::Function(f) | Type::Callback(f) => f.return_type.clone(),
848                _ => Type::Invalid,
849            },
850            Expression::SelfAssignment { .. } => Type::Void,
851            Expression::ImageReference { .. } => Type::Image,
852            Expression::Condition { condition: _, true_expr, false_expr } => {
853                let true_type = true_expr.ty();
854                let false_type = false_expr.ty();
855                if true_type == false_type {
856                    true_type
857                } else if true_type == Type::Invalid {
858                    false_type
859                } else if false_type == Type::Invalid {
860                    true_type
861                } else {
862                    Type::Void
863                }
864            }
865            Expression::BinaryExpression { op, lhs, rhs } => {
866                if operator_class(*op) != OperatorClass::ArithmeticOp {
867                    Type::Bool
868                } else if *op == '+' || *op == '-' {
869                    let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty());
870                    if rhs_ty == lhs_ty { rhs_ty } else { Type::Invalid }
871                } else {
872                    debug_assert!(*op == '*' || *op == '/');
873                    let unit_vec = |ty| {
874                        if let Type::UnitProduct(v) = ty {
875                            v
876                        } else if let Some(u) = ty.default_unit() {
877                            vec![(u, 1)]
878                        } else {
879                            Vec::new()
880                        }
881                    };
882                    let mut l_units = unit_vec(lhs.ty());
883                    let mut r_units = unit_vec(rhs.ty());
884                    if *op == '/' {
885                        for (_, power) in &mut r_units {
886                            *power = -*power;
887                        }
888                    }
889                    for (unit, power) in r_units {
890                        if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) {
891                            *p += power;
892                        } else {
893                            l_units.push((unit, power));
894                        }
895                    }
896
897                    // normalize the vector by removing empty and sorting
898                    l_units.retain(|(_, p)| *p != 0);
899                    l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) {
900                        std::cmp::Ordering::Equal => u1.cmp(u2),
901                        x => x,
902                    });
903
904                    if l_units.is_empty() {
905                        Type::Float32
906                    } else if l_units.len() == 1 && l_units[0].1 == 1 {
907                        l_units[0].0.ty()
908                    } else {
909                        Type::UnitProduct(l_units)
910                    }
911                }
912            }
913            Expression::UnaryOp { sub, .. } => sub.ty(),
914            Expression::Array { element_ty, .. } => Type::Array(Rc::new(element_ty.clone())),
915            Expression::Struct { ty, .. } => ty.clone().into(),
916            Expression::PathData { .. } => Type::PathData,
917            Expression::StoreLocalVariable { .. } => Type::Void,
918            Expression::ReadLocalVariable { ty, .. } => ty.clone(),
919            Expression::EasingCurve(_) => Type::Easing,
920            Expression::LinearGradient { .. } => Type::Brush,
921            Expression::RadialGradient { .. } => Type::Brush,
922            Expression::ConicGradient { .. } => Type::Brush,
923            Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
924            // invalid because the expression is unreachable
925            Expression::ReturnStatement(_) => Type::Invalid,
926            Expression::LayoutCacheAccess { .. } => Type::LogicalLength,
927            Expression::OrganizeGridLayout(..) => Type::ArrayOfU16,
928            Expression::ComputeLayoutInfo(..) => typeregister::layout_info_type().into(),
929            Expression::ComputeGridLayoutInfo { .. } => typeregister::layout_info_type().into(),
930            Expression::SolveLayout(..) => Type::LayoutCache,
931            Expression::SolveGridLayout { .. } => Type::LayoutCache,
932            Expression::MinMax { ty, .. } => ty.clone(),
933            Expression::EmptyComponentFactory => Type::ComponentFactory,
934            Expression::DebugHook { expression, .. } => expression.ty(),
935        }
936    }
937
938    /// Call the visitor for each sub-expression.  (note: this function does not recurse)
939    pub fn visit(&self, mut visitor: impl FnMut(&Self)) {
940        match self {
941            Expression::Invalid => {}
942            Expression::Uncompiled(_) => {}
943            Expression::StringLiteral(_) => {}
944            Expression::NumberLiteral(_, _) => {}
945            Expression::BoolLiteral(_) => {}
946            Expression::PropertyReference { .. } => {}
947            Expression::FunctionParameterReference { .. } => {}
948            Expression::ElementReference(_) => {}
949            Expression::StructFieldAccess { base, .. } => visitor(base),
950            Expression::ArrayIndex { array, index } => {
951                visitor(array);
952                visitor(index);
953            }
954            Expression::RepeaterIndexReference { .. } => {}
955            Expression::RepeaterModelReference { .. } => {}
956            Expression::Cast { from, .. } => visitor(from),
957            Expression::CodeBlock(sub) => {
958                sub.iter().for_each(visitor);
959            }
960            Expression::FunctionCall { function: _, arguments, source_location: _ } => {
961                arguments.iter().for_each(visitor);
962            }
963            Expression::SelfAssignment { lhs, rhs, .. } => {
964                visitor(lhs);
965                visitor(rhs);
966            }
967            Expression::ImageReference { .. } => {}
968            Expression::Condition { condition, true_expr, false_expr } => {
969                visitor(condition);
970                visitor(true_expr);
971                visitor(false_expr);
972            }
973            Expression::BinaryExpression { lhs, rhs, .. } => {
974                visitor(lhs);
975                visitor(rhs);
976            }
977            Expression::UnaryOp { sub, .. } => visitor(sub),
978            Expression::Array { values, .. } => {
979                for x in values {
980                    visitor(x);
981                }
982            }
983            Expression::Struct { values, .. } => {
984                for x in values.values() {
985                    visitor(x);
986                }
987            }
988            Expression::PathData(data) => match data {
989                Path::Elements(elements) => {
990                    for element in elements {
991                        element.bindings.values().for_each(|binding| visitor(&binding.borrow()))
992                    }
993                }
994                Path::Events(events, coordinates) => {
995                    events.iter().chain(coordinates.iter()).for_each(visitor);
996                }
997                Path::Commands(commands) => visitor(commands),
998            },
999            Expression::StoreLocalVariable { value, .. } => visitor(value),
1000            Expression::ReadLocalVariable { .. } => {}
1001            Expression::EasingCurve(_) => {}
1002            Expression::LinearGradient { angle, stops } => {
1003                visitor(angle);
1004                for (c, s) in stops {
1005                    visitor(c);
1006                    visitor(s);
1007                }
1008            }
1009            Expression::RadialGradient { stops } => {
1010                for (c, s) in stops {
1011                    visitor(c);
1012                    visitor(s);
1013                }
1014            }
1015            Expression::ConicGradient { from_angle, stops } => {
1016                visitor(from_angle);
1017                for (c, s) in stops {
1018                    visitor(c);
1019                    visitor(s);
1020                }
1021            }
1022            Expression::EnumerationValue(_) => {}
1023            Expression::ReturnStatement(expr) => {
1024                expr.as_deref().map(visitor);
1025            }
1026            Expression::LayoutCacheAccess { repeater_index, .. } => {
1027                repeater_index.as_deref().map(visitor);
1028            }
1029            Expression::OrganizeGridLayout(..) => {}
1030            Expression::ComputeLayoutInfo(..) => {}
1031            Expression::ComputeGridLayoutInfo { .. } => {}
1032            Expression::SolveLayout(..) => {}
1033            Expression::SolveGridLayout { .. } => {}
1034            Expression::MinMax { lhs, rhs, .. } => {
1035                visitor(lhs);
1036                visitor(rhs);
1037            }
1038            Expression::EmptyComponentFactory => {}
1039            Expression::DebugHook { expression, .. } => visitor(expression),
1040        }
1041    }
1042
1043    pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) {
1044        match self {
1045            Expression::Invalid => {}
1046            Expression::Uncompiled(_) => {}
1047            Expression::StringLiteral(_) => {}
1048            Expression::NumberLiteral(_, _) => {}
1049            Expression::BoolLiteral(_) => {}
1050            Expression::PropertyReference { .. } => {}
1051            Expression::FunctionParameterReference { .. } => {}
1052            Expression::ElementReference(_) => {}
1053            Expression::StructFieldAccess { base, .. } => visitor(base),
1054            Expression::ArrayIndex { array, index } => {
1055                visitor(array);
1056                visitor(index);
1057            }
1058            Expression::RepeaterIndexReference { .. } => {}
1059            Expression::RepeaterModelReference { .. } => {}
1060            Expression::Cast { from, .. } => visitor(from),
1061            Expression::CodeBlock(sub) => {
1062                sub.iter_mut().for_each(visitor);
1063            }
1064            Expression::FunctionCall { function: _, arguments, source_location: _ } => {
1065                arguments.iter_mut().for_each(visitor);
1066            }
1067            Expression::SelfAssignment { lhs, rhs, .. } => {
1068                visitor(lhs);
1069                visitor(rhs);
1070            }
1071            Expression::ImageReference { .. } => {}
1072            Expression::Condition { condition, true_expr, false_expr } => {
1073                visitor(condition);
1074                visitor(true_expr);
1075                visitor(false_expr);
1076            }
1077            Expression::BinaryExpression { lhs, rhs, .. } => {
1078                visitor(lhs);
1079                visitor(rhs);
1080            }
1081            Expression::UnaryOp { sub, .. } => visitor(sub),
1082            Expression::Array { values, .. } => {
1083                for x in values {
1084                    visitor(x);
1085                }
1086            }
1087            Expression::Struct { values, .. } => {
1088                for x in values.values_mut() {
1089                    visitor(x);
1090                }
1091            }
1092            Expression::PathData(data) => match data {
1093                Path::Elements(elements) => {
1094                    for element in elements {
1095                        element
1096                            .bindings
1097                            .values_mut()
1098                            .for_each(|binding| visitor(&mut binding.borrow_mut()))
1099                    }
1100                }
1101                Path::Events(events, coordinates) => {
1102                    events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
1103                }
1104                Path::Commands(commands) => visitor(commands),
1105            },
1106            Expression::StoreLocalVariable { value, .. } => visitor(value),
1107            Expression::ReadLocalVariable { .. } => {}
1108            Expression::EasingCurve(_) => {}
1109            Expression::LinearGradient { angle, stops } => {
1110                visitor(angle);
1111                for (c, s) in stops {
1112                    visitor(c);
1113                    visitor(s);
1114                }
1115            }
1116            Expression::RadialGradient { stops } => {
1117                for (c, s) in stops {
1118                    visitor(c);
1119                    visitor(s);
1120                }
1121            }
1122            Expression::ConicGradient { from_angle, stops } => {
1123                visitor(from_angle);
1124                for (c, s) in stops {
1125                    visitor(c);
1126                    visitor(s);
1127                }
1128            }
1129            Expression::EnumerationValue(_) => {}
1130            Expression::ReturnStatement(expr) => {
1131                expr.as_deref_mut().map(visitor);
1132            }
1133            Expression::LayoutCacheAccess { repeater_index, .. } => {
1134                repeater_index.as_deref_mut().map(visitor);
1135            }
1136            Expression::OrganizeGridLayout(..) => {}
1137            Expression::ComputeLayoutInfo(..) => {}
1138            Expression::ComputeGridLayoutInfo { .. } => {}
1139            Expression::SolveLayout(..) => {}
1140            Expression::SolveGridLayout { .. } => {}
1141            Expression::MinMax { lhs, rhs, .. } => {
1142                visitor(lhs);
1143                visitor(rhs);
1144            }
1145            Expression::EmptyComponentFactory => {}
1146            Expression::DebugHook { expression, .. } => visitor(expression),
1147        }
1148    }
1149
1150    /// Visit itself and each sub expression recursively
1151    pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) {
1152        visitor(self);
1153        self.visit(|e| e.visit_recursive(visitor));
1154    }
1155
1156    /// Visit itself and each sub expression recursively
1157    pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
1158        visitor(self);
1159        self.visit_mut(|e| e.visit_recursive_mut(visitor));
1160    }
1161
1162    pub fn is_constant(&self, ga: Option<&crate::passes::GlobalAnalysis>) -> bool {
1163        match self {
1164            Expression::Invalid => true,
1165            Expression::Uncompiled(_) => false,
1166            Expression::StringLiteral(_) => true,
1167            Expression::NumberLiteral(_, _) => true,
1168            Expression::BoolLiteral(_) => true,
1169            Expression::PropertyReference(nr) => nr.is_constant(),
1170            Expression::ElementReference(_) => false,
1171            Expression::RepeaterIndexReference { .. } => false,
1172            Expression::RepeaterModelReference { .. } => false,
1173            // Allow functions to be marked as const
1174            Expression::FunctionParameterReference { .. } => true,
1175            Expression::StructFieldAccess { base, .. } => base.is_constant(ga),
1176            Expression::ArrayIndex { array, index } => {
1177                array.is_constant(ga) && index.is_constant(ga)
1178            }
1179            Expression::Cast { from, .. } => from.is_constant(ga),
1180            // This is conservative: the return value is the last expression in the block, but
1181            // we kind of mean "pure" here too, so ensure the whole body is OK.
1182            Expression::CodeBlock(sub) => sub.iter().all(|s| s.is_constant(ga)),
1183            Expression::FunctionCall { function, arguments, .. } => {
1184                let is_const = match function {
1185                    Callable::Builtin(b) => b.is_const(ga),
1186                    Callable::Function(nr) => nr.is_constant(),
1187                    Callable::Callback(..) => false,
1188                };
1189                is_const && arguments.iter().all(|a| a.is_constant(ga))
1190            }
1191            Expression::SelfAssignment { .. } => false,
1192            Expression::ImageReference { .. } => true,
1193            Expression::Condition { condition, false_expr, true_expr } => {
1194                condition.is_constant(ga) && false_expr.is_constant(ga) && true_expr.is_constant(ga)
1195            }
1196            Expression::BinaryExpression { lhs, rhs, .. } => {
1197                lhs.is_constant(ga) && rhs.is_constant(ga)
1198            }
1199            Expression::UnaryOp { sub, .. } => sub.is_constant(ga),
1200            // Array will turn into model, and they can't be considered as constant if the model
1201            // is used and the model is changed. CF issue #5249
1202            //Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
1203            Expression::Array { .. } => false,
1204            Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant(ga)),
1205            Expression::PathData(data) => match data {
1206                Path::Elements(elements) => elements
1207                    .iter()
1208                    .all(|element| element.bindings.values().all(|v| v.borrow().is_constant(ga))),
1209                Path::Events(_, _) => true,
1210                Path::Commands(_) => false,
1211            },
1212            Expression::StoreLocalVariable { value, .. } => value.is_constant(ga),
1213            // We only load what we store, and stores are alredy checked
1214            Expression::ReadLocalVariable { .. } => true,
1215            Expression::EasingCurve(_) => true,
1216            Expression::LinearGradient { angle, stops } => {
1217                angle.is_constant(ga)
1218                    && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1219            }
1220            Expression::RadialGradient { stops } => {
1221                stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1222            }
1223            Expression::ConicGradient { from_angle, stops } => {
1224                from_angle.is_constant(ga)
1225                    && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1226            }
1227            Expression::EnumerationValue(_) => true,
1228            Expression::ReturnStatement(expr) => {
1229                expr.as_ref().is_none_or(|expr| expr.is_constant(ga))
1230            }
1231            // TODO:  detect constant property within layouts
1232            Expression::LayoutCacheAccess { .. } => false,
1233            Expression::OrganizeGridLayout { .. } => false,
1234            Expression::ComputeLayoutInfo(..) => false,
1235            Expression::ComputeGridLayoutInfo { .. } => false,
1236            Expression::SolveLayout(..) => false,
1237            Expression::SolveGridLayout { .. } => false,
1238            Expression::MinMax { lhs, rhs, .. } => lhs.is_constant(ga) && rhs.is_constant(ga),
1239            Expression::EmptyComponentFactory => true,
1240            Expression::DebugHook { .. } => false,
1241        }
1242    }
1243
1244    /// Create a conversion node if needed, or throw an error if the type is not matching
1245    #[must_use]
1246    pub fn maybe_convert_to(
1247        self,
1248        target_type: Type,
1249        node: &dyn Spanned,
1250        diag: &mut BuildDiagnostics,
1251    ) -> Expression {
1252        let ty = self.ty();
1253        if ty == target_type
1254            || target_type == Type::Void
1255            || target_type == Type::Invalid
1256            || ty == Type::Invalid
1257        {
1258            self
1259        } else if ty.can_convert(&target_type) {
1260            let from = match (ty, &target_type) {
1261                (Type::Brush, Type::Color) => match self {
1262                    Expression::LinearGradient { .. }
1263                    | Expression::RadialGradient { .. }
1264                    | Expression::ConicGradient { .. } => {
1265                        let message = format!(
1266                            "Narrowing conversion from {0} to {1}. This can lead to unexpected behavior because the {0} is a gradient",
1267                            Type::Brush,
1268                            Type::Color
1269                        );
1270                        diag.push_warning(message, node);
1271                        self
1272                    }
1273                    _ => self,
1274                },
1275                (Type::Percent, Type::Float32) => Expression::BinaryExpression {
1276                    lhs: Box::new(self),
1277                    rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
1278                    op: '*',
1279                },
1280                (ref from_ty @ Type::Struct(ref left), Type::Struct(right))
1281                    if left.fields != right.fields =>
1282                {
1283                    if let Expression::Struct { mut values, .. } = self {
1284                        let mut new_values = HashMap::new();
1285                        for (key, ty) in &right.fields {
1286                            let (key, expression) = values.remove_entry(key).map_or_else(
1287                                || (key.clone(), Expression::default_value_for_type(ty)),
1288                                |(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)),
1289                            );
1290                            new_values.insert(key, expression);
1291                        }
1292                        return Expression::Struct { values: new_values, ty: right.clone() };
1293                    }
1294                    static COUNT: std::sync::atomic::AtomicUsize =
1295                        std::sync::atomic::AtomicUsize::new(0);
1296                    let var_name = format_smolstr!(
1297                        "tmpobj_conv_{}",
1298                        COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
1299                    );
1300                    let mut new_values = HashMap::new();
1301                    for (key, ty) in &right.fields {
1302                        let expression = if left.fields.contains_key(key) {
1303                            Expression::StructFieldAccess {
1304                                base: Box::new(Expression::ReadLocalVariable {
1305                                    name: var_name.clone(),
1306                                    ty: from_ty.clone(),
1307                                }),
1308                                name: key.clone(),
1309                            }
1310                            .maybe_convert_to(ty.clone(), node, diag)
1311                        } else {
1312                            Expression::default_value_for_type(ty)
1313                        };
1314                        new_values.insert(key.clone(), expression);
1315                    }
1316                    return Expression::CodeBlock(vec![
1317                        Expression::StoreLocalVariable { name: var_name, value: Box::new(self) },
1318                        Expression::Struct { values: new_values, ty: right.clone() },
1319                    ]);
1320                }
1321                (left, right) => match (left.as_unit_product(), right.as_unit_product()) {
1322                    (Some(left), Some(right)) => {
1323                        if let Some(conversion_powers) =
1324                            crate::langtype::unit_product_length_conversion(&left, &right)
1325                        {
1326                            let apply_power =
1327                                |mut result, power: i8, builtin_fn: BuiltinFunction| {
1328                                    let op = if power < 0 { '*' } else { '/' };
1329                                    for _ in 0..power.abs() {
1330                                        result = Expression::BinaryExpression {
1331                                            lhs: Box::new(result),
1332                                            rhs: Box::new(Expression::FunctionCall {
1333                                                function: Callable::Builtin(builtin_fn.clone()),
1334                                                arguments: Vec::new(),
1335                                                source_location: Some(node.to_source_location()),
1336                                            }),
1337                                            op,
1338                                        }
1339                                    }
1340                                    result
1341                                };
1342
1343                            let mut result = self;
1344
1345                            if conversion_powers.rem_to_px_power != 0 {
1346                                result = apply_power(
1347                                    result,
1348                                    conversion_powers.rem_to_px_power,
1349                                    BuiltinFunction::GetWindowDefaultFontSize,
1350                                )
1351                            }
1352                            if conversion_powers.px_to_phx_power != 0 {
1353                                result = apply_power(
1354                                    result,
1355                                    conversion_powers.px_to_phx_power,
1356                                    BuiltinFunction::GetWindowScaleFactor,
1357                                )
1358                            }
1359
1360                            result
1361                        } else {
1362                            self
1363                        }
1364                    }
1365                    _ => self,
1366                },
1367            };
1368            Expression::Cast { from: Box::new(from), to: target_type }
1369        } else if matches!(
1370            (&ty, &target_type, &self),
1371            (Type::Array(_), Type::Array(_), Expression::Array { .. })
1372        ) {
1373            // Special case for converting array literals
1374            match (self, target_type) {
1375                (Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array {
1376                    values: values
1377                        .into_iter()
1378                        .map(|e| e.maybe_convert_to((*target_type).clone(), node, diag))
1379                        .take_while(|e| !matches!(e, Expression::Invalid))
1380                        .collect(),
1381                    element_ty: (*target_type).clone(),
1382                },
1383                _ => unreachable!(),
1384            }
1385        } else if let (Type::Struct(struct_type), Expression::Struct { values, .. }) =
1386            (&target_type, &self)
1387        {
1388            // Also special case struct literal in case they contain array literal
1389            let mut fields = struct_type.fields.clone();
1390            let mut new_values = HashMap::new();
1391            for (f, v) in values {
1392                if let Some(t) = fields.remove(f) {
1393                    new_values.insert(f.clone(), v.clone().maybe_convert_to(t, node, diag));
1394                } else {
1395                    diag.push_error(format!("Cannot convert {ty} to {target_type}"), node);
1396                    return self;
1397                }
1398            }
1399            for (f, t) in fields {
1400                new_values.insert(f, Expression::default_value_for_type(&t));
1401            }
1402            Expression::Struct { ty: struct_type.clone(), values: new_values }
1403        } else {
1404            let mut message = format!("Cannot convert {ty} to {target_type}");
1405            // Explicit error message for unit conversion
1406            if let Some(from_unit) = ty.default_unit() {
1407                if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) {
1408                    message =
1409                        format!("{message}. Divide by 1{from_unit} to convert to a plain number");
1410                }
1411            } else if let Some(to_unit) = target_type.default_unit()
1412                && matches!(ty, Type::Int32 | Type::Float32)
1413            {
1414                if let Expression::NumberLiteral(value, Unit::None) = self
1415                    && value == 0.
1416                {
1417                    // Allow conversion from literal 0 to any unit
1418                    return Expression::NumberLiteral(0., to_unit);
1419                }
1420                message = format!(
1421                    "{message}. Use an unit, or multiply by 1{to_unit} to convert explicitly"
1422                );
1423            }
1424            diag.push_error(message, node);
1425            self
1426        }
1427    }
1428
1429    /// Return the default value for the given type
1430    pub fn default_value_for_type(ty: &Type) -> Expression {
1431        match ty {
1432            Type::Invalid
1433            | Type::Callback { .. }
1434            | Type::Function { .. }
1435            | Type::InferredProperty
1436            | Type::InferredCallback
1437            | Type::ElementReference
1438            | Type::LayoutCache
1439            | Type::ArrayOfU16 => Expression::Invalid,
1440            Type::Void => Expression::CodeBlock(Vec::new()),
1441            Type::Float32 => Expression::NumberLiteral(0., Unit::None),
1442            Type::String => Expression::StringLiteral(SmolStr::default()),
1443            Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast {
1444                from: Box::new(Expression::NumberLiteral(0., Unit::None)),
1445                to: ty.clone(),
1446            },
1447            Type::Duration => Expression::NumberLiteral(0., Unit::Ms),
1448            Type::Angle => Expression::NumberLiteral(0., Unit::Deg),
1449            Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx),
1450            Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px),
1451            Type::Rem => Expression::NumberLiteral(0., Unit::Rem),
1452            Type::Percent => Expression::NumberLiteral(100., Unit::Percent),
1453            Type::Image => Expression::ImageReference {
1454                resource_ref: ImageReference::None,
1455                source_location: None,
1456                nine_slice: None,
1457            },
1458            Type::Bool => Expression::BoolLiteral(false),
1459            Type::Model => Expression::Invalid,
1460            Type::PathData => Expression::PathData(Path::Elements(Vec::new())),
1461            Type::Array(element_ty) => {
1462                Expression::Array { element_ty: (**element_ty).clone(), values: Vec::new() }
1463            }
1464            Type::Struct(s) => Expression::Struct {
1465                ty: s.clone(),
1466                values: s
1467                    .fields
1468                    .iter()
1469                    .map(|(k, v)| (k.clone(), Expression::default_value_for_type(v)))
1470                    .collect(),
1471            },
1472            Type::Easing => Expression::EasingCurve(EasingCurve::default()),
1473            Type::Brush => Expression::Cast {
1474                from: Box::new(Expression::default_value_for_type(&Type::Color)),
1475                to: Type::Brush,
1476            },
1477            Type::Enumeration(enumeration) => {
1478                Expression::EnumerationValue(enumeration.clone().default_value())
1479            }
1480            Type::ComponentFactory => Expression::EmptyComponentFactory,
1481            Type::StyledText => Expression::Invalid,
1482        }
1483    }
1484
1485    /// Try to mark this expression to a lvalue that can be assigned to.
1486    ///
1487    /// Return true if the expression is a "lvalue" that can be used as the left hand side of a `=` or `+=` or similar
1488    pub fn try_set_rw(
1489        &mut self,
1490        ctx: &mut LookupCtx,
1491        what: &'static str,
1492        node: &dyn Spanned,
1493    ) -> bool {
1494        match self {
1495            Expression::PropertyReference(nr) => {
1496                nr.mark_as_set();
1497                let mut lookup = nr.element().borrow().lookup_property(nr.name());
1498                lookup.is_local_to_component &= ctx.is_local_element(&nr.element());
1499                if lookup.property_visibility == PropertyVisibility::Constexpr {
1500                    ctx.diag.push_error(
1501                        "The property must be known at compile time and cannot be changed at runtime"
1502                            .into(),
1503                        node,
1504                    );
1505                    false
1506                } else if lookup.is_valid_for_assignment() {
1507                    if !nr
1508                        .element()
1509                        .borrow()
1510                        .property_analysis
1511                        .borrow()
1512                        .get(nr.name())
1513                        .is_some_and(|d| d.is_linked_to_read_only)
1514                    {
1515                        true
1516                    } else if ctx.is_legacy_component() {
1517                        ctx.diag.push_warning("Modifying a property that is linked to a read-only property is deprecated".into(), node);
1518                        true
1519                    } else {
1520                        ctx.diag.push_error(
1521                            "Cannot modify a property that is linked to a read-only property"
1522                                .into(),
1523                            node,
1524                        );
1525                        false
1526                    }
1527                } else if ctx.is_legacy_component()
1528                    && lookup.property_visibility == PropertyVisibility::Output
1529                {
1530                    ctx.diag
1531                        .push_warning(format!("{what} on an output property is deprecated"), node);
1532                    true
1533                } else {
1534                    ctx.diag.push_error(
1535                        format!("{what} on a {} property", lookup.property_visibility),
1536                        node,
1537                    );
1538                    false
1539                }
1540            }
1541            Expression::StructFieldAccess { base, .. } => base.try_set_rw(ctx, what, node),
1542            Expression::RepeaterModelReference { .. } => true,
1543            Expression::ArrayIndex { array, .. } => array.try_set_rw(ctx, what, node),
1544            _ => {
1545                ctx.diag.push_error(format!("{what} needs to be done on a property"), node);
1546                false
1547            }
1548        }
1549    }
1550
1551    /// Unwrap DebugHook expressions to their contained sub-expression
1552    pub fn ignore_debug_hooks(&self) -> &Expression {
1553        match self {
1554            Expression::DebugHook { expression, .. } => expression.as_ref(),
1555            _ => self,
1556        }
1557    }
1558}
1559
1560fn model_inner_type(model: &Expression) -> Type {
1561    match model {
1562        Expression::Cast { from, to: Type::Model } => model_inner_type(from),
1563        Expression::CodeBlock(cb) => cb.last().map_or(Type::Invalid, model_inner_type),
1564        _ => match model.ty() {
1565            Type::Float32 | Type::Int32 => Type::Int32,
1566            Type::Array(elem) => (*elem).clone(),
1567            _ => Type::Invalid,
1568        },
1569    }
1570}
1571
1572/// The right hand side of a two way binding
1573#[derive(Clone, Debug)]
1574pub struct TwoWayBinding {
1575    /// The property being linked
1576    pub property: NamedReference,
1577    /// If property is a struct, this is the fields.
1578    /// So if you have `foo <=> element.property.baz.xyz`, then `field_access` is `vec!["baz", "xyz"]`
1579    pub field_access: Vec<SmolStr>,
1580}
1581impl TwoWayBinding {
1582    pub fn ty(&self) -> Type {
1583        let mut ty = self.property.ty();
1584        for x in &self.field_access {
1585            ty = match ty {
1586                Type::InferredProperty | Type::InferredCallback => return ty,
1587                Type::Struct(s) => s.fields.get(x).cloned().unwrap_or_default(),
1588                _ => return Type::Invalid,
1589            };
1590        }
1591        ty
1592    }
1593}
1594
1595impl From<NamedReference> for TwoWayBinding {
1596    fn from(nr: NamedReference) -> Self {
1597        Self { property: nr, field_access: Vec::new() }
1598    }
1599}
1600
1601/// The expression in the Element::binding hash table
1602#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
1603pub struct BindingExpression {
1604    #[deref]
1605    #[deref_mut]
1606    pub expression: Expression,
1607    /// The location of this expression in the source code
1608    pub span: Option<SourceLocation>,
1609    /// How deep is this binding declared in the hierarchy. When two bindings are conflicting
1610    /// for the same priority (because of a two way binding), the lower priority wins.
1611    /// The priority starts at 1, and each level of inlining adds one to the priority.
1612    /// 0 means the expression was added by some passes and it is not explicit in the source code
1613    pub priority: i32,
1614
1615    pub animation: Option<PropertyAnimation>,
1616
1617    /// The analysis information. None before it is computed
1618    pub analysis: Option<BindingAnalysis>,
1619
1620    /// The properties this expression is aliased with using two way bindings
1621    pub two_way_bindings: Vec<TwoWayBinding>,
1622}
1623
1624impl std::convert::From<Expression> for BindingExpression {
1625    fn from(expression: Expression) -> Self {
1626        Self {
1627            expression,
1628            span: None,
1629            priority: 0,
1630            animation: Default::default(),
1631            analysis: Default::default(),
1632            two_way_bindings: Default::default(),
1633        }
1634    }
1635}
1636
1637impl BindingExpression {
1638    pub fn new_uncompiled(node: SyntaxNode) -> Self {
1639        Self {
1640            expression: Expression::Uncompiled(node.clone()),
1641            span: Some(node.to_source_location()),
1642            priority: 1,
1643            animation: Default::default(),
1644            analysis: Default::default(),
1645            two_way_bindings: Default::default(),
1646        }
1647    }
1648    pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self {
1649        Self {
1650            expression,
1651            span: Some(span),
1652            priority: 0,
1653            animation: Default::default(),
1654            analysis: Default::default(),
1655            two_way_bindings: Default::default(),
1656        }
1657    }
1658
1659    /// Create an expression binding that simply is a two way binding to the other
1660    pub fn new_two_way(other: TwoWayBinding) -> Self {
1661        Self {
1662            expression: Expression::Invalid,
1663            span: None,
1664            priority: 0,
1665            animation: Default::default(),
1666            analysis: Default::default(),
1667            two_way_bindings: vec![other],
1668        }
1669    }
1670
1671    /// Merge the other into this one. Normally, &self is kept intact (has priority)
1672    /// unless the expression is invalid, in which case the other one is taken.
1673    ///
1674    /// Also the animation is taken if the other don't have one, and the two ways binding
1675    /// are taken into account.
1676    ///
1677    /// Returns true if the other expression was taken
1678    pub fn merge_with(&mut self, other: &Self) -> bool {
1679        if self.animation.is_none() {
1680            self.animation.clone_from(&other.animation);
1681        }
1682        let has_binding = self.has_binding();
1683        self.two_way_bindings.extend_from_slice(&other.two_way_bindings);
1684        if !has_binding {
1685            self.priority = other.priority;
1686            self.expression = other.expression.clone();
1687            true
1688        } else {
1689            false
1690        }
1691    }
1692
1693    /// returns false if there is no expression or two way binding
1694    pub fn has_binding(&self) -> bool {
1695        !matches!(self.expression, Expression::Invalid) || !self.two_way_bindings.is_empty()
1696    }
1697}
1698
1699impl Spanned for BindingExpression {
1700    fn span(&self) -> crate::diagnostics::Span {
1701        self.span.as_ref().map(|x| x.span()).unwrap_or_default()
1702    }
1703    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
1704        self.span.as_ref().and_then(|x| x.source_file())
1705    }
1706}
1707
1708#[derive(Default, Debug, Clone)]
1709pub struct BindingAnalysis {
1710    /// true if that binding is part of a binding loop that already has been reported.
1711    pub is_in_binding_loop: Cell<bool>,
1712
1713    /// true if the binding is a constant value that can be set without creating a binding at runtime
1714    pub is_const: bool,
1715
1716    /// true if this binding does not depends on the value of property that are set externally.
1717    /// When true, this binding cannot be part of a binding loop involving external components
1718    pub no_external_dependencies: bool,
1719}
1720
1721#[derive(Debug, Clone)]
1722pub enum Path {
1723    Elements(Vec<PathElement>),
1724    Events(Vec<Expression>, Vec<Expression>),
1725    Commands(Box<Expression>), // expr must evaluate to string
1726}
1727
1728#[derive(Debug, Clone)]
1729pub struct PathElement {
1730    pub element_type: Rc<BuiltinElement>,
1731    pub bindings: BindingsMap,
1732}
1733
1734#[derive(Clone, Debug, Default)]
1735pub enum EasingCurve {
1736    #[default]
1737    Linear,
1738    CubicBezier(f32, f32, f32, f32),
1739    EaseInElastic,
1740    EaseOutElastic,
1741    EaseInOutElastic,
1742    EaseInBounce,
1743    EaseOutBounce,
1744    EaseInOutBounce,
1745    // CubicBezierNonConst([Box<Expression>; 4]),
1746    // Custom(Box<dyn Fn(f32)->f32>),
1747}
1748
1749// The compiler generates ResourceReference::AbsolutePath for all references like @image-url("foo.png")
1750// and the resource lowering path may change this to EmbeddedData if configured.
1751#[derive(Clone, Debug)]
1752pub enum ImageReference {
1753    None,
1754    AbsolutePath(SmolStr),
1755    EmbeddedData { resource_id: usize, extension: String },
1756    EmbeddedTexture { resource_id: usize },
1757}
1758
1759/// Print the expression as a .slint code (not necessarily valid .slint)
1760pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result {
1761    match expression {
1762        Expression::Invalid => write!(f, "<invalid>"),
1763        Expression::Uncompiled(u) => write!(f, "{u:?}"),
1764        Expression::StringLiteral(s) => write!(f, "{s:?}"),
1765        Expression::NumberLiteral(vl, unit) => write!(f, "{vl}{unit}"),
1766        Expression::BoolLiteral(b) => write!(f, "{b:?}"),
1767        Expression::PropertyReference(a) => write!(f, "{a:?}"),
1768        Expression::ElementReference(a) => write!(f, "{a:?}"),
1769        Expression::RepeaterIndexReference { element } => {
1770            crate::namedreference::pretty_print_element_ref(f, element)
1771        }
1772        Expression::RepeaterModelReference { element } => {
1773            crate::namedreference::pretty_print_element_ref(f, element)?;
1774            write!(f, ".@model")
1775        }
1776        Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_{index}"),
1777        Expression::StoreLocalVariable { name, value } => {
1778            write!(f, "{name} = ")?;
1779            pretty_print(f, value)
1780        }
1781        Expression::ReadLocalVariable { name, ty: _ } => write!(f, "{name}"),
1782        Expression::StructFieldAccess { base, name } => {
1783            pretty_print(f, base)?;
1784            write!(f, ".{name}")
1785        }
1786        Expression::ArrayIndex { array, index } => {
1787            pretty_print(f, array)?;
1788            write!(f, "[")?;
1789            pretty_print(f, index)?;
1790            write!(f, "]")
1791        }
1792        Expression::Cast { from, to } => {
1793            write!(f, "(")?;
1794            pretty_print(f, from)?;
1795            write!(f, "/* as {to} */)")
1796        }
1797        Expression::CodeBlock(c) => {
1798            write!(f, "{{ ")?;
1799            for e in c {
1800                pretty_print(f, e)?;
1801                write!(f, "; ")?;
1802            }
1803            write!(f, "}}")
1804        }
1805        Expression::FunctionCall { function, arguments, source_location: _ } => {
1806            match function {
1807                Callable::Builtin(b) => write!(f, "{b:?}")?,
1808                Callable::Callback(nr) | Callable::Function(nr) => write!(f, "{nr:?}")?,
1809            }
1810            write!(f, "(")?;
1811            for e in arguments {
1812                pretty_print(f, e)?;
1813                write!(f, ", ")?;
1814            }
1815            write!(f, ")")
1816        }
1817        Expression::SelfAssignment { lhs, rhs, op, .. } => {
1818            pretty_print(f, lhs)?;
1819            write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
1820            pretty_print(f, rhs)
1821        }
1822        Expression::BinaryExpression { lhs, rhs, op } => {
1823            write!(f, "(")?;
1824            pretty_print(f, lhs)?;
1825            match *op {
1826                '=' | '!' => write!(f, " {op}= ")?,
1827                _ => write!(f, " {op} ")?,
1828            };
1829            pretty_print(f, rhs)?;
1830            write!(f, ")")
1831        }
1832        Expression::UnaryOp { sub, op } => {
1833            write!(f, "{op}")?;
1834            pretty_print(f, sub)
1835        }
1836        Expression::ImageReference { resource_ref, .. } => write!(f, "{resource_ref:?}"),
1837        Expression::Condition { condition, true_expr, false_expr } => {
1838            write!(f, "if (")?;
1839            pretty_print(f, condition)?;
1840            write!(f, ") {{ ")?;
1841            pretty_print(f, true_expr)?;
1842            write!(f, " }} else {{ ")?;
1843            pretty_print(f, false_expr)?;
1844            write!(f, " }}")
1845        }
1846        Expression::Array { element_ty: _, values } => {
1847            write!(f, "[")?;
1848            for e in values {
1849                pretty_print(f, e)?;
1850                write!(f, ", ")?;
1851            }
1852            write!(f, "]")
1853        }
1854        Expression::Struct { ty: _, values } => {
1855            write!(f, "{{ ")?;
1856            for (name, e) in values {
1857                write!(f, "{name}: ")?;
1858                pretty_print(f, e)?;
1859                write!(f, ", ")?;
1860            }
1861            write!(f, " }}")
1862        }
1863        Expression::PathData(data) => write!(f, "{data:?}"),
1864        Expression::EasingCurve(e) => write!(f, "{e:?}"),
1865        Expression::LinearGradient { angle, stops } => {
1866            write!(f, "@linear-gradient(")?;
1867            pretty_print(f, angle)?;
1868            for (c, s) in stops {
1869                write!(f, ", ")?;
1870                pretty_print(f, c)?;
1871                write!(f, "  ")?;
1872                pretty_print(f, s)?;
1873            }
1874            write!(f, ")")
1875        }
1876        Expression::RadialGradient { stops } => {
1877            write!(f, "@radial-gradient(circle")?;
1878            for (c, s) in stops {
1879                write!(f, ", ")?;
1880                pretty_print(f, c)?;
1881                write!(f, "  ")?;
1882                pretty_print(f, s)?;
1883            }
1884            write!(f, ")")
1885        }
1886        Expression::ConicGradient { from_angle, stops } => {
1887            write!(f, "@conic-gradient(from ")?;
1888            pretty_print(f, from_angle)?;
1889            for (c, s) in stops {
1890                write!(f, ", ")?;
1891                pretty_print(f, c)?;
1892                write!(f, " ")?;
1893                pretty_print(f, s)?;
1894            }
1895            write!(f, ")")
1896        }
1897        Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value) {
1898            Some(val) => write!(f, "{}.{}", e.enumeration.name, val),
1899            None => write!(f, "{}.{}", e.enumeration.name, e.value),
1900        },
1901        Expression::ReturnStatement(e) => {
1902            write!(f, "return ")?;
1903            e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(()))
1904        }
1905        Expression::LayoutCacheAccess {
1906            layout_cache_prop,
1907            index,
1908            repeater_index,
1909            entries_per_item,
1910        } => {
1911            if repeater_index.is_some() {
1912                write!(
1913                    f,
1914                    "{:?}[{:?}[{}] + $repeater_index * {}]",
1915                    layout_cache_prop, layout_cache_prop, index, entries_per_item
1916                )
1917            } else {
1918                write!(f, "{:?}[{}]", layout_cache_prop, index)
1919            }
1920        }
1921        Expression::OrganizeGridLayout(..) => write!(f, "organize_grid_layout(..)"),
1922        Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)"),
1923        Expression::ComputeGridLayoutInfo { .. } => write!(f, "grid_layout_info(..)"),
1924        Expression::SolveLayout(..) => write!(f, "solve_layout(..)"),
1925        Expression::SolveGridLayout { .. } => write!(f, "solve_grid_layout(..)"),
1926        Expression::MinMax { ty: _, op, lhs, rhs } => {
1927            match op {
1928                MinMaxOp::Min => write!(f, "min(")?,
1929                MinMaxOp::Max => write!(f, "max(")?,
1930            }
1931            pretty_print(f, lhs)?;
1932            write!(f, ", ")?;
1933            pretty_print(f, rhs)?;
1934            write!(f, ")")
1935        }
1936        Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>"),
1937        Expression::DebugHook { expression, id } => {
1938            write!(f, "debug-hook(")?;
1939            pretty_print(f, expression)?;
1940            write!(f, "\"{id}\")")
1941        }
1942    }
1943}