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: &NamedReference) => nr.ty(),
427 Callable::Function(nr: &NamedReference) => nr.ty(),
428 Callable::Builtin(b: &BuiltinFunction) => 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: &Box, to: Type::Model } => model_inner_type(model:from),
1436 Expression::CodeBlock(cb: &Vec) => cb.last().map_or(default:Type::Invalid, f:model_inner_type),
1437 _ => match model.ty() {
1438 Type::Float32 | Type::Int32 => Type::Int32,
1439 Type::Array(elem: Rc) => (*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: &SourceLocation| x.span()).unwrap_or_default()
1546 }
1547 fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
1548 self.span.as_ref().and_then(|x: &SourceLocation| 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}
1767