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