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