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