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