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