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