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, BuiltinStruct, EnumerationValue, Function, Keys, PropertyLookupMode, Struct,
7    Type,
8};
9use crate::layout::Orientation;
10use crate::lookup::LookupCtx;
11use crate::object_tree::*;
12use crate::parser::{NodeOrToken, SyntaxNode};
13use crate::symbol_counters::SymbolCounters;
14use crate::typeregister;
15use core::cell::RefCell;
16use smol_str::{SmolStr, format_smolstr};
17use std::cell::Cell;
18use std::collections::BTreeMap;
19use std::rc::{Rc, Weak};
20use std::sync::Arc;
21
22// FIXME remove the pub
23pub use crate::namedreference::NamedReference;
24pub use crate::passes::resolving;
25
26#[derive(Debug, Clone, PartialEq, Eq, strum::EnumString)]
27/// A function built into the run-time.
28/// Member functions of builtin elements bind to a variant with `#[slint(builtin_function(..))]`.
29pub enum BuiltinFunction {
30    GetWindowScaleFactor,
31    GetWindowDefaultFontSize,
32    AnimationTick,
33    Debug,
34    Mod,
35    Round,
36    Ceil,
37    Floor,
38    Abs,
39    Sqrt,
40    Cos,
41    Sin,
42    Tan,
43    ACos,
44    ASin,
45    ATan,
46    ATan2,
47    Log,
48    Ln,
49    Pow,
50    Exp,
51    ToFixed,
52    ToPrecision,
53    ToStringUnlocalized,
54    SetFocusItem,
55    ClearFocusItem,
56    ShowPopupWindow,
57    ClosePopupWindow,
58    /// Show a context popup menu.
59    /// Arguments are `(parent, menu_tree, position)`
60    ///
61    /// The first argument (parent) is a reference to the `ContextMenu` native item
62    /// The second argument (menu_tree) is a ElementReference to the root of the tree,
63    /// and in the LLR, a NumberLiteral to an index in  [`crate::llr::SubComponent::menu_item_trees`]
64    ShowPopupMenu,
65    /// Show a context popup menu from a list of entries.
66    /// Arguments are `(parent, entries, position)`
67    /// The entries argument is an array of MenuEntry
68    ShowPopupMenuInternal,
69    SetSelectionOffsets,
70    ItemFontMetrics,
71    /// the "42".to_float()
72    StringToFloat,
73    /// the "42".is_float()
74    StringIsFloat,
75    /// the "42".is_empty
76    StringIsEmpty,
77    /// the "42".length
78    StringCharacterCount,
79    StringToLowercase,
80    StringToUppercase,
81    StringStartsWith,
82    StringEndsWith,
83    StringReplaceAll,
84    KeysToString,
85    ColorRgbaStruct,
86    ColorHsvaStruct,
87    ColorOklchStruct,
88    ColorBrighter,
89    ColorDarker,
90    ColorTransparentize,
91    ColorMix,
92    ColorWithAlpha,
93    ImageSize,
94    ArrayLength,
95    ArrayPush,
96    ArrayRemove,
97    ArrayInsert,
98    ArrayAny,
99    ArrayAll,
100    ArrayFindIndex,
101    Rgb,
102    Hsv,
103    Oklch,
104    ColorScheme,
105    AccentColor,
106    SupportsNativeMenuBar,
107    /// Setup the menu bar
108    ///
109    /// arguments are: `(ref entries, ref sub-menu, ref activated, item_tree_root, no_native_menu_bar, <condition>)`
110    /// `item_tree_root` is a reference to the MenuItem tree root (just like in the [`Self::ShowPopupMenu`] call).
111    /// `no_native_menu_bar` is a boolean literal that is true when we shouldn't try to setup the native menu bar.
112    /// `condition` is an optional expression that is the expression to `if condition : MenuBar { ... }` for optional menu
113    SetupMenuBar,
114    /// Setup the menu of a `SystemTrayIcon`.
115    ///
116    /// Arguments are `(system_tray_ref, menu_tree_root)` where `menu_tree_root` is an ElementReference to the root of
117    /// the MenuItem tree, lowered to a NumberLiteral index into [`crate::llr::SubComponent::menu_item_trees`] in the LLR.
118    SetupSystemTrayIcon,
119    Use24HourFormat,
120    MonthDayCount,
121    MonthOffset,
122    FormatDate,
123    DateNow,
124    ValidDate,
125    ParseDate,
126    TextInputFocused,
127    SetTextInputFocused,
128    #[strum(disabled)]
129    ImplicitLayoutInfo(Orientation),
130    ItemAbsolutePosition,
131    RegisterCustomFontByPath,
132    RegisterCustomFontByMemory,
133    RegisterBitmapFont,
134    Translate,
135    UpdateTimers,
136    DetectOperatingSystem,
137    StartTimer,
138    StopTimer,
139    RestartTimer,
140    OpenUrl,
141    MacosBringAllWindowsToFront,
142    ParseMarkdown,
143    StringToStyledText,
144    /// Converts a color to a hex string wrapped in StyledText.
145    /// Used for `<font color='\{expr}'>` interpolation in `@markdown`,
146    /// because `parse_interpolated` takes `StyledText` arguments.
147    ColorToStyledText,
148    DecimalSeparator,
149    /// The window title when the application doesn't set one, see
150    /// `i_slint_core::window::default_window_title`
151    DefaultWindowTitle,
152    PathPointAt,
153    PathAngleAt,
154}
155
156#[derive(Debug, Clone)]
157/// A builtin function which is handled by the compiler pass
158///
159/// Builtin function expect their arguments in one and a specific type, so that's easier
160/// for the generator. Macro however can do some transformation on their argument.
161///
162pub enum BuiltinMacroFunction {
163    /// Transform `min(a, b, c, ..., z)` into a series of conditional expression and comparisons
164    Min,
165    /// Transform `max(a, b, c, ..., z)` into  a series of conditional expression and comparisons
166    Max,
167    /// Transforms `clamp(v, min, max)` into a series of min/max calls
168    Clamp,
169    /// Add the right conversion operations so that the return type is the same as the argument type
170    Mod,
171    /// Add the right conversion operations so that the return type is the same as the argument type
172    Abs,
173    /// Equivalent to `x < 0 ? -1 : 1`
174    Sign,
175    CubicBezier,
176    /// The argument can be r,g,b,a or r,g,b and they can be percentages or integer.
177    /// transform the argument so it is always rgb(r, g, b, a) with r, g, b between 0 and 255.
178    Rgb,
179    Hsv,
180    Oklch,
181    /// transform `debug(a, b, c)` into debug `a + " " + b + " " + c`
182    Debug,
183    ArrayPush,
184    ArrayRemove,
185    ArrayInsert,
186    /// Transforms `array.index-of(value)` into `array.find-index((x) => x == value)`
187    ArrayIndexOf,
188    CustomMouseCursor,
189    Spring,
190}
191
192macro_rules! declare_builtin_function_types {
193    ($( $Name:ident $(($Pattern:tt))? : ($( $Arg:expr ),*) -> $ReturnType:expr $(,)? )*) => {
194        #[allow(non_snake_case)]
195        pub struct BuiltinFunctionTypes {
196            $(pub $Name : Arc<Function>),*
197        }
198        impl BuiltinFunctionTypes {
199            pub fn new() -> Self {
200                Self {
201                    $($Name : Arc::new(Function{
202                        args: vec![$($Arg),*],
203                        return_type: $ReturnType,
204                        arg_names: Vec::new(),
205                    })),*
206                }
207            }
208
209            pub fn ty(&self, function: &BuiltinFunction) -> Arc<Function> {
210                match function {
211                    $(BuiltinFunction::$Name $(($Pattern))? => self.$Name.clone()),*
212                }
213            }
214        }
215    };
216}
217
218declare_builtin_function_types!(
219    GetWindowScaleFactor: () -> Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)]),
220    GetWindowDefaultFontSize: () -> Type::LogicalLength,
221    AnimationTick: () -> Type::Duration,
222    Debug: (Type::String) -> Type::Void,
223    Mod: (Type::Int32, Type::Int32) -> Type::Int32,
224    Round: (Type::Float32) -> Type::Int32,
225    Ceil: (Type::Float32) -> Type::Int32,
226    Floor: (Type::Float32) -> Type::Int32,
227    Sqrt: (Type::Float32) -> Type::Float32,
228    Abs: (Type::Float32) -> Type::Float32,
229    Cos: (Type::Angle) -> Type::Float32,
230    Sin: (Type::Angle) -> Type::Float32,
231    Tan: (Type::Angle) -> Type::Float32,
232    ACos: (Type::Float32) -> Type::Angle,
233    ASin: (Type::Float32) -> Type::Angle,
234    ATan: (Type::Float32) -> Type::Angle,
235    ATan2: (Type::Float32, Type::Float32) -> Type::Angle,
236    DecimalSeparator: () -> Type::String,
237    DefaultWindowTitle: () -> Type::String,
238    Log: (Type::Float32, Type::Float32) -> Type::Float32,
239    Ln: (Type::Float32) -> Type::Float32,
240    Pow: (Type::Float32, Type::Float32) -> Type::Float32,
241    Exp: (Type::Float32) -> Type::Float32,
242    ToFixed: (Type::Float32, Type::Int32) -> Type::String,
243    ToPrecision: (Type::Float32, Type::Int32) -> Type::String,
244    ToStringUnlocalized: (Type::Float32) -> Type::String,
245    SetFocusItem: (Type::ElementReference) -> Type::Void,
246    ClearFocusItem: (Type::ElementReference) -> Type::Void,
247    ShowPopupWindow: (Type::ElementReference) -> Type::Void,
248    ClosePopupWindow: (Type::ElementReference) -> Type::Void,
249    ShowPopupMenu: (Type::ElementReference, Type::ElementReference, typeregister::logical_point_type().into()) -> Type::Void,
250    ShowPopupMenuInternal: (Type::ElementReference, Type::Model, typeregister::logical_point_type().into()) -> Type::Void,
251    SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void,
252    ItemFontMetrics: (Type::ElementReference) -> typeregister::font_metrics_type(),
253    StringToFloat: (Type::String) -> Type::Float32,
254    StringIsFloat: (Type::String) -> Type::Bool,
255    StringIsEmpty: (Type::String) -> Type::Bool,
256    StringCharacterCount: (Type::String) -> Type::Int32,
257    StringToLowercase: (Type::String) -> Type::String,
258    StringToUppercase: (Type::String) -> Type::String,
259    StringStartsWith: (Type::String, Type::String) -> Type::Bool,
260    StringEndsWith: (Type::String, Type::String) -> Type::Bool,
261    StringReplaceAll: (Type::String, Type::String, Type::String) -> Type::String,
262    KeysToString: (Type::Keys) -> Type::String,
263    ImplicitLayoutInfo(..): (Type::ElementReference, Type::Float32) -> typeregister::layout_info_type().into(),
264    ColorRgbaStruct: (Type::Color) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
265            (SmolStr::new_static("red"), Type::Int32),
266            (SmolStr::new_static("green"), Type::Int32),
267            (SmolStr::new_static("blue"), Type::Int32),
268            (SmolStr::new_static("alpha"), Type::Int32),
269        ])
270        .collect(), BuiltinStruct::Color))),
271    ColorHsvaStruct: (Type::Color) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
272            (SmolStr::new_static("hue"), Type::Float32),
273            (SmolStr::new_static("saturation"), Type::Float32),
274            (SmolStr::new_static("value"), Type::Float32),
275            (SmolStr::new_static("alpha"), Type::Float32),
276        ])
277        .collect(), BuiltinStruct::Color))),
278    ColorOklchStruct: (Type::Color) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
279            (SmolStr::new_static("lightness"), Type::Float32),
280            (SmolStr::new_static("chroma"), Type::Float32),
281            (SmolStr::new_static("hue"), Type::Float32),
282            (SmolStr::new_static("alpha"), Type::Float32),
283        ])
284        .collect(), BuiltinStruct::Color))),
285    ColorBrighter: (Type::Brush, Type::Float32) -> Type::Brush,
286    ColorDarker: (Type::Brush, Type::Float32) -> Type::Brush,
287    ColorTransparentize: (Type::Brush, Type::Float32) -> Type::Brush,
288    ColorWithAlpha: (Type::Brush, Type::Float32) -> Type::Brush,
289    ColorMix: (Type::Color, Type::Color, Type::Float32) -> Type::Color,
290    ImageSize: (Type::Image) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
291            (SmolStr::new_static("width"), Type::Int32),
292            (SmolStr::new_static("height"), Type::Int32),
293        ])
294        .collect(), crate::langtype::BuiltinStruct::Size))),
295    ArrayLength: (Type::Model) -> Type::Int32,
296    // Using Type::InferredProperty as there is currently no valid type for the data argument.
297    ArrayPush: (Type::Model, Type::InferredProperty) -> Type::Void,
298    ArrayRemove: (Type::Model, Type::Int32) -> Type::Void,
299    ArrayInsert: (Type::Model, Type::Int32, Type::InferredProperty) -> Type::Void,
300    ArrayAny: (Type::Model, Type::Closure) -> Type::Bool,
301    ArrayAll: (Type::Model, Type::Closure) -> Type::Bool,
302    ArrayFindIndex: (Type::Model, Type::Closure) -> Type::Int32,
303    Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color,
304    Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
305    Oklch: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
306    ColorScheme: () -> Type::Enumeration(
307        typeregister::BUILTIN.enums.ColorScheme.clone(),
308    ),
309    AccentColor: () -> Type::Color,
310    SupportsNativeMenuBar: () -> Type::Bool,
311    // entries, sub-menu, activate. But the types here are not accurate.
312    SetupMenuBar: (Type::Model, typeregister::noarg_callback_type(), typeregister::noarg_callback_type()) -> Type::Void,
313    // (system_tray_ref, menu_tree_ref) — types not accurate (menu_tree becomes an index in LLR)
314    SetupSystemTrayIcon: (Type::ElementReference, Type::ElementReference) -> Type::Void,
315    MonthDayCount: (Type::Int32, Type::Int32) -> Type::Int32,
316    MonthOffset: (Type::Int32, Type::Int32) -> Type::Int32,
317    FormatDate: (Type::String, Type::Int32, Type::Int32, Type::Int32) -> Type::String,
318    TextInputFocused: () -> Type::Bool,
319    DateNow: () -> Type::Array(Arc::new(Type::Int32)),
320    ValidDate: (Type::String, Type::String) -> Type::Bool,
321    ParseDate: (Type::String, Type::String) -> Type::Array(Arc::new(Type::Int32)),
322    SetTextInputFocused: (Type::Bool) -> Type::Void,
323    ItemAbsolutePosition: (Type::ElementReference) -> typeregister::logical_point_type().into(),
324    RegisterCustomFontByPath: (Type::String) -> Type::Void,
325    RegisterCustomFontByMemory: (Type::Int32) -> Type::Void,
326    RegisterBitmapFont: (Type::Int32) -> Type::Void,
327    // original, context, domain, args
328    Translate: (Type::String, Type::String, Type::String, Type::Array(Type::String.into())) -> Type::String,
329    Use24HourFormat: () -> Type::Bool,
330    UpdateTimers: () -> Type::Void,
331    DetectOperatingSystem: () -> Type::Enumeration(
332        typeregister::BUILTIN.enums.OperatingSystemType.clone(),
333    ),
334    StartTimer: (Type::ElementReference) -> Type::Void,
335    StopTimer: (Type::ElementReference) -> Type::Void,
336    RestartTimer: (Type::ElementReference) -> Type::Void,
337    ParseMarkdown: (Type::String, Type::Array(Type::StyledText.into())) -> Type::StyledText,
338    StringToStyledText: (Type::String) -> Type::StyledText,
339    ColorToStyledText: (Type::Color) -> Type::StyledText
340    OpenUrl: (Type::String) -> Type::Bool,
341    MacosBringAllWindowsToFront: () -> Type::Void,
342    PathPointAt: (Type::ElementReference, Type::Float32) -> typeregister::logical_point_type().into(),
343    PathAngleAt: (Type::ElementReference, Type::Float32) -> Type::Angle,
344);
345
346impl Default for BuiltinFunctionTypes {
347    fn default() -> Self {
348        Self::new()
349    }
350}
351
352impl BuiltinFunction {
353    pub fn ty(&self) -> Arc<Function> {
354        static TYPES: std::sync::LazyLock<BuiltinFunctionTypes> =
355            std::sync::LazyLock::new(BuiltinFunctionTypes::new);
356        TYPES.ty(self)
357    }
358
359    /// It is const if the return value only depends on its argument and has no side effect
360    fn is_const(&self, global_analysis: Option<&crate::passes::GlobalAnalysis>) -> bool {
361        match self {
362            BuiltinFunction::GetWindowScaleFactor => {
363                global_analysis.is_some_and(|x| x.const_scale_factor.is_some())
364            }
365            BuiltinFunction::GetWindowDefaultFontSize => {
366                global_analysis.is_some_and(|x| x.default_font_size.is_const())
367            }
368            BuiltinFunction::AnimationTick => false,
369            BuiltinFunction::ColorScheme => false,
370            BuiltinFunction::AccentColor => false,
371            BuiltinFunction::SupportsNativeMenuBar => false,
372            BuiltinFunction::SetupMenuBar => false,
373            BuiltinFunction::SetupSystemTrayIcon => false,
374            BuiltinFunction::MonthDayCount => false,
375            BuiltinFunction::MonthOffset => false,
376            BuiltinFunction::FormatDate => false,
377            BuiltinFunction::DateNow => false,
378            BuiltinFunction::ValidDate => false,
379            BuiltinFunction::ParseDate => false,
380            BuiltinFunction::DecimalSeparator => false,
381            BuiltinFunction::DefaultWindowTitle => false,
382            // Even if it is not pure, we optimize it away anyway
383            BuiltinFunction::Debug => true,
384            BuiltinFunction::Mod
385            | BuiltinFunction::Round
386            | BuiltinFunction::Ceil
387            | BuiltinFunction::Floor
388            | BuiltinFunction::Abs
389            | BuiltinFunction::Sqrt
390            | BuiltinFunction::Cos
391            | BuiltinFunction::Sin
392            | BuiltinFunction::Tan
393            | BuiltinFunction::ACos
394            | BuiltinFunction::ASin
395            | BuiltinFunction::Log
396            | BuiltinFunction::Ln
397            | BuiltinFunction::Pow
398            | BuiltinFunction::Exp
399            | BuiltinFunction::ATan
400            | BuiltinFunction::ATan2
401            | BuiltinFunction::ToStringUnlocalized => true,
402            // The result depends on the locale's decimal separator, like DecimalSeparator.
403            // The constant propagation folds the locale-independent cases and promotes
404            // their binding back to constant.
405            BuiltinFunction::ToFixed
406            | BuiltinFunction::ToPrecision
407            | BuiltinFunction::StringToFloat
408            | BuiltinFunction::StringIsFloat => false,
409            BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
410            BuiltinFunction::ShowPopupWindow
411            | BuiltinFunction::ClosePopupWindow
412            | BuiltinFunction::ShowPopupMenu
413            | BuiltinFunction::ShowPopupMenuInternal => false,
414            BuiltinFunction::SetSelectionOffsets => false,
415            BuiltinFunction::ItemFontMetrics => false, // depends also on Window's font properties
416            BuiltinFunction::StringIsEmpty
417            | BuiltinFunction::StringCharacterCount
418            | BuiltinFunction::StringToLowercase
419            | BuiltinFunction::StringToUppercase
420            | BuiltinFunction::StringStartsWith
421            | BuiltinFunction::StringEndsWith
422            | BuiltinFunction::StringReplaceAll
423            | BuiltinFunction::KeysToString => true,
424            BuiltinFunction::ColorRgbaStruct
425            | BuiltinFunction::ColorHsvaStruct
426            | BuiltinFunction::ColorOklchStruct
427            | BuiltinFunction::ColorBrighter
428            | BuiltinFunction::ColorDarker
429            | BuiltinFunction::ColorTransparentize
430            | BuiltinFunction::ColorMix
431            | BuiltinFunction::ColorWithAlpha => true,
432            // On the web, the browser loads images asynchronously, so the size is initially 0/0
433            // and updates once the image is loaded. Calls to this function must stay within a
434            // binding so that the property notification kicks in when the code may run on the web.
435            BuiltinFunction::ImageSize => global_analysis.is_some_and(|x| x.const_image_sizes),
436            BuiltinFunction::ArrayLength => true,
437            BuiltinFunction::ArrayPush
438            | BuiltinFunction::ArrayRemove
439            | BuiltinFunction::ArrayInsert => false,
440            BuiltinFunction::Rgb => true,
441            BuiltinFunction::Hsv => true,
442            BuiltinFunction::Oklch => true,
443            BuiltinFunction::SetTextInputFocused => false,
444            BuiltinFunction::TextInputFocused => false,
445            BuiltinFunction::ImplicitLayoutInfo(_) => false,
446            BuiltinFunction::ItemAbsolutePosition => true,
447            BuiltinFunction::RegisterCustomFontByPath
448            | BuiltinFunction::RegisterCustomFontByMemory
449            | BuiltinFunction::RegisterBitmapFont => false,
450            BuiltinFunction::Translate => false,
451            BuiltinFunction::Use24HourFormat => false,
452            BuiltinFunction::UpdateTimers => false,
453            BuiltinFunction::DetectOperatingSystem => true,
454            BuiltinFunction::StartTimer => false,
455            BuiltinFunction::StopTimer => false,
456            BuiltinFunction::RestartTimer => false,
457            BuiltinFunction::ParseMarkdown => false,
458            BuiltinFunction::StringToStyledText => true,
459            BuiltinFunction::ColorToStyledText => true,
460            BuiltinFunction::OpenUrl => false,
461            BuiltinFunction::MacosBringAllWindowsToFront => false,
462            BuiltinFunction::PathPointAt => true,
463            BuiltinFunction::PathAngleAt => true,
464            BuiltinFunction::ArrayAny
465            | BuiltinFunction::ArrayAll
466            | BuiltinFunction::ArrayFindIndex => true,
467        }
468    }
469
470    // It is pure if it has no side effect
471    pub fn is_pure(&self) -> bool {
472        match self {
473            BuiltinFunction::GetWindowScaleFactor => true,
474            BuiltinFunction::GetWindowDefaultFontSize => true,
475            BuiltinFunction::AnimationTick => true,
476            BuiltinFunction::ColorScheme => true,
477            BuiltinFunction::AccentColor => true,
478            BuiltinFunction::SupportsNativeMenuBar => true,
479            BuiltinFunction::SetupMenuBar => false,
480            BuiltinFunction::SetupSystemTrayIcon => false,
481            BuiltinFunction::MonthDayCount => true,
482            BuiltinFunction::MonthOffset => true,
483            BuiltinFunction::FormatDate => true,
484            BuiltinFunction::DateNow => true,
485            BuiltinFunction::ValidDate => true,
486            BuiltinFunction::ParseDate => true,
487            BuiltinFunction::DecimalSeparator => true,
488            BuiltinFunction::DefaultWindowTitle => true,
489            // Even if it has technically side effect, we still consider it as pure for our purpose
490            BuiltinFunction::Debug => true,
491            BuiltinFunction::Mod
492            | BuiltinFunction::Round
493            | BuiltinFunction::Ceil
494            | BuiltinFunction::Floor
495            | BuiltinFunction::Abs
496            | BuiltinFunction::Sqrt
497            | BuiltinFunction::Cos
498            | BuiltinFunction::Sin
499            | BuiltinFunction::Tan
500            | BuiltinFunction::ACos
501            | BuiltinFunction::ASin
502            | BuiltinFunction::Log
503            | BuiltinFunction::Ln
504            | BuiltinFunction::Pow
505            | BuiltinFunction::Exp
506            | BuiltinFunction::ATan
507            | BuiltinFunction::ATan2
508            | BuiltinFunction::ToFixed
509            | BuiltinFunction::ToPrecision
510            | BuiltinFunction::ToStringUnlocalized => true,
511            BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
512            BuiltinFunction::ShowPopupWindow
513            | BuiltinFunction::ClosePopupWindow
514            | BuiltinFunction::ShowPopupMenu
515            | BuiltinFunction::ShowPopupMenuInternal => false,
516            BuiltinFunction::SetSelectionOffsets => false,
517            BuiltinFunction::ItemFontMetrics => true,
518            BuiltinFunction::StringToFloat
519            | BuiltinFunction::StringIsFloat
520            | BuiltinFunction::StringIsEmpty
521            | BuiltinFunction::StringCharacterCount
522            | BuiltinFunction::StringToLowercase
523            | BuiltinFunction::StringToUppercase
524            | BuiltinFunction::StringStartsWith
525            | BuiltinFunction::StringEndsWith
526            | BuiltinFunction::StringReplaceAll
527            | BuiltinFunction::KeysToString => true,
528            BuiltinFunction::ColorRgbaStruct
529            | BuiltinFunction::ColorHsvaStruct
530            | BuiltinFunction::ColorOklchStruct
531            | BuiltinFunction::ColorBrighter
532            | BuiltinFunction::ColorDarker
533            | BuiltinFunction::ColorTransparentize
534            | BuiltinFunction::ColorMix
535            | BuiltinFunction::ColorWithAlpha => true,
536            BuiltinFunction::ImageSize => true,
537            BuiltinFunction::ArrayLength => true,
538            BuiltinFunction::ArrayPush
539            | BuiltinFunction::ArrayRemove
540            | BuiltinFunction::ArrayInsert => false,
541            BuiltinFunction::Rgb => true,
542            BuiltinFunction::Hsv => true,
543            BuiltinFunction::Oklch => true,
544            BuiltinFunction::ImplicitLayoutInfo(_) => true,
545            BuiltinFunction::ItemAbsolutePosition => true,
546            BuiltinFunction::SetTextInputFocused => false,
547            BuiltinFunction::TextInputFocused => true,
548            BuiltinFunction::RegisterCustomFontByPath
549            | BuiltinFunction::RegisterCustomFontByMemory
550            | BuiltinFunction::RegisterBitmapFont => false,
551            BuiltinFunction::Translate => true,
552            BuiltinFunction::Use24HourFormat => true,
553            BuiltinFunction::UpdateTimers => false,
554            BuiltinFunction::DetectOperatingSystem => true,
555            BuiltinFunction::StartTimer => false,
556            BuiltinFunction::StopTimer => false,
557            BuiltinFunction::RestartTimer => false,
558            BuiltinFunction::ParseMarkdown => true,
559            BuiltinFunction::StringToStyledText => true,
560            BuiltinFunction::ColorToStyledText => true,
561            BuiltinFunction::OpenUrl => false,
562            BuiltinFunction::MacosBringAllWindowsToFront => false,
563            BuiltinFunction::PathPointAt => true,
564            BuiltinFunction::PathAngleAt => true,
565            BuiltinFunction::ArrayAny
566            | BuiltinFunction::ArrayAll
567            | BuiltinFunction::ArrayFindIndex => true,
568        }
569    }
570}
571
572/// The base of a Expression::FunctionCall
573#[derive(Debug, Clone)]
574pub enum Callable {
575    Callback(NamedReference),
576    Function(NamedReference),
577    Builtin(BuiltinFunction),
578}
579impl Callable {
580    pub fn ty(&self) -> Type {
581        match self {
582            Callable::Callback(nr) => nr.ty(),
583            Callable::Function(nr) => nr.ty(),
584            Callable::Builtin(b) => Type::Function(b.ty()),
585        }
586    }
587}
588impl From<BuiltinFunction> for Callable {
589    fn from(function: BuiltinFunction) -> Self {
590        Self::Builtin(function)
591    }
592}
593
594#[derive(Debug, Clone, Eq, PartialEq)]
595pub enum OperatorClass {
596    ComparisonOp,
597    LogicalOp,
598    ArithmeticOp,
599}
600
601/// the class of for this (binary) operation
602pub fn operator_class(op: char) -> OperatorClass {
603    match op {
604        '=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp,
605        '&' | '|' => OperatorClass::LogicalOp,
606        '+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp,
607        _ => panic!("Invalid operator {op:?}"),
608    }
609}
610
611macro_rules! declare_units {
612    // A unit written without a conversion is already its type's canonical unit.
613    (@normalize $value:ident, $ident:ident) => { ($value, Unit::$ident) };
614    // Otherwise scale by the factor and switch to the named canonical unit.
615    (@normalize $value:ident, $ident:ident, $canon:ident, $factor:expr) => {
616        ($value * ($factor as f64), Unit::$canon)
617    };
618    ($( $(#[$m:meta])* $ident:ident = $string:literal $(-> $canon:ident * $factor:expr)? ,)*) => {
619        /// A unit as written after a number in the source (`px`, `cm`, `grad`, ...).
620        ///
621        /// These are all the units a user can type. A literal is normalized to the
622        /// canonical [`Unit`] of its type the moment it enters the expression tree, so
623        /// only the parser and tooling ever handle a `WrittenUnit`.
624        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
625        pub enum WrittenUnit {
626            $($(#[$m])* $ident,)*
627        }
628
629        impl std::fmt::Display for WrittenUnit {
630            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631                match self {
632                    $(Self::$ident => write!(f, $string), )*
633                }
634            }
635        }
636
637        impl std::str::FromStr for WrittenUnit {
638            type Err = ();
639            fn from_str(s: &str) -> Result<Self, Self::Err> {
640                match s {
641                    $($string => Ok(Self::$ident), )*
642                    _ => Err(())
643                }
644            }
645        }
646
647        impl WrittenUnit {
648            /// Scale `value`, written in this surface unit, to the value and canonical
649            /// [`Unit`] a `NumberLiteral` stores. The scale and the unit come out
650            /// together so they can't drift apart.
651            pub fn normalize(self, value: f64) -> (f64, Unit) {
652                match self {
653                    $(Self::$ident => declare_units!(@normalize value, $ident $(, $canon, $factor)?), )*
654                }
655            }
656        }
657    };
658}
659
660declare_units! {
661    /// No unit was given
662    None = "",
663    /// Percent value
664    Percent = "%",
665
666    // Lengths or Coord
667
668    /// Physical pixels
669    Phx = "phx",
670    /// Logical pixels
671    Px = "px",
672    /// Centimeters
673    Cm = "cm" -> Px * 37.8,
674    /// Millimeters
675    Mm = "mm" -> Px * 3.78,
676    /// inches
677    In = "in" -> Px * 96,
678    /// Points
679    Pt = "pt" -> Px * 96./72.,
680    /// Logical pixels multiplied with the window's default-font-size
681    Rem = "rem",
682
683    // durations
684
685    /// Seconds
686    S = "s" -> Ms * 1000,
687    /// Milliseconds
688    Ms = "ms",
689
690    // angles
691
692    /// Degree
693    Deg = "deg",
694    /// Gradians
695    Grad = "grad" -> Deg * 360./180.,
696    /// Turns
697    Turn = "turn" -> Deg * 360.,
698    /// Radians
699    Rad = "rad" -> Deg * 360./std::f32::consts::TAU,
700}
701
702/// The unit a [`Expression::NumberLiteral`] carries: always the canonical unit of
703/// its type, so the stored value is already scaled and needs no further
704/// conversion. The units a user can type (`cm`, `pt`, `grad`, ...) are
705/// [`WrittenUnit`] and are normalized to one of these on the way in.
706#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
707pub enum Unit {
708    /// Dimension-less (`float`, `int`)
709    #[default]
710    None,
711    /// Percent
712    Percent,
713    /// Physical pixels
714    Phx,
715    /// Logical pixels
716    Px,
717    /// Logical pixels multiplied with the window's default-font-size
718    Rem,
719    /// Milliseconds
720    Ms,
721    /// Degrees
722    Deg,
723}
724
725impl Unit {
726    pub fn ty(self) -> Type {
727        match self {
728            Unit::None => Type::Float32,
729            Unit::Percent => Type::Percent,
730            Unit::Px => Type::LogicalLength,
731            Unit::Phx => Type::PhysicalLength,
732            Unit::Rem => Type::Rem,
733            Unit::Ms => Type::Duration,
734            Unit::Deg => Type::Angle,
735        }
736    }
737}
738
739impl std::fmt::Display for Unit {
740    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
741        let s = match self {
742            Unit::None => "",
743            Unit::Percent => "%",
744            Unit::Px => "px",
745            Unit::Phx => "phx",
746            Unit::Rem => "rem",
747            Unit::Ms => "ms",
748            Unit::Deg => "deg",
749        };
750        write!(f, "{s}")
751    }
752}
753
754#[derive(Debug, Clone, Copy)]
755pub enum MinMaxOp {
756    Min,
757    Max,
758}
759
760/// The three places a `.slint` file writes a conditional.
761#[derive(Debug, Clone)]
762pub enum ConditionLocation {
763    /// The `?` of a `?:`.
764    Question(SourceLocation),
765    /// A state's name, and the `when` of its condition.
766    StateSelection { name: SourceLocation, when: SourceLocation },
767    /// The property a state changes.
768    StateChange(SourceLocation),
769}
770
771/// The Expression is held by properties, so it should not hold any strong references to node from the object_tree
772#[derive(Debug, Clone, Default)]
773pub enum Expression {
774    /// Something went wrong (and an error will be reported)
775    #[default]
776    Invalid,
777    /// We haven't done the lookup yet
778    Uncompiled(SyntaxNode),
779
780    /// A string literal. The .0 is the content of the string, without the quotes
781    StringLiteral(SmolStr),
782    /// Number
783    NumberLiteral(f64, Unit),
784    /// Bool
785    BoolLiteral(bool),
786
787    /// Reference to the property
788    PropertyReference(NamedReference),
789
790    /// A reference to a specific element. This isn't possible to create in .slint syntax itself, but intermediate passes may generate this
791    /// type of expression.
792    ElementReference(Weak<RefCell<Element>>),
793
794    /// Reference to the index variable of a repeater
795    ///
796    /// Example: `idx`  in `for xxx[idx] in ...`.   The element is the reference to the
797    /// element that is repeated
798    RepeaterIndexReference {
799        element: Weak<RefCell<Element>>,
800    },
801
802    /// Reference to the model variable of a repeater
803    ///
804    /// Example: `xxx`  in `for xxx[idx] in ...`.   The element is the reference to the
805    /// element that is repeated
806    RepeaterModelReference {
807        element: Weak<RefCell<Element>>,
808    },
809
810    /// Reference the parameter at the given index of the current function.
811    FunctionParameterReference {
812        index: usize,
813        ty: Type,
814    },
815
816    /// Should be directly within a CodeBlock expression, and store the value of the expression in a local variable
817    StoreLocalVariable {
818        name: SmolStr,
819        value: Box<Expression>,
820    },
821
822    /// a reference to the local variable with the given name. The type system should ensure that a variable has been stored
823    /// with this name and this type before in one of the statement of an enclosing codeblock
824    ReadLocalVariable {
825        name: SmolStr,
826        ty: Type,
827    },
828
829    /// Access to a field of the given name within a struct.
830    StructFieldAccess {
831        /// This expression should have [`Type::Struct`] type
832        base: Box<Expression>,
833        name: SmolStr,
834    },
835
836    /// Access to a index within an array.
837    ArrayIndex {
838        /// This expression should have [`Type::Array`] type
839        array: Box<Expression>,
840        index: Box<Expression>,
841    },
842
843    /// Cast an expression to the given type
844    Cast {
845        from: Box<Expression>,
846        to: Type,
847    },
848
849    /// a code block with different expression
850    CodeBlock(Vec<Expression>),
851
852    /// A function call
853    FunctionCall {
854        function: Callable,
855        arguments: Vec<Expression>,
856        source_location: Option<SourceLocation>,
857    },
858
859    /// A SelfAssignment or an Assignment.  When op is '=' this is a simple assignment.
860    SelfAssignment {
861        lhs: Box<Expression>,
862        rhs: Box<Expression>,
863        /// '+', '-', '/', '*', or '='
864        op: char,
865        node: Option<NodeOrToken>,
866    },
867
868    BinaryExpression {
869        lhs: Box<Expression>,
870        rhs: Box<Expression>,
871        /// '+', '-', '/', '*', '=', '!', '<', '>', '≤', '≥', '&', '|'
872        op: char,
873        /// The operator token, when written in the source.
874        source_location: Option<SourceLocation>,
875    },
876
877    UnaryOp {
878        sub: Box<Expression>,
879        /// '+', '-', '!'
880        op: char,
881    },
882
883    ImageReference {
884        resource_ref: ImageReference,
885        source_location: Option<SourceLocation>,
886        nine_slice: Option<[u16; 4]>,
887    },
888
889    Condition {
890        condition: Box<Expression>,
891        true_expr: Box<Expression>,
892        false_expr: Box<Expression>,
893        /// Where the source writes the conditional, when it writes one.
894        source_location: Option<ConditionLocation>,
895    },
896
897    Array {
898        element_ty: Type,
899        values: Vec<Expression>,
900    },
901    Struct {
902        ty: Arc<Struct>,
903        values: BTreeMap<SmolStr, Expression>,
904    },
905
906    PathData(Path),
907
908    EasingCurve(EasingCurve),
909
910    EmptyDataTransfer,
911
912    MouseCursor(MouseCursorInner),
913
914    LinearGradient {
915        angle: Box<Expression>,
916        /// First expression in the tuple is a color, second expression is the stop position
917        stops: Vec<(Expression, Expression)>,
918    },
919
920    RadialGradient {
921        /// Explicit gradient center in the element's local coordinate space (`at <x> <y>`).
922        /// `None` means use the element's bbox centre.
923        center: Option<(Box<Expression>, Box<Expression>)>,
924        /// Explicit radius in the element's local coordinate space (`circle <r>`).
925        /// `None` means use the element's bbox half-diagonal.
926        radius: Option<Box<Expression>>,
927        /// First expression in the tuple is a color, second expression is the stop position
928        stops: Vec<(Expression, Expression)>,
929    },
930
931    ConicGradient {
932        /// The starting angle (rotation) of the gradient, corresponding to CSS `from <angle>`
933        from_angle: Box<Expression>,
934        /// Explicit gradient center in the element's local coordinate space (`at <x> <y>`).
935        /// `None` means use the element's bbox centre.
936        center: Option<(Box<Expression>, Box<Expression>)>,
937        /// First expression in the tuple is a color, second expression is the stop angle
938        stops: Vec<(Expression, Expression)>,
939    },
940
941    EnumerationValue(EnumerationValue),
942
943    Keys(Keys),
944
945    ReturnStatement(Option<Box<Expression>>),
946
947    /// Standard cache access (see docs/development/layout-system.md)
948    LayoutCacheAccess {
949        /// This property holds an array of entries
950        layout_cache_prop: NamedReference,
951        /// The index into that array. If repeater_index is None, then the code will be `layout_cache_prop[index]`
952        index: usize,
953        /// When set, this is the index within a repeater, and the index is then the location of another offset.
954        /// The code will be `layout_cache_prop[layout_cache_prop[index] + repeater_index * entries_per_item]`
955        /// Not used by GridLayout (see GridRepeaterCacheAccess)
956        repeater_index: Option<Box<Expression>>,
957        /// The number of entries to skip per repeater iteration:
958        /// 2 for pos+size, 4 for flex x+y+w+, 4 for grid organized data
959        /// This is only used when repeater_index is set
960        entries_per_item: usize,
961    },
962
963    /// Two-level indirection for grid layouts with repeaters (see docs/development/layout-system.md).
964    GridRepeaterCacheAccess {
965        /// This property holds an array of entries
966        layout_cache_prop: NamedReference,
967        /// The index of the jump cell
968        index: usize,
969        /// The outer repeater index.
970        repeater_index: Box<Expression>,
971        /// Total cache entries per outer row (= step * entries_per_item).
972        /// A compile-time literal for static rows; a runtime cache read for rows
973        /// that contain inner repeaters (see lower_layout.rs for construction details).
974        stride: Box<Expression>,
975        /// Offset within a row's data block (e.g. k*2 for pos, k*2+1 for size).
976        child_offset: usize,
977        /// For nested repeaters: the inner repeater index.
978        inner_repeater_index: Option<Box<Expression>>,
979        /// For nested repeaters: stride per inner repeater iteration (2 for coordinate cache, 4 for organized data).
980        entries_per_item: usize,
981    },
982
983    /// Organize a grid layout, i.e. decide what goes where
984    OrganizeGridLayout(crate::layout::GridLayout),
985
986    /// Compute the LayoutInfo for the given box layout.
987    /// The orientation is the orientation of the cache, not the orientation of the layout.
988    ComputeBoxLayoutInfo {
989        layout: crate::layout::BoxLayout,
990        orientation: crate::layout::Orientation,
991        /// only set in `layoutinfo-v-with-constraint`
992        cross_axis_size: Option<Box<Expression>>,
993    },
994    ComputeGridLayoutInfo {
995        layout_organized_data_prop: NamedReference,
996        layout: crate::layout::GridLayout,
997        orientation: crate::layout::Orientation,
998        /// only set in `layoutinfo-v-with-constraint`
999        cross_axis_size: Option<Box<Expression>>,
1000    },
1001    /// Determine the coordinates of the items in the given orientation.
1002    SolveBoxLayout(crate::layout::BoxLayout, crate::layout::Orientation),
1003    SolveGridLayout {
1004        layout_organized_data_prop: NamedReference,
1005        layout: crate::layout::GridLayout,
1006        orientation: crate::layout::Orientation,
1007    },
1008    /// Solve a FlexboxLayout - returns positions for all items (x, y, width, height per item)
1009    SolveFlexboxLayout(crate::layout::FlexboxLayout),
1010    /// Compute the LayoutInfo for the given FlexboxLayout.
1011    ComputeFlexboxLayoutInfo {
1012        layout: crate::layout::FlexboxLayout,
1013        orientation: crate::layout::Orientation,
1014        /// The width parameter in `layoutinfo-v-with-constraint`, the flex's
1015        /// own height in `layoutinfo-h-at-own-height`
1016        cross_axis_size: Option<Box<Expression>>,
1017    },
1018
1019    MinMax {
1020        ty: Type,
1021        op: MinMaxOp,
1022        lhs: Box<Expression>,
1023        rhs: Box<Expression>,
1024    },
1025
1026    DebugHook {
1027        expression: Box<Expression>,
1028        id: SmolStr,
1029        /// True if this hook was materialized for a property that had no binding in the source.
1030        /// Passes should treat a synthetic hook as "no binding" — the same as `Expression::Invalid`.
1031        synthetic: bool,
1032    },
1033
1034    EmptyComponentFactory,
1035
1036    Closure {
1037        arg_name: SmolStr,
1038        expression: Box<Expression>,
1039    },
1040}
1041
1042impl Expression {
1043    /// Return the type of this property
1044    pub fn ty(&self) -> Type {
1045        match self {
1046            Expression::Invalid => Type::Invalid,
1047            Expression::Uncompiled(_) => Type::Invalid,
1048            Expression::StringLiteral(_) => Type::String,
1049            Expression::NumberLiteral(_, unit) => unit.ty(),
1050            Expression::BoolLiteral(_) => Type::Bool,
1051            Expression::PropertyReference(nr) => nr.ty(),
1052            Expression::ElementReference(_) => Type::ElementReference,
1053            Expression::RepeaterIndexReference { .. } => Type::Int32,
1054            Expression::RepeaterModelReference { element } => element
1055                .upgrade()
1056                .unwrap()
1057                .borrow()
1058                .repeated
1059                .as_ref()
1060                .map_or(Type::Invalid, |e| model_inner_type(&e.model)),
1061            Expression::FunctionParameterReference { ty, .. } => ty.clone(),
1062            Expression::StructFieldAccess { base, name } => match base.ty() {
1063                Type::Struct(s) => s.fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone(),
1064                _ => Type::Invalid,
1065            },
1066            Expression::ArrayIndex { array, .. } => match array.ty() {
1067                Type::Array(ty) => (*ty).clone(),
1068                _ => Type::Invalid,
1069            },
1070            Expression::Cast { to, .. } => to.clone(),
1071            Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
1072            Expression::FunctionCall { function, .. } => match function.ty() {
1073                Type::Function(f) | Type::Callback(f) => f.return_type.clone(),
1074                _ => Type::Invalid,
1075            },
1076            Expression::SelfAssignment { .. } => Type::Void,
1077            Expression::ImageReference { .. } => Type::Image,
1078            Expression::Condition { condition: _, true_expr, false_expr, .. } => {
1079                let true_type = true_expr.ty();
1080                let false_type = false_expr.ty();
1081                if true_type == false_type {
1082                    true_type
1083                } else if true_type == Type::Invalid {
1084                    false_type
1085                } else if false_type == Type::Invalid {
1086                    true_type
1087                } else {
1088                    Type::Void
1089                }
1090            }
1091            Expression::BinaryExpression { op, lhs, rhs, .. } => {
1092                if operator_class(*op) != OperatorClass::ArithmeticOp {
1093                    Type::Bool
1094                } else if *op == '+' || *op == '-' {
1095                    let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty());
1096                    if rhs_ty == lhs_ty { rhs_ty } else { Type::Invalid }
1097                } else {
1098                    debug_assert!(*op == '*' || *op == '/');
1099                    let unit_vec = |ty| {
1100                        if let Type::UnitProduct(v) = ty {
1101                            v
1102                        } else if let Some(u) = ty.default_unit() {
1103                            vec![(u, 1)]
1104                        } else {
1105                            Vec::new()
1106                        }
1107                    };
1108                    let mut l_units = unit_vec(lhs.ty());
1109                    let mut r_units = unit_vec(rhs.ty());
1110                    if *op == '/' {
1111                        for (_, power) in &mut r_units {
1112                            *power = -*power;
1113                        }
1114                    }
1115                    for (unit, power) in r_units {
1116                        if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) {
1117                            *p += power;
1118                        } else {
1119                            l_units.push((unit, power));
1120                        }
1121                    }
1122
1123                    // normalize the vector by removing empty and sorting
1124                    l_units.retain(|(_, p)| *p != 0);
1125                    l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) {
1126                        std::cmp::Ordering::Equal => u1.cmp(u2),
1127                        x => x,
1128                    });
1129
1130                    if l_units.is_empty() {
1131                        Type::Float32
1132                    } else if l_units.len() == 1 && l_units[0].1 == 1 {
1133                        l_units[0].0.ty()
1134                    } else {
1135                        Type::UnitProduct(l_units)
1136                    }
1137                }
1138            }
1139            Expression::UnaryOp { sub, .. } => sub.ty(),
1140            Expression::Array { element_ty, .. } => Type::Array(Arc::new(element_ty.clone())),
1141            Expression::Struct { ty, .. } => ty.clone().into(),
1142            Expression::PathData { .. } => Type::PathData,
1143            Expression::EmptyDataTransfer => Type::DataTransfer,
1144            Expression::StoreLocalVariable { .. } => Type::Void,
1145            Expression::ReadLocalVariable { ty, .. } => ty.clone(),
1146            Expression::EasingCurve(_) => Type::Easing,
1147            Expression::MouseCursor(_) => Type::MouseCursor,
1148            Expression::LinearGradient { .. } => Type::Brush,
1149            Expression::RadialGradient { .. } => Type::Brush,
1150            Expression::ConicGradient { .. } => Type::Brush,
1151            Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
1152            Expression::Keys(_) => Type::Keys,
1153            // invalid because the expression is unreachable
1154            Expression::ReturnStatement(_) => Type::Invalid,
1155            Expression::LayoutCacheAccess { .. } => Type::LogicalLength,
1156            Expression::GridRepeaterCacheAccess { .. } => Type::LogicalLength,
1157            Expression::OrganizeGridLayout(..) => Type::ArrayOfU16,
1158            Expression::ComputeBoxLayoutInfo { .. } => typeregister::layout_info_type().into(),
1159            Expression::ComputeGridLayoutInfo { .. } => typeregister::layout_info_type().into(),
1160            Expression::SolveBoxLayout(..) => Type::LayoutCache,
1161            Expression::SolveGridLayout { .. } => Type::LayoutCache,
1162            Expression::SolveFlexboxLayout(..) => Type::LayoutCache,
1163            Expression::ComputeFlexboxLayoutInfo { .. } => typeregister::layout_info_type().into(),
1164            Expression::MinMax { ty, .. } => ty.clone(),
1165            Expression::EmptyComponentFactory => Type::ComponentFactory,
1166            Expression::DebugHook { expression, .. } => expression.ty(),
1167            Expression::Closure { .. } => Type::Closure,
1168        }
1169    }
1170
1171    /// Call the visitor for each sub-expression.  (note: this function does not recurse)
1172    pub fn visit(&self, mut visitor: impl FnMut(&Self)) {
1173        self.visit_dyn(&mut visitor)
1174    }
1175
1176    fn visit_dyn(&self, visitor: &mut dyn FnMut(&Self)) {
1177        match self {
1178            Expression::Invalid => {}
1179            Expression::Uncompiled(_) => {}
1180            Expression::StringLiteral(_) => {}
1181            Expression::NumberLiteral(_, _) => {}
1182            Expression::BoolLiteral(_) => {}
1183            Expression::PropertyReference { .. } => {}
1184            Expression::FunctionParameterReference { .. } => {}
1185            Expression::ElementReference(_) => {}
1186            Expression::StructFieldAccess { base, .. } => visitor(base),
1187            Expression::ArrayIndex { array, index } => {
1188                visitor(array);
1189                visitor(index);
1190            }
1191            Expression::RepeaterIndexReference { .. } => {}
1192            Expression::RepeaterModelReference { .. } => {}
1193            Expression::Cast { from, .. } => visitor(from),
1194            Expression::CodeBlock(sub) => {
1195                sub.iter().for_each(visitor);
1196            }
1197            Expression::FunctionCall { function: _, arguments, source_location: _ } => {
1198                arguments.iter().for_each(visitor);
1199            }
1200            Expression::SelfAssignment { lhs, rhs, .. } => {
1201                visitor(lhs);
1202                visitor(rhs);
1203            }
1204            Expression::ImageReference { .. } => {}
1205            Expression::Condition { condition, true_expr, false_expr, .. } => {
1206                visitor(condition);
1207                visitor(true_expr);
1208                visitor(false_expr);
1209            }
1210            Expression::BinaryExpression { lhs, rhs, .. } => {
1211                visitor(lhs);
1212                visitor(rhs);
1213            }
1214            Expression::UnaryOp { sub, .. } => visitor(sub),
1215            Expression::Array { values, .. } => {
1216                for x in values {
1217                    visitor(x);
1218                }
1219            }
1220            Expression::Struct { values, .. } => {
1221                for x in values.values() {
1222                    visitor(x);
1223                }
1224            }
1225            Expression::PathData(data) => match data {
1226                Path::Elements(elements) => {
1227                    for element in elements {
1228                        element.bindings.values().for_each(|binding| visitor(&binding.borrow()))
1229                    }
1230                }
1231                Path::Events(events, coordinates) => {
1232                    events.iter().chain(coordinates.iter()).for_each(visitor);
1233                }
1234                Path::Commands(commands) => visitor(commands),
1235            },
1236            Expression::EmptyDataTransfer => {}
1237            Expression::StoreLocalVariable { value, .. } => visitor(value),
1238            Expression::ReadLocalVariable { .. } => {}
1239            Expression::EasingCurve(_) => {}
1240            Expression::MouseCursor(cursor) => match cursor {
1241                MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
1242                    visitor(image);
1243                    visitor(hotspot_x);
1244                    visitor(hotspot_y);
1245                }
1246                MouseCursorInner::BuiltIn(e) => visitor(e),
1247            },
1248            Expression::LinearGradient { angle, stops } => {
1249                visitor(angle);
1250                for (c, s) in stops {
1251                    visitor(c);
1252                    visitor(s);
1253                }
1254            }
1255            Expression::RadialGradient { center, radius, stops } => {
1256                if let Some((cx, cy)) = center {
1257                    visitor(cx);
1258                    visitor(cy);
1259                }
1260                if let Some(r) = radius {
1261                    visitor(r);
1262                }
1263                for (c, s) in stops {
1264                    visitor(c);
1265                    visitor(s);
1266                }
1267            }
1268            Expression::ConicGradient { from_angle, center, stops } => {
1269                visitor(from_angle);
1270                if let Some((cx, cy)) = center {
1271                    visitor(cx);
1272                    visitor(cy);
1273                }
1274                for (c, s) in stops {
1275                    visitor(c);
1276                    visitor(s);
1277                }
1278            }
1279            Expression::EnumerationValue(_) => {}
1280            Expression::Keys(_) => {}
1281            Expression::ReturnStatement(expr) => {
1282                expr.as_deref().map(visitor);
1283            }
1284            Expression::LayoutCacheAccess { repeater_index, .. } => {
1285                repeater_index.as_deref().map(visitor);
1286            }
1287            Expression::GridRepeaterCacheAccess {
1288                repeater_index,
1289                stride,
1290                inner_repeater_index,
1291                ..
1292            } => {
1293                visitor(repeater_index);
1294                visitor(stride);
1295                inner_repeater_index.as_deref().map(visitor);
1296            }
1297            Expression::OrganizeGridLayout(..) => {}
1298            Expression::ComputeBoxLayoutInfo { cross_axis_size, .. }
1299            | Expression::ComputeGridLayoutInfo { cross_axis_size, .. }
1300            | Expression::ComputeFlexboxLayoutInfo { cross_axis_size, .. } => {
1301                if let Some(cas) = cross_axis_size {
1302                    visitor(cas);
1303                }
1304            }
1305            Expression::SolveBoxLayout(..) => {}
1306            Expression::SolveGridLayout { .. } => {}
1307            Expression::SolveFlexboxLayout(..) => {}
1308            Expression::MinMax { lhs, rhs, .. } => {
1309                visitor(lhs);
1310                visitor(rhs);
1311            }
1312            Expression::EmptyComponentFactory => {}
1313            Expression::DebugHook { expression, .. } => visitor(expression),
1314            Expression::Closure { expression, .. } => visitor(expression),
1315        }
1316    }
1317
1318    pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) {
1319        self.visit_mut_dyn(&mut visitor)
1320    }
1321
1322    fn visit_mut_dyn(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
1323        match self {
1324            Expression::Invalid => {}
1325            Expression::Uncompiled(_) => {}
1326            Expression::StringLiteral(_) => {}
1327            Expression::NumberLiteral(_, _) => {}
1328            Expression::BoolLiteral(_) => {}
1329            Expression::PropertyReference { .. } => {}
1330            Expression::FunctionParameterReference { .. } => {}
1331            Expression::ElementReference(_) => {}
1332            Expression::StructFieldAccess { base, .. } => visitor(base),
1333            Expression::ArrayIndex { array, index } => {
1334                visitor(array);
1335                visitor(index);
1336            }
1337            Expression::RepeaterIndexReference { .. } => {}
1338            Expression::RepeaterModelReference { .. } => {}
1339            Expression::Cast { from, .. } => visitor(from),
1340            Expression::CodeBlock(sub) => {
1341                sub.iter_mut().for_each(visitor);
1342            }
1343            Expression::FunctionCall { function: _, arguments, source_location: _ } => {
1344                arguments.iter_mut().for_each(visitor);
1345            }
1346            Expression::SelfAssignment { lhs, rhs, .. } => {
1347                visitor(lhs);
1348                visitor(rhs);
1349            }
1350            Expression::ImageReference { .. } => {}
1351            Expression::Condition { condition, true_expr, false_expr, .. } => {
1352                visitor(condition);
1353                visitor(true_expr);
1354                visitor(false_expr);
1355            }
1356            Expression::BinaryExpression { lhs, rhs, .. } => {
1357                visitor(lhs);
1358                visitor(rhs);
1359            }
1360            Expression::UnaryOp { sub, .. } => visitor(sub),
1361            Expression::Array { values, .. } => {
1362                for x in values {
1363                    visitor(x);
1364                }
1365            }
1366            Expression::Struct { values, .. } => {
1367                for x in values.values_mut() {
1368                    visitor(x);
1369                }
1370            }
1371            Expression::PathData(data) => match data {
1372                Path::Elements(elements) => {
1373                    for element in elements {
1374                        element
1375                            .bindings
1376                            .values_mut()
1377                            .for_each(|binding| visitor(&mut binding.borrow_mut()))
1378                    }
1379                }
1380                Path::Events(events, coordinates) => {
1381                    events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
1382                }
1383                Path::Commands(commands) => visitor(commands),
1384            },
1385            Expression::EmptyDataTransfer => {}
1386            Expression::StoreLocalVariable { value, .. } => visitor(value),
1387            Expression::ReadLocalVariable { .. } => {}
1388            Expression::EasingCurve(_) => {}
1389            Expression::MouseCursor(cursor) => match cursor {
1390                MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
1391                    visitor(image);
1392                    visitor(hotspot_x);
1393                    visitor(hotspot_y);
1394                }
1395                MouseCursorInner::BuiltIn(e) => visitor(e),
1396            },
1397            Expression::LinearGradient { angle, stops } => {
1398                visitor(angle);
1399                for (c, s) in stops {
1400                    visitor(c);
1401                    visitor(s);
1402                }
1403            }
1404            Expression::RadialGradient { center, radius, stops } => {
1405                if let Some((cx, cy)) = center {
1406                    visitor(cx);
1407                    visitor(cy);
1408                }
1409                if let Some(r) = radius {
1410                    visitor(r);
1411                }
1412                for (c, s) in stops {
1413                    visitor(c);
1414                    visitor(s);
1415                }
1416            }
1417            Expression::ConicGradient { from_angle, center, stops } => {
1418                visitor(from_angle);
1419                if let Some((cx, cy)) = center {
1420                    visitor(cx);
1421                    visitor(cy);
1422                }
1423                for (c, s) in stops {
1424                    visitor(c);
1425                    visitor(s);
1426                }
1427            }
1428            Expression::EnumerationValue(_) => {}
1429            Expression::Keys(_) => {}
1430            Expression::ReturnStatement(expr) => {
1431                expr.as_deref_mut().map(visitor);
1432            }
1433            Expression::LayoutCacheAccess { repeater_index, .. } => {
1434                repeater_index.as_deref_mut().map(visitor);
1435            }
1436            Expression::GridRepeaterCacheAccess {
1437                repeater_index,
1438                stride,
1439                inner_repeater_index,
1440                ..
1441            } => {
1442                visitor(repeater_index);
1443                visitor(stride);
1444                inner_repeater_index.as_deref_mut().map(visitor);
1445            }
1446            Expression::OrganizeGridLayout(..) => {}
1447            Expression::ComputeBoxLayoutInfo { cross_axis_size, .. }
1448            | Expression::ComputeGridLayoutInfo { cross_axis_size, .. }
1449            | Expression::ComputeFlexboxLayoutInfo { cross_axis_size, .. } => {
1450                if let Some(cas) = cross_axis_size {
1451                    visitor(cas);
1452                }
1453            }
1454            Expression::SolveBoxLayout(..) => {}
1455            Expression::SolveGridLayout { .. } => {}
1456            Expression::SolveFlexboxLayout(..) => {}
1457            Expression::MinMax { lhs, rhs, .. } => {
1458                visitor(lhs);
1459                visitor(rhs);
1460            }
1461            Expression::EmptyComponentFactory => {}
1462            Expression::DebugHook { expression, .. } => visitor(expression),
1463            Expression::Closure { expression, .. } => visitor(expression),
1464        }
1465    }
1466
1467    /// Visit itself and each sub expression recursively
1468    pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) {
1469        visitor(self);
1470        self.visit(|e| e.visit_recursive(visitor));
1471    }
1472
1473    /// Visit itself and each sub expression recursively
1474    pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
1475        visitor(self);
1476        self.visit_mut(|e| e.visit_recursive_mut(visitor));
1477    }
1478
1479    pub fn is_constant(&self, ga: Option<&crate::passes::GlobalAnalysis>) -> bool {
1480        match self {
1481            Expression::Invalid => true,
1482            Expression::Uncompiled(_) => false,
1483            Expression::StringLiteral(_) => true,
1484            Expression::NumberLiteral(_, _) => true,
1485            Expression::BoolLiteral(_) => true,
1486            Expression::PropertyReference(nr) => nr.is_constant(),
1487            Expression::ElementReference(_) => false,
1488            Expression::RepeaterIndexReference { .. } => false,
1489            Expression::RepeaterModelReference { .. } => false,
1490            // Allow functions to be marked as const
1491            Expression::FunctionParameterReference { .. } => true,
1492            Expression::StructFieldAccess { base, .. } => base.is_constant(ga),
1493            Expression::ArrayIndex { array, index } => {
1494                array.is_constant(ga) && index.is_constant(ga)
1495            }
1496            Expression::Cast { from, to } => {
1497                // Converting a float to string depends on the locale's decimal separator,
1498                // unless the result contains none, like for integer literals.
1499                // The constant propagation folds the remaining constant cases and
1500                // promotes their binding back to constant.
1501                if *to == Type::String
1502                    && from.ty() == Type::Float32
1503                    && !matches!(&**from, Expression::NumberLiteral(n, Unit::None)
1504                        if locale_independent_number_to_string(*n).is_some())
1505                {
1506                    return false;
1507                }
1508                from.is_constant(ga)
1509            }
1510            // This is conservative: the return value is the last expression in the block, but
1511            // we kind of mean "pure" here too, so ensure the whole body is OK.
1512            Expression::CodeBlock(sub) => sub.iter().all(|s| s.is_constant(ga)),
1513            Expression::FunctionCall { function, arguments, .. } => {
1514                let is_const = match function {
1515                    Callable::Builtin(b) => b.is_const(ga),
1516                    Callable::Function(nr) => nr.is_constant(),
1517                    Callable::Callback(..) => false,
1518                };
1519                is_const && arguments.iter().all(|a| a.is_constant(ga))
1520            }
1521            Expression::SelfAssignment { .. } => false,
1522            Expression::ImageReference { .. } => true,
1523            Expression::Condition { condition, false_expr, true_expr, .. } => {
1524                condition.is_constant(ga) && false_expr.is_constant(ga) && true_expr.is_constant(ga)
1525            }
1526            Expression::BinaryExpression { lhs, rhs, .. } => {
1527                lhs.is_constant(ga) && rhs.is_constant(ga)
1528            }
1529            Expression::UnaryOp { sub, .. } => sub.is_constant(ga),
1530            // Array will turn into model, and they can't be considered as constant if the model
1531            // is used and the model is changed. CF issue #5249
1532            //Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
1533            Expression::Array { .. } => false,
1534            Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant(ga)),
1535            Expression::PathData(data) => match data {
1536                Path::Elements(elements) => elements
1537                    .iter()
1538                    .all(|element| element.bindings.values().all(|v| v.borrow().is_constant(ga))),
1539                Path::Events(_, _) => true,
1540                Path::Commands(_) => false,
1541            },
1542            Expression::EmptyDataTransfer => true,
1543            Expression::StoreLocalVariable { value, .. } => value.is_constant(ga),
1544            // We only load what we store, and stores are already checked
1545            Expression::ReadLocalVariable { .. } => true,
1546            Expression::EasingCurve(_) => true,
1547            Expression::MouseCursor(cursor) => match cursor {
1548                MouseCursorInner::BuiltIn(cursor) => cursor.is_constant(ga),
1549                MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
1550                    image.is_constant(ga) && hotspot_x.is_constant(ga) && hotspot_y.is_constant(ga)
1551                }
1552            },
1553            Expression::LinearGradient { angle, stops } => {
1554                angle.is_constant(ga)
1555                    && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1556            }
1557            Expression::RadialGradient { center, radius, stops } => {
1558                center.as_ref().is_none_or(|(cx, cy)| cx.is_constant(ga) && cy.is_constant(ga))
1559                    && radius.as_ref().is_none_or(|r| r.is_constant(ga))
1560                    && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1561            }
1562            Expression::ConicGradient { from_angle, center, stops } => {
1563                from_angle.is_constant(ga)
1564                    && center
1565                        .as_ref()
1566                        .is_none_or(|(cx, cy)| cx.is_constant(ga) && cy.is_constant(ga))
1567                    && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1568            }
1569            Expression::EnumerationValue(_) => true,
1570            Expression::Keys(_) => true,
1571            Expression::ReturnStatement(expr) => {
1572                expr.as_ref().is_none_or(|expr| expr.is_constant(ga))
1573            }
1574            // TODO:  detect constant property within layouts
1575            Expression::LayoutCacheAccess { .. } => false,
1576            Expression::GridRepeaterCacheAccess { .. } => false,
1577            Expression::OrganizeGridLayout { .. } => false,
1578            Expression::ComputeBoxLayoutInfo { .. } => false,
1579            Expression::ComputeGridLayoutInfo { .. } => false,
1580            Expression::SolveBoxLayout(..) => false,
1581            Expression::SolveGridLayout { .. } => false,
1582            Expression::SolveFlexboxLayout(..) => false,
1583            Expression::ComputeFlexboxLayoutInfo { .. } => false,
1584            Expression::MinMax { lhs, rhs, .. } => lhs.is_constant(ga) && rhs.is_constant(ga),
1585            Expression::EmptyComponentFactory => true,
1586            Expression::DebugHook { .. } => false,
1587            Expression::Closure { expression, .. } => expression.is_constant(ga),
1588        }
1589    }
1590
1591    /// Create a conversion node if needed, or throw an error if the type is not matching
1592    #[must_use]
1593    pub fn maybe_convert_to(
1594        self,
1595        target_type: Type,
1596        node: &dyn Spanned,
1597        diag: &mut BuildDiagnostics,
1598        symbol_counters: &SymbolCounters,
1599    ) -> Expression {
1600        let ty = self.ty();
1601
1602        if let Expression::Condition { .. } = self
1603            && ty == Type::Void
1604        {
1605            // The true and false expressions do not return the same type. So at least one does not match
1606            // with expected and an error was already added so we don't have to add an additional error here
1607            self
1608        } else if ty == target_type
1609            || target_type == Type::Void
1610            || target_type == Type::Invalid
1611            || ty == Type::Invalid
1612        {
1613            self
1614        } else if ty.can_convert(&target_type) {
1615            let from = match (ty, &target_type) {
1616                (Type::Brush, Type::Color) => match self {
1617                    Expression::LinearGradient { .. }
1618                    | Expression::RadialGradient { .. }
1619                    | Expression::ConicGradient { .. } => {
1620                        let message = format!(
1621                            "Narrowing conversion from {0} to {1}. This can lead to unexpected behavior because the {0} is a gradient",
1622                            Type::Brush,
1623                            Type::Color
1624                        );
1625                        diag.push_warning(message, node);
1626                        self
1627                    }
1628                    _ => self,
1629                },
1630                (Type::Percent, Type::Float32) => Expression::BinaryExpression {
1631                    lhs: Box::new(self),
1632                    rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
1633                    op: '*',
1634                    source_location: None,
1635                },
1636                (ref from_ty @ Type::Struct(ref left), Type::Struct(right))
1637                    if left.fields != right.fields =>
1638                {
1639                    // Slint SC converts a struct only when the source names
1640                    // exactly the target's fields: no defaulting of an omitted
1641                    // field and no dropping of an extra member.
1642                    #[cfg(feature = "slint-sc")]
1643                    if diag.slint_sc {
1644                        for f in left.fields.keys() {
1645                            if !right.fields.contains_key(f) {
1646                                diag.slint_sc_error(
1647                                    &format!("Providing the extra struct member '{f}' is"),
1648                                    node,
1649                                );
1650                            }
1651                        }
1652                        for f in right.fields.keys() {
1653                            if !left.fields.contains_key(f) {
1654                                diag.slint_sc_error(
1655                                    &format!("Omitting the struct field '{f}' is"),
1656                                    node,
1657                                );
1658                            }
1659                        }
1660                    }
1661                    if !diag.is_slint_sc() {
1662                        let extra = left
1663                            .fields
1664                            .keys()
1665                            .filter(|f| !right.fields.contains_key(*f))
1666                            .map(|f| format!("'{f}'"))
1667                            .collect::<Vec<_>>();
1668                        if let Some((last, rest)) = extra.split_last() {
1669                            let (noun, list) = match rest {
1670                                [] => ("field", last.clone()),
1671                                _ => ("fields", format!("{} and {last}", rest.join(", "))),
1672                            };
1673                            diag.push_warning(
1674                                format!(
1675                                    "Conversion to {target_type} ignores the extra {noun} {list}"
1676                                ),
1677                                node,
1678                            );
1679                        }
1680                    }
1681                    if let Expression::Struct { mut values, .. } = self {
1682                        let mut new_values = BTreeMap::new();
1683                        for (key, ty) in &right.fields {
1684                            let (key, expression) = values.remove_entry(key).map_or_else(
1685                                || (key.clone(), right.default_value_for_field(key)),
1686                                |(k, e)| {
1687                                    (k, e.maybe_convert_to(ty.clone(), node, diag, symbol_counters))
1688                                },
1689                            );
1690                            new_values.insert(key, expression);
1691                        }
1692                        return Expression::Struct { values: new_values, ty: right.clone() };
1693                    }
1694                    let var_name = symbol_counters.generate_name("tmpobj_conv_");
1695                    let mut new_values = BTreeMap::new();
1696                    for (key, ty) in &right.fields {
1697                        let expression = if left.fields.contains_key(key) {
1698                            Expression::StructFieldAccess {
1699                                base: Box::new(Expression::ReadLocalVariable {
1700                                    name: var_name.clone(),
1701                                    ty: from_ty.clone(),
1702                                }),
1703                                name: key.clone(),
1704                            }
1705                            .maybe_convert_to(
1706                                ty.clone(),
1707                                node,
1708                                diag,
1709                                symbol_counters,
1710                            )
1711                        } else {
1712                            right.default_value_for_field(key)
1713                        };
1714                        new_values.insert(key.clone(), expression);
1715                    }
1716                    return Expression::CodeBlock(vec![
1717                        Expression::StoreLocalVariable { name: var_name, value: Box::new(self) },
1718                        Expression::Struct { values: new_values, ty: right.clone() },
1719                    ]);
1720                }
1721                (left, right) => match (left.as_unit_product(), right.as_unit_product()) {
1722                    (Some(left), Some(right)) => {
1723                        if let Some(conversion_powers) =
1724                            crate::langtype::unit_product_length_conversion(&left, &right)
1725                        {
1726                            let apply_power =
1727                                |mut result, power: i8, builtin_fn: BuiltinFunction| {
1728                                    let op = if power < 0 { '*' } else { '/' };
1729                                    for _ in 0..power.abs() {
1730                                        result = Expression::BinaryExpression {
1731                                            source_location: None,
1732                                            lhs: Box::new(result),
1733                                            rhs: Box::new(Expression::FunctionCall {
1734                                                function: Callable::Builtin(builtin_fn.clone()),
1735                                                arguments: Vec::new(),
1736                                                source_location: Some(node.to_source_location()),
1737                                            }),
1738                                            op,
1739                                        }
1740                                    }
1741                                    result
1742                                };
1743
1744                            let mut result = self;
1745
1746                            if conversion_powers.rem_to_px_power != 0 {
1747                                result = apply_power(
1748                                    result,
1749                                    conversion_powers.rem_to_px_power,
1750                                    BuiltinFunction::GetWindowDefaultFontSize,
1751                                )
1752                            }
1753                            if conversion_powers.px_to_phx_power != 0 {
1754                                result = apply_power(
1755                                    result,
1756                                    conversion_powers.px_to_phx_power,
1757                                    BuiltinFunction::GetWindowScaleFactor,
1758                                )
1759                            }
1760
1761                            result
1762                        } else {
1763                            self
1764                        }
1765                    }
1766                    _ => self,
1767                },
1768            };
1769            Expression::Cast { from: Box::new(from), to: target_type }
1770        } else if matches!(
1771            (&ty, &target_type, &self),
1772            (Type::Array(_), Type::Array(_), Expression::Array { .. })
1773        ) {
1774            // Special case for converting array literals
1775            match (self, target_type) {
1776                (Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array {
1777                    values: values
1778                        .into_iter()
1779                        .map(|e| {
1780                            e.maybe_convert_to((*target_type).clone(), node, diag, symbol_counters)
1781                        })
1782                        .take_while(|e| !matches!(e, Expression::Invalid))
1783                        .collect(),
1784                    element_ty: (*target_type).clone(),
1785                },
1786                _ => unreachable!(),
1787            }
1788        } else if let (Type::Struct(target_struct_type), Expression::Struct { values, .. }) =
1789            (&target_type, &self)
1790        {
1791            // Also special case struct literal in case they contain array literal
1792            let mut target_fields = target_struct_type.fields.clone();
1793            let mut new_values = BTreeMap::new();
1794            for (f, v) in values {
1795                if let Some(t) = target_fields.remove(f) {
1796                    new_values.insert(
1797                        f.clone(),
1798                        v.clone().maybe_convert_to(t, node, diag, symbol_counters),
1799                    );
1800                } else {
1801                    let available_fields_message = if target_struct_type.name.slint_name().is_some()
1802                    {
1803                        let available_fields = target_struct_type
1804                            .fields
1805                            .keys()
1806                            .map(SmolStr::as_str)
1807                            .collect::<Vec<_>>()
1808                            .join("', '");
1809                        format!(". Available fields: '{available_fields}'")
1810                    } else {
1811                        String::new()
1812                    };
1813                    diag.push_error(
1814                        format!("Cannot convert {ty} to {target_type}: Field '{f}' not found{available_fields_message}"),
1815                        node,
1816                    );
1817                    return self;
1818                }
1819            }
1820            for f in target_fields.into_keys() {
1821                let default_value = target_struct_type.default_value_for_field(&f);
1822                new_values.insert(f, default_value);
1823            }
1824            Expression::Struct { ty: target_struct_type.clone(), values: new_values }
1825        } else if let Expression::Condition { condition, true_expr, false_expr, source_location } =
1826            self
1827        {
1828            // Recursive try to convert the conditional expressions to the target_type
1829            // true_expr and false_expr are equal this is handled with the condition at the beginning
1830            // of this function so if one fails to convert, we should not try to convert the false case
1831            // as well
1832            let true_expr_converted = true_expr.clone().maybe_convert_to(
1833                target_type.clone(),
1834                node,
1835                diag,
1836                symbol_counters,
1837            );
1838            if true_expr_converted.ty() != target_type.clone() {
1839                // Failed to convert so we don't have to try to convert the false expr as well
1840                Expression::Condition { condition, true_expr, false_expr, source_location }
1841            } else {
1842                Expression::Condition {
1843                    condition,
1844                    source_location,
1845                    true_expr: Box::new(true_expr_converted),
1846                    false_expr: Box::new(false_expr.maybe_convert_to(
1847                        target_type,
1848                        node,
1849                        diag,
1850                        symbol_counters,
1851                    )),
1852                }
1853            }
1854        } else {
1855            let mut message = format!("Cannot convert {ty} to {target_type}");
1856            // Explicit error message for unit conversion
1857            if let Some(from_unit) = ty.default_unit() {
1858                if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) {
1859                    message =
1860                        format!("{message}. Divide by 1{from_unit} to convert to a plain number");
1861                }
1862            } else if matches!(target_type, Type::StyledText) && ty.can_convert(&Type::String) {
1863                message = format!(
1864                    "{message}. Wrap the expression in `@markdown(\"\\{{...}}\")` to convert it explicitly"
1865                );
1866            } else if let Some(to_unit) = target_type.default_unit()
1867                && matches!(ty, Type::Int32 | Type::Float32)
1868            {
1869                if let Expression::NumberLiteral(value, Unit::None) = self
1870                    && value == 0.
1871                {
1872                    // Allow conversion from literal 0 to any unit
1873                    return Expression::NumberLiteral(0., to_unit);
1874                }
1875                message = format!(
1876                    "{message}. Use an unit, or multiply by 1{to_unit} to convert explicitly"
1877                );
1878            }
1879            diag.push_error(message, node);
1880            self
1881        }
1882    }
1883
1884    /// Return the default value for the given type
1885    pub fn default_value_for_type(ty: &Type) -> Expression {
1886        match ty {
1887            Type::Invalid
1888            | Type::Callback { .. }
1889            | Type::Function { .. }
1890            | Type::InferredProperty
1891            | Type::InferredCallback
1892            | Type::ElementReference
1893            | Type::LayoutCache
1894            | Type::ArrayOfU16 => Expression::Invalid,
1895            Type::Void => Expression::CodeBlock(Vec::new()),
1896            Type::DataTransfer => Expression::EmptyDataTransfer,
1897            Type::Float32 => Expression::NumberLiteral(0., Unit::None),
1898            Type::String => Expression::StringLiteral(SmolStr::default()),
1899            Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast {
1900                from: Box::new(Expression::NumberLiteral(0., Unit::None)),
1901                to: ty.clone(),
1902            },
1903            Type::Duration => Expression::NumberLiteral(0., Unit::Ms),
1904            Type::Angle => Expression::NumberLiteral(0., Unit::Deg),
1905            Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx),
1906            Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px),
1907            Type::Rem => Expression::NumberLiteral(0., Unit::Rem),
1908            Type::Percent => Expression::NumberLiteral(100., Unit::Percent),
1909            Type::Image => Expression::ImageReference {
1910                resource_ref: ImageReference::None,
1911                source_location: None,
1912                nine_slice: None,
1913            },
1914            Type::Bool => Expression::BoolLiteral(false),
1915            Type::Model => Expression::Invalid,
1916            Type::PathData => Expression::PathData(Path::Elements(Vec::new())),
1917            Type::Array(element_ty) => {
1918                Expression::Array { element_ty: (**element_ty).clone(), values: Vec::new() }
1919            }
1920            Type::Struct(s) => Expression::Struct {
1921                ty: s.clone(),
1922                values: s
1923                    .fields
1924                    .keys()
1925                    .map(|k| (k.clone(), s.default_value_for_field(k)))
1926                    .collect(),
1927            },
1928            Type::Easing => Expression::EasingCurve(EasingCurve::default()),
1929            Type::MouseCursor => {
1930                let e = crate::typeregister::BUILTIN.enums.BuiltInMouseCursor.clone();
1931                Expression::MouseCursor(MouseCursorInner::BuiltIn(Box::new(
1932                    Expression::EnumerationValue(e.default_value()),
1933                )))
1934            }
1935            Type::Brush => Expression::Cast {
1936                from: Box::new(Expression::default_value_for_type(&Type::Color)),
1937                to: Type::Brush,
1938            },
1939            Type::Enumeration(enumeration) => {
1940                Expression::EnumerationValue(enumeration.clone().default_value())
1941            }
1942            Type::Keys => Expression::Keys(Keys::default()),
1943            Type::ComponentFactory => Expression::EmptyComponentFactory,
1944            Type::StyledText => Expression::FunctionCall {
1945                function: Callable::Builtin(BuiltinFunction::StringToStyledText),
1946                arguments: vec![Self::default_value_for_type(&Type::String)],
1947                source_location: None,
1948            },
1949            Type::Closure => Expression::Invalid,
1950        }
1951    }
1952
1953    /// Try to mark this expression to a lvalue that can be assigned to.
1954    ///
1955    /// Return true if the expression is a "lvalue" that can be used as the left hand side of a `=` or `+=` or similar
1956    pub fn try_set_rw(
1957        &mut self,
1958        ctx: &mut LookupCtx,
1959        what: &'static str,
1960        node: &dyn Spanned,
1961    ) -> bool {
1962        match self {
1963            Expression::PropertyReference(nr) => {
1964                nr.mark_as_set();
1965                let mut lookup = nr
1966                    .element()
1967                    .borrow()
1968                    .lookup_property(nr.name(), PropertyLookupMode::InternalName);
1969                lookup.is_local_to_component &= ctx.is_local_element(&nr.element());
1970                if lookup.property_visibility == PropertyVisibility::Constexpr {
1971                    ctx.diag.push_error(
1972                        "The property must be known at compile time and cannot be changed at runtime"
1973                            .into(),
1974                        node,
1975                    );
1976                    false
1977                } else if lookup.is_valid_for_assignment() {
1978                    if !nr
1979                        .element()
1980                        .borrow()
1981                        .property_analysis
1982                        .borrow()
1983                        .get(nr.name())
1984                        .is_some_and(|d| d.is_linked_to_read_only)
1985                    {
1986                        true
1987                    } else if ctx.is_legacy_component() {
1988                        ctx.diag.push_warning("Modifying a property that is linked to a read-only property is deprecated".into(), node);
1989                        true
1990                    } else {
1991                        ctx.diag.push_error(
1992                            "Cannot modify a property that is linked to a read-only property"
1993                                .into(),
1994                            node,
1995                        );
1996                        false
1997                    }
1998                } else if ctx.is_legacy_component()
1999                    && lookup.property_visibility == PropertyVisibility::Output
2000                {
2001                    ctx.diag.push_warning(
2002                        format!(
2003                            "{what} on an '{}' property is deprecated",
2004                            PropertyVisibility::Output
2005                        ),
2006                        node,
2007                    );
2008                    true
2009                } else {
2010                    ctx.diag.push_error(
2011                        format!("{what} on an '{}' property", lookup.property_visibility),
2012                        node,
2013                    );
2014                    false
2015                }
2016            }
2017            Expression::StructFieldAccess { base, .. } => base.try_set_rw(ctx, what, node),
2018            Expression::RepeaterModelReference { .. } => true,
2019            Expression::ArrayIndex { array, .. } => array.try_set_rw(ctx, what, node),
2020            _ => {
2021                ctx.diag.push_error(format!("{what} needs to be done on a property"), node);
2022                false
2023            }
2024        }
2025    }
2026
2027    /// Unwrap DebugHook expressions to their contained sub-expression
2028    pub fn ignore_debug_hooks(&self) -> &Expression {
2029        match self {
2030            Expression::DebugHook { expression, .. } => expression.as_ref(),
2031            _ => self,
2032        }
2033    }
2034
2035    pub fn ignore_debug_hooks_mut(&mut self) -> &mut Expression {
2036        match self {
2037            Expression::DebugHook { expression, .. } => expression.as_mut(),
2038            _ => self,
2039        }
2040    }
2041
2042    /// Returns true if this is a synthetic debug hook — i.e. a hook materialized for a property
2043    /// that had no binding in the source. Passes should treat this like `Expression::Invalid`.
2044    pub fn is_synthetic_debug_hook(&self) -> bool {
2045        matches!(self, Expression::DebugHook { synthetic: true, .. })
2046    }
2047}
2048
2049fn model_inner_type(model: &Expression) -> Type {
2050    match model {
2051        Expression::Cast { from, to: Type::Model } => model_inner_type(from),
2052        Expression::CodeBlock(cb) => cb.last().map_or(Type::Invalid, model_inner_type),
2053        _ => match model.ty() {
2054            Type::Float32 | Type::Int32 => Type::Int32,
2055            Type::Array(elem) => (*elem).clone(),
2056            _ => Type::Invalid,
2057        },
2058    }
2059}
2060
2061/// Converts a float to a string when the result contains no decimal separator,
2062/// and is therefore the same in every locale.
2063pub fn locale_independent_number_to_string(n: f64) -> Option<SmolStr> {
2064    let string = format_smolstr!("{}", i_slint_common::FormattedNumber(n));
2065    (!string.contains('.')).then_some(string)
2066}
2067
2068/// The right hand side of a two way binding
2069#[derive(Clone, Debug)]
2070pub enum TwoWayBinding {
2071    Property {
2072        /// The property being linked
2073        property: NamedReference,
2074        /// If property is a struct, this is the fields.
2075        /// So if you have `foo <=> element.property.baz.xyz`, then `field_access` is `vec!["baz", "xyz"]`
2076        field_access: Vec<SmolStr>,
2077    },
2078    ModelData {
2079        /// The model being linked
2080        repeated_element: ElementWeak,
2081        /// same as `Self::Property::field_access`
2082        field_access: Vec<SmolStr>,
2083    },
2084}
2085impl TwoWayBinding {
2086    pub fn ty(&self) -> Type {
2087        let (mut ty, field_access) = match self {
2088            Self::Property { property, field_access } => (property.ty(), field_access),
2089            Self::ModelData { repeated_element, field_access } => {
2090                let ty =
2091                    Expression::RepeaterModelReference { element: repeated_element.clone() }.ty();
2092                (ty, field_access)
2093            }
2094        };
2095        for x in field_access {
2096            ty = match ty {
2097                Type::InferredProperty | Type::InferredCallback => return ty,
2098                Type::Struct(s) => s.fields.get(x).cloned().unwrap_or_default(),
2099                _ => return Type::Invalid,
2100            };
2101        }
2102        ty
2103    }
2104
2105    pub fn is_constant(&self) -> bool {
2106        match self {
2107            Self::Property { property, .. } => property.is_constant(),
2108            Self::ModelData { .. } => false,
2109        }
2110    }
2111
2112    pub fn property(&self) -> Option<&NamedReference> {
2113        match self {
2114            Self::Property { property, .. } => Some(property),
2115            Self::ModelData { .. } => None,
2116        }
2117    }
2118}
2119
2120impl From<NamedReference> for TwoWayBinding {
2121    fn from(nr: NamedReference) -> Self {
2122        Self::Property { property: nr, field_access: Vec::new() }
2123    }
2124}
2125
2126/// The expression in the Element::binding hash table
2127#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
2128pub struct BindingExpression {
2129    #[deref]
2130    #[deref_mut]
2131    pub expression: Expression,
2132    /// The location of this expression in the source code
2133    pub span: Option<SourceLocation>,
2134    /// How deep is this binding declared in the hierarchy. When two bindings are conflicting
2135    /// for the same priority (because of a two way binding), the lower priority wins.
2136    /// The priority starts at 1, and each level of inlining adds one to the priority.
2137    /// 0 means the expression was added by some passes and it is not explicit in the source code
2138    pub priority: i32,
2139
2140    pub animation: Option<PropertyAnimation>,
2141
2142    /// The analysis information. None before it is computed
2143    pub analysis: Option<BindingAnalysis>,
2144
2145    /// The properties this expression is aliased with using two way bindings
2146    pub two_way_bindings: Vec<TwoWayBinding>,
2147}
2148
2149impl std::convert::From<Expression> for BindingExpression {
2150    fn from(expression: Expression) -> Self {
2151        Self {
2152            expression,
2153            span: None,
2154            priority: 0,
2155            animation: Default::default(),
2156            analysis: Default::default(),
2157            two_way_bindings: Default::default(),
2158        }
2159    }
2160}
2161
2162impl BindingExpression {
2163    pub fn new_uncompiled(node: SyntaxNode) -> Self {
2164        Self {
2165            expression: Expression::Uncompiled(node.clone()),
2166            span: Some(node.to_source_location()),
2167            priority: 1,
2168            animation: Default::default(),
2169            analysis: Default::default(),
2170            two_way_bindings: Default::default(),
2171        }
2172    }
2173    pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self {
2174        Self {
2175            expression,
2176            span: Some(span),
2177            priority: 0,
2178            animation: Default::default(),
2179            analysis: Default::default(),
2180            two_way_bindings: Default::default(),
2181        }
2182    }
2183
2184    /// Create an expression binding that simply is a two way binding to the other
2185    pub fn new_two_way(other: TwoWayBinding) -> Self {
2186        Self {
2187            expression: Expression::Invalid,
2188            span: None,
2189            priority: 0,
2190            animation: Default::default(),
2191            analysis: Default::default(),
2192            two_way_bindings: vec![other],
2193        }
2194    }
2195
2196    /// Merge the other into this one. Normally, &self is kept intact (has priority)
2197    /// unless the expression is invalid or a synthetic debug hook, in which case the
2198    /// other one is taken.
2199    ///
2200    /// Also the animation is taken if the other don't have one, and the two ways binding
2201    /// are taken into account.
2202    ///
2203    /// Returns true if the other expression was taken
2204    pub fn merge_with(&mut self, other: &Self) -> bool {
2205        if self.animation.is_none() {
2206            self.animation.clone_from(&other.animation);
2207        }
2208        let has_binding = self.has_binding();
2209        self.two_way_bindings.extend_from_slice(&other.two_way_bindings);
2210        if has_binding {
2211            return false;
2212        }
2213        // A synthetic debug hook is equivalent to "no binding", but the hook wrapper (and
2214        // its id) must survive the merge so the property stays live-editable on this
2215        // element: upgrade the hook in place with the other side's real expression.
2216        if let Expression::DebugHook { expression, synthetic, .. } = &mut self.expression {
2217            debug_assert!(*synthetic, "has_binding() returned false for a non-synthetic hook");
2218            if !matches!(other.expression, Expression::Invalid)
2219                && !other.expression.is_synthetic_debug_hook()
2220            {
2221                **expression = other.expression.clone();
2222                *synthetic = false;
2223                self.priority = other.priority;
2224                return true;
2225            }
2226            if self.two_way_bindings.is_empty() {
2227                // Nothing real to adopt from the other side: keep the synthetic placeholder.
2228                return false;
2229            }
2230            // Two-way bindings now drive this property. The synthetic default must not
2231            // become the two-way's initial value, so the hook is dropped (the property is
2232            // then edited through the two-way target instead).
2233            self.expression = Expression::Invalid;
2234            self.priority = other.priority;
2235            return true;
2236        }
2237        self.priority = other.priority;
2238        self.expression = other.expression.clone();
2239        true
2240    }
2241
2242    /// returns false if there is no expression or two way binding
2243    ///
2244    /// A synthetic debug hook (a materialized placeholder for an unbound property) counts
2245    /// as "no expression".
2246    pub fn has_binding(&self) -> bool {
2247        (!matches!(self.expression, Expression::Invalid)
2248            && !self.expression.is_synthetic_debug_hook())
2249            || !self.two_way_bindings.is_empty()
2250    }
2251
2252    /// The bound expression with any debug-hook wrapper removed.
2253    /// Use before matching on the expression variant.
2254    pub fn value_expression(&self) -> &Expression {
2255        self.expression.ignore_debug_hooks()
2256    }
2257
2258    /// Replace the bound value, leaving priority, animation and two-way bindings untouched.
2259    ///
2260    /// A synthetic debug hook is upgraded in place — its wrapper and id are kept and it becomes
2261    /// real — so the property stays live-editable. Any other expression (including a real,
2262    /// non-synthetic hook) is replaced wholesale.
2263    pub fn set_value_expression(&mut self, expr: Expression) {
2264        match &mut self.expression {
2265            Expression::DebugHook { expression, synthetic, .. } if *synthetic => {
2266                **expression = expr;
2267                *synthetic = false;
2268            }
2269            expression => *expression = expr,
2270        }
2271    }
2272}
2273
2274impl Spanned for BindingExpression {
2275    fn span(&self) -> crate::diagnostics::Span {
2276        self.span.as_ref().map(|x| x.span()).unwrap_or_default()
2277    }
2278    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
2279        self.span.as_ref().and_then(|x| x.source_file())
2280    }
2281}
2282
2283#[derive(Default, Debug, Clone)]
2284pub struct BindingAnalysis {
2285    /// true if that binding is part of a binding loop that already has been reported.
2286    pub is_in_binding_loop: Cell<bool>,
2287
2288    /// true if the binding is a constant value that can be set without creating a binding at runtime
2289    pub is_const: bool,
2290
2291    /// true if this binding does not depends on the value of property that are set externally.
2292    /// When true, this binding cannot be part of a binding loop involving external components
2293    pub no_external_dependencies: bool,
2294}
2295
2296#[derive(Debug, Clone)]
2297pub enum Path {
2298    Elements(Vec<PathElement>),
2299    Events(Vec<Expression>, Vec<Expression>),
2300    Commands(Box<Expression>), // expr must evaluate to string
2301}
2302
2303#[derive(Debug, Clone)]
2304pub struct PathElement {
2305    pub element_type: Rc<BuiltinElement>,
2306    pub bindings: BindingsMap,
2307}
2308
2309#[derive(Clone, Debug, Default)]
2310pub enum EasingCurve {
2311    #[default]
2312    Linear,
2313    CubicBezier(f32, f32, f32, f32),
2314    EaseInElastic,
2315    EaseOutElastic,
2316    EaseInOutElastic,
2317    EaseInBounce,
2318    EaseOutBounce,
2319    EaseInOutBounce,
2320    Spring(f32),
2321    // CubicBezierNonConst([Box<Expression>; 4]),
2322    // Custom(Box<dyn Fn(f32)->f32>),
2323}
2324
2325/// The compiled `mouse-cursor` value: either a built-in cursor or a custom one built from an
2326/// image. Generic over the expression type so both the tree and the LLR reuse the same shape.
2327#[derive(Clone, Debug)]
2328pub enum MouseCursorInner<E = Expression> {
2329    BuiltIn(Box<E>),
2330    CustomMouseCursor { image: Box<E>, hotspot_x: Box<E>, hotspot_y: Box<E> },
2331}
2332
2333impl<E: Default> Default for MouseCursorInner<E> {
2334    fn default() -> Self {
2335        Self::BuiltIn(Box::default())
2336    }
2337}
2338
2339// The compiler resolves every `@image-url("foo.png")` into a `Path`, `Url`, or
2340// `DataUri` reference; the resource lowering pass may then replace it with
2341// `EmbeddedData`/`EmbeddedTexture` if configured.
2342#[derive(Clone, Debug)]
2343pub enum ImageReference {
2344    None,
2345    /// An absolute path to a local image file on disk.
2346    Path(SmolStr),
2347    /// A non-`data:` URL, e.g. `builtin:/`, `http(s):`, or `user://`.
2348    Url(url::Url),
2349    /// An inline `data:` URI carrying the image content.
2350    DataUri(SmolStr),
2351    EmbeddedData {
2352        resource_id: crate::embedded_resources::EmbeddedResourcesIdx,
2353        extension: String,
2354    },
2355    EmbeddedTexture {
2356        resource_id: crate::embedded_resources::EmbeddedResourcesIdx,
2357    },
2358}
2359
2360impl ImageReference {
2361    /// Classify a resolved `@image-url` string (an absolute path, a URL, or a
2362    /// `data:` URI) into the matching reference kind.
2363    pub fn from_resolved(reference: SmolStr) -> Self {
2364        if reference.starts_with("data:") {
2365            return Self::DataUri(reference);
2366        }
2367        // A single-character scheme is a Windows drive letter (`c:\...`), i.e. a
2368        // path rather than a URL.
2369        match url::Url::parse(&reference) {
2370            Ok(url) if url.scheme().len() > 1 => Self::Url(url),
2371            _ => Self::Path(reference),
2372        }
2373    }
2374
2375    /// Classify a URL returned by the resource mapper. It is already a URL, so
2376    /// the only distinction is a `data:` URI (kept as a string, see
2377    /// [`Self::DataUri`]) from any other URL.
2378    pub fn from_mapped_url(url: url::Url) -> Self {
2379        if url.scheme() == "data" { Self::DataUri(url.as_str().into()) } else { Self::Url(url) }
2380    }
2381
2382    /// The image source loaded at run-time for a non-embedded reference: the
2383    /// path, the URL, or the `data:` URI, as the string handed to
2384    /// `Image::load_from_path`. `None` for embedded references.
2385    pub fn source(&self) -> Option<&str> {
2386        match self {
2387            Self::Path(source) | Self::DataUri(source) => Some(source),
2388            Self::Url(url) => Some(url.as_str()),
2389            Self::None | Self::EmbeddedData { .. } | Self::EmbeddedTexture { .. } => None,
2390        }
2391    }
2392}
2393
2394/// Print the expression as a .slint code (not necessarily valid .slint)
2395pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result {
2396    match expression {
2397        Expression::Invalid => write!(f, "<invalid>"),
2398        Expression::Uncompiled(u) => write!(f, "{u:?}"),
2399        Expression::StringLiteral(s) => write!(f, "{s:?}"),
2400        Expression::NumberLiteral(vl, unit) => write!(f, "{vl}{unit}"),
2401        Expression::BoolLiteral(b) => write!(f, "{b:?}"),
2402        Expression::PropertyReference(a) => write!(f, "{a:?}"),
2403        Expression::ElementReference(a) => write!(f, "{a:?}"),
2404        Expression::RepeaterIndexReference { element } => {
2405            crate::namedreference::pretty_print_element_ref(f, element)
2406        }
2407        Expression::RepeaterModelReference { element } => {
2408            crate::namedreference::pretty_print_element_ref(f, element)?;
2409            write!(f, ".@model")
2410        }
2411        Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_{index}"),
2412        Expression::StoreLocalVariable { name, value } => {
2413            write!(f, "{name} = ")?;
2414            pretty_print(f, value)
2415        }
2416        Expression::ReadLocalVariable { name, ty: _ } => write!(f, "{name}"),
2417        Expression::StructFieldAccess { base, name } => {
2418            pretty_print(f, base)?;
2419            write!(f, ".{name}")
2420        }
2421        Expression::ArrayIndex { array, index } => {
2422            pretty_print(f, array)?;
2423            write!(f, "[")?;
2424            pretty_print(f, index)?;
2425            write!(f, "]")
2426        }
2427        Expression::Cast { from, to } => {
2428            write!(f, "(")?;
2429            pretty_print(f, from)?;
2430            write!(f, "/* as {to} */)")
2431        }
2432        Expression::CodeBlock(c) => {
2433            write!(f, "{{ ")?;
2434            for e in c {
2435                pretty_print(f, e)?;
2436                write!(f, "; ")?;
2437            }
2438            write!(f, "}}")
2439        }
2440        Expression::FunctionCall { function, arguments, source_location: _ } => {
2441            match function {
2442                Callable::Builtin(b) => write!(f, "{b:?}")?,
2443                Callable::Callback(nr) | Callable::Function(nr) => write!(f, "{nr:?}")?,
2444            }
2445            write!(f, "(")?;
2446            for e in arguments {
2447                pretty_print(f, e)?;
2448                write!(f, ", ")?;
2449            }
2450            write!(f, ")")
2451        }
2452        Expression::SelfAssignment { lhs, rhs, op, .. } => {
2453            pretty_print(f, lhs)?;
2454            write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
2455            pretty_print(f, rhs)
2456        }
2457        Expression::BinaryExpression { lhs, rhs, op, .. } => {
2458            write!(f, "(")?;
2459            pretty_print(f, lhs)?;
2460            match *op {
2461                '=' | '!' => write!(f, " {op}= ")?,
2462                _ => write!(f, " {op} ")?,
2463            };
2464            pretty_print(f, rhs)?;
2465            write!(f, ")")
2466        }
2467        Expression::UnaryOp { sub, op } => {
2468            write!(f, "{op}")?;
2469            pretty_print(f, sub)
2470        }
2471        Expression::ImageReference { resource_ref, .. } => write!(f, "{resource_ref:?}"),
2472        Expression::Condition { condition, true_expr, false_expr, .. } => {
2473            write!(f, "if (")?;
2474            pretty_print(f, condition)?;
2475            write!(f, ") {{ ")?;
2476            pretty_print(f, true_expr)?;
2477            write!(f, " }} else {{ ")?;
2478            pretty_print(f, false_expr)?;
2479            write!(f, " }}")
2480        }
2481        Expression::Array { element_ty: _, values } => {
2482            write!(f, "[")?;
2483            for e in values {
2484                pretty_print(f, e)?;
2485                write!(f, ", ")?;
2486            }
2487            write!(f, "]")
2488        }
2489        Expression::Struct { ty: _, values } => {
2490            write!(f, "{{ ")?;
2491            for (name, e) in values {
2492                write!(f, "{name}: ")?;
2493                pretty_print(f, e)?;
2494                write!(f, ", ")?;
2495            }
2496            write!(f, " }}")
2497        }
2498        Expression::PathData(data) => write!(f, "{data:?}"),
2499        Expression::EmptyDataTransfer => write!(f, "{{ }}"),
2500        Expression::EasingCurve(e) => write!(f, "{e:?}"),
2501        Expression::MouseCursor(m) => write!(f, "{m:?}"),
2502        Expression::LinearGradient { angle, stops } => {
2503            write!(f, "@linear-gradient(")?;
2504            pretty_print(f, angle)?;
2505            for (c, s) in stops {
2506                write!(f, ", ")?;
2507                pretty_print(f, c)?;
2508                write!(f, "  ")?;
2509                pretty_print(f, s)?;
2510            }
2511            write!(f, ")")
2512        }
2513        Expression::RadialGradient { center, radius, stops } => {
2514            write!(f, "@radial-gradient(circle")?;
2515            if let Some(r) = radius {
2516                write!(f, " ")?;
2517                pretty_print(f, r)?;
2518            }
2519            if let Some((cx, cy)) = center {
2520                write!(f, " at ")?;
2521                pretty_print(f, cx)?;
2522                write!(f, " ")?;
2523                pretty_print(f, cy)?;
2524            }
2525            for (c, s) in stops {
2526                write!(f, ", ")?;
2527                pretty_print(f, c)?;
2528                write!(f, "  ")?;
2529                pretty_print(f, s)?;
2530            }
2531            write!(f, ")")
2532        }
2533        Expression::ConicGradient { from_angle, center, stops } => {
2534            write!(f, "@conic-gradient(from ")?;
2535            pretty_print(f, from_angle)?;
2536            if let Some((cx, cy)) = center {
2537                write!(f, " at ")?;
2538                pretty_print(f, cx)?;
2539                write!(f, " ")?;
2540                pretty_print(f, cy)?;
2541            }
2542            for (c, s) in stops {
2543                write!(f, ", ")?;
2544                pretty_print(f, c)?;
2545                write!(f, " ")?;
2546                pretty_print(f, s)?;
2547            }
2548            write!(f, ")")
2549        }
2550        Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value) {
2551            Some(val) => write!(f, "{}.{}", e.enumeration.name, val),
2552            None => write!(f, "{}.{}", e.enumeration.name, e.value),
2553        },
2554        Expression::Keys(keys) => {
2555            write!(f, "@keys({keys})")
2556        }
2557        Expression::ReturnStatement(e) => {
2558            write!(f, "return ")?;
2559            e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(()))
2560        }
2561        Expression::LayoutCacheAccess {
2562            layout_cache_prop,
2563            index,
2564            repeater_index,
2565            entries_per_item,
2566        } => {
2567            if repeater_index.is_some() {
2568                write!(
2569                    f,
2570                    "{:?}[{:?}[{}] + $repeater_index * {}]",
2571                    layout_cache_prop, layout_cache_prop, index, entries_per_item
2572                )
2573            } else {
2574                write!(f, "{:?}[{}]", layout_cache_prop, index)
2575            }
2576        }
2577        Expression::GridRepeaterCacheAccess {
2578            layout_cache_prop,
2579            index,
2580            repeater_index: _,
2581            stride: _,
2582            child_offset,
2583            inner_repeater_index,
2584            entries_per_item,
2585        } => {
2586            if inner_repeater_index.is_some() {
2587                write!(
2588                    f,
2589                    "{0:?}[{0:?}[{1}] + $repeater_index * $stride + {2} + $inner_repeater_index * {3}]",
2590                    layout_cache_prop, index, child_offset, entries_per_item
2591                )
2592            } else {
2593                write!(
2594                    f,
2595                    "{0:?}[{0:?}[{1}] + $repeater_index * $stride + {2}]",
2596                    layout_cache_prop, index, child_offset
2597                )
2598            }
2599        }
2600        Expression::OrganizeGridLayout(..) => write!(f, "organize_grid_layout(..)"),
2601        Expression::ComputeBoxLayoutInfo { .. } => write!(f, "layout_info(..)"),
2602        Expression::ComputeGridLayoutInfo { .. } => write!(f, "grid_layout_info(..)"),
2603        Expression::SolveBoxLayout(..) => write!(f, "solve_box_layout(..)"),
2604        Expression::SolveGridLayout { .. } => write!(f, "solve_grid_layout(..)"),
2605        Expression::SolveFlexboxLayout(..) => write!(f, "solve_flexbox_layout(..)"),
2606        Expression::ComputeFlexboxLayoutInfo { .. } => write!(f, "flexbox_layout_info(..)"),
2607        Expression::MinMax { ty: _, op, lhs, rhs } => {
2608            match op {
2609                MinMaxOp::Min => write!(f, "min(")?,
2610                MinMaxOp::Max => write!(f, "max(")?,
2611            }
2612            pretty_print(f, lhs)?;
2613            write!(f, ", ")?;
2614            pretty_print(f, rhs)?;
2615            write!(f, ")")
2616        }
2617        Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>"),
2618        Expression::DebugHook { expression, id, synthetic } => {
2619            write!(f, "debug-hook(")?;
2620            pretty_print(f, expression)?;
2621            if *synthetic {
2622                write!(f, " SYNTHETIC")?;
2623            }
2624            write!(f, "\"{id}\")")
2625        }
2626        Expression::Closure { arg_name, expression } => {
2627            let display_name = arg_name.strip_prefix("local_").unwrap_or(arg_name);
2628            write!(f, "({display_name}) => ")?;
2629            pretty_print(f, expression)
2630        }
2631    }
2632}