1use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
5use crate::langtype::{
6 BuiltinElement, BuiltinPublicStruct, EnumerationValue, Function, Struct, Type,
7};
8use crate::layout::Orientation;
9use crate::lookup::LookupCtx;
10use crate::object_tree::*;
11use crate::parser::{NodeOrToken, SyntaxNode};
12use crate::typeregister;
13use core::cell::RefCell;
14use smol_str::{SmolStr, format_smolstr};
15use std::cell::Cell;
16use std::collections::HashMap;
17use std::rc::{Rc, Weak};
18
19pub use crate::namedreference::NamedReference;
21pub use crate::passes::resolving;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum BuiltinFunction {
26 GetWindowScaleFactor,
27 GetWindowDefaultFontSize,
28 AnimationTick,
29 Debug,
30 Mod,
31 Round,
32 Ceil,
33 Floor,
34 Abs,
35 Sqrt,
36 Cos,
37 Sin,
38 Tan,
39 ACos,
40 ASin,
41 ATan,
42 ATan2,
43 Log,
44 Ln,
45 Pow,
46 Exp,
47 ToFixed,
48 ToPrecision,
49 SetFocusItem,
50 ClearFocusItem,
51 ShowPopupWindow,
52 ClosePopupWindow,
53 ShowPopupMenu,
60 ShowPopupMenuInternal,
64 SetSelectionOffsets,
65 ItemFontMetrics,
66 StringToFloat,
68 StringIsFloat,
70 StringIsEmpty,
72 StringCharacterCount,
74 StringToLowercase,
75 StringToUppercase,
76 ColorRgbaStruct,
77 ColorHsvaStruct,
78 ColorOklchStruct,
79 ColorBrighter,
80 ColorDarker,
81 ColorTransparentize,
82 ColorMix,
83 ColorWithAlpha,
84 ImageSize,
85 ArrayLength,
86 Rgb,
87 Hsv,
88 Oklch,
89 ColorScheme,
90 SupportsNativeMenuBar,
91 SetupMenuBar,
98 Use24HourFormat,
99 MonthDayCount,
100 MonthOffset,
101 FormatDate,
102 DateNow,
103 ValidDate,
104 ParseDate,
105 TextInputFocused,
106 SetTextInputFocused,
107 ImplicitLayoutInfo(Orientation),
108 ItemAbsolutePosition,
109 RegisterCustomFontByPath,
110 RegisterCustomFontByMemory,
111 RegisterBitmapFont,
112 Translate,
113 UpdateTimers,
114 DetectOperatingSystem,
115 StartTimer,
116 StopTimer,
117 RestartTimer,
118 ParseMarkdown,
119 EscapeMarkdown,
120}
121
122#[derive(Debug, Clone)]
123pub enum BuiltinMacroFunction {
129 Min,
131 Max,
133 Clamp,
135 Mod,
137 Abs,
139 Sign,
141 CubicBezier,
142 Rgb,
145 Hsv,
146 Oklch,
147 Debug,
149}
150
151macro_rules! declare_builtin_function_types {
152 ($( $Name:ident $(($Pattern:tt))? : ($( $Arg:expr ),*) -> $ReturnType:expr $(,)? )*) => {
153 #[allow(non_snake_case)]
154 pub struct BuiltinFunctionTypes {
155 $(pub $Name : Rc<Function>),*
156 }
157 impl BuiltinFunctionTypes {
158 pub fn new() -> Self {
159 Self {
160 $($Name : Rc::new(Function{
161 args: vec![$($Arg),*],
162 return_type: $ReturnType,
163 arg_names: Vec::new(),
164 })),*
165 }
166 }
167
168 pub fn ty(&self, function: &BuiltinFunction) -> Rc<Function> {
169 match function {
170 $(BuiltinFunction::$Name $(($Pattern))? => self.$Name.clone()),*
171 }
172 }
173 }
174 };
175}
176
177declare_builtin_function_types!(
178 GetWindowScaleFactor: () -> Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)]),
179 GetWindowDefaultFontSize: () -> Type::LogicalLength,
180 AnimationTick: () -> Type::Duration,
181 Debug: (Type::String) -> Type::Void,
182 Mod: (Type::Int32, Type::Int32) -> Type::Int32,
183 Round: (Type::Float32) -> Type::Int32,
184 Ceil: (Type::Float32) -> Type::Int32,
185 Floor: (Type::Float32) -> Type::Int32,
186 Sqrt: (Type::Float32) -> Type::Float32,
187 Abs: (Type::Float32) -> Type::Float32,
188 Cos: (Type::Angle) -> Type::Float32,
189 Sin: (Type::Angle) -> Type::Float32,
190 Tan: (Type::Angle) -> Type::Float32,
191 ACos: (Type::Float32) -> Type::Angle,
192 ASin: (Type::Float32) -> Type::Angle,
193 ATan: (Type::Float32) -> Type::Angle,
194 ATan2: (Type::Float32, Type::Float32) -> Type::Angle,
195 Log: (Type::Float32, Type::Float32) -> Type::Float32,
196 Ln: (Type::Float32) -> Type::Float32,
197 Pow: (Type::Float32, Type::Float32) -> Type::Float32,
198 Exp: (Type::Float32) -> Type::Float32,
199 ToFixed: (Type::Float32, Type::Int32) -> Type::String,
200 ToPrecision: (Type::Float32, Type::Int32) -> Type::String,
201 SetFocusItem: (Type::ElementReference) -> Type::Void,
202 ClearFocusItem: (Type::ElementReference) -> Type::Void,
203 ShowPopupWindow: (Type::ElementReference) -> Type::Void,
204 ClosePopupWindow: (Type::ElementReference) -> Type::Void,
205 ShowPopupMenu: (Type::ElementReference, Type::ElementReference, typeregister::logical_point_type().into()) -> Type::Void,
206 ShowPopupMenuInternal: (Type::ElementReference, Type::Model, typeregister::logical_point_type().into()) -> Type::Void,
207 SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void,
208 ItemFontMetrics: (Type::ElementReference) -> typeregister::font_metrics_type(),
209 StringToFloat: (Type::String) -> Type::Float32,
210 StringIsFloat: (Type::String) -> Type::Bool,
211 StringIsEmpty: (Type::String) -> Type::Bool,
212 StringCharacterCount: (Type::String) -> Type::Int32,
213 StringToLowercase: (Type::String) -> Type::String,
214 StringToUppercase: (Type::String) -> Type::String,
215 ImplicitLayoutInfo(..): (Type::ElementReference) -> typeregister::layout_info_type().into(),
216 ColorRgbaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct {
217 fields: IntoIterator::into_iter([
218 (SmolStr::new_static("red"), Type::Int32),
219 (SmolStr::new_static("green"), Type::Int32),
220 (SmolStr::new_static("blue"), Type::Int32),
221 (SmolStr::new_static("alpha"), Type::Int32),
222 ])
223 .collect(),
224 name: BuiltinPublicStruct::Color.into(),
225 })),
226 ColorHsvaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct {
227 fields: IntoIterator::into_iter([
228 (SmolStr::new_static("hue"), Type::Float32),
229 (SmolStr::new_static("saturation"), Type::Float32),
230 (SmolStr::new_static("value"), Type::Float32),
231 (SmolStr::new_static("alpha"), Type::Float32),
232 ])
233 .collect(),
234 name: BuiltinPublicStruct::Color.into(),
235 })),
236 ColorOklchStruct: (Type::Color) -> Type::Struct(Rc::new(Struct {
237 fields: IntoIterator::into_iter([
238 (SmolStr::new_static("lightness"), Type::Float32),
239 (SmolStr::new_static("chroma"), Type::Float32),
240 (SmolStr::new_static("hue"), Type::Float32),
241 (SmolStr::new_static("alpha"), Type::Float32),
242 ])
243 .collect(),
244 name: BuiltinPublicStruct::Color.into(),
245 })),
246 ColorBrighter: (Type::Brush, Type::Float32) -> Type::Brush,
247 ColorDarker: (Type::Brush, Type::Float32) -> Type::Brush,
248 ColorTransparentize: (Type::Brush, Type::Float32) -> Type::Brush,
249 ColorWithAlpha: (Type::Brush, Type::Float32) -> Type::Brush,
250 ColorMix: (Type::Color, Type::Color, Type::Float32) -> Type::Color,
251 ImageSize: (Type::Image) -> Type::Struct(Rc::new(Struct {
252 fields: IntoIterator::into_iter([
253 (SmolStr::new_static("width"), Type::Int32),
254 (SmolStr::new_static("height"), Type::Int32),
255 ])
256 .collect(),
257 name: crate::langtype::BuiltinPrivateStruct::Size.into(),
258 })),
259 ArrayLength: (Type::Model) -> Type::Int32,
260 Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color,
261 Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
262 Oklch: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
263 ColorScheme: () -> Type::Enumeration(
264 typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone()),
265 ),
266 SupportsNativeMenuBar: () -> Type::Bool,
267 SetupMenuBar: (Type::Model, typeregister::noarg_callback_type(), typeregister::noarg_callback_type()) -> Type::Void,
269 MonthDayCount: (Type::Int32, Type::Int32) -> Type::Int32,
270 MonthOffset: (Type::Int32, Type::Int32) -> Type::Int32,
271 FormatDate: (Type::String, Type::Int32, Type::Int32, Type::Int32) -> Type::String,
272 TextInputFocused: () -> Type::Bool,
273 DateNow: () -> Type::Array(Rc::new(Type::Int32)),
274 ValidDate: (Type::String, Type::String) -> Type::Bool,
275 ParseDate: (Type::String, Type::String) -> Type::Array(Rc::new(Type::Int32)),
276 SetTextInputFocused: (Type::Bool) -> Type::Void,
277 ItemAbsolutePosition: (Type::ElementReference) -> typeregister::logical_point_type().into(),
278 RegisterCustomFontByPath: (Type::String) -> Type::Void,
279 RegisterCustomFontByMemory: (Type::Int32) -> Type::Void,
280 RegisterBitmapFont: (Type::Int32) -> Type::Void,
281 Translate: (Type::String, Type::String, Type::String, Type::Array(Type::String.into())) -> Type::String,
283 Use24HourFormat: () -> Type::Bool,
284 UpdateTimers: () -> Type::Void,
285 DetectOperatingSystem: () -> Type::Enumeration(
286 typeregister::BUILTIN.with(|e| e.enums.OperatingSystemType.clone()),
287 ),
288 StartTimer: (Type::ElementReference) -> Type::Void,
289 StopTimer: (Type::ElementReference) -> Type::Void,
290 RestartTimer: (Type::ElementReference) -> Type::Void,
291 EscapeMarkdown: (Type::String) -> Type::String,
292 ParseMarkdown: (Type::String) -> Type::StyledText
293);
294
295impl BuiltinFunction {
296 pub fn ty(&self) -> Rc<Function> {
297 thread_local! {
298 static TYPES: BuiltinFunctionTypes = BuiltinFunctionTypes::new();
299 }
300 TYPES.with(|types| types.ty(self))
301 }
302
303 fn is_const(&self, global_analysis: Option<&crate::passes::GlobalAnalysis>) -> bool {
305 match self {
306 BuiltinFunction::GetWindowScaleFactor => {
307 global_analysis.is_some_and(|x| x.const_scale_factor.is_some())
308 }
309 BuiltinFunction::GetWindowDefaultFontSize => {
310 global_analysis.is_some_and(|x| x.default_font_size.is_const())
311 }
312 BuiltinFunction::AnimationTick => false,
313 BuiltinFunction::ColorScheme => false,
314 BuiltinFunction::SupportsNativeMenuBar => false,
315 BuiltinFunction::SetupMenuBar => false,
316 BuiltinFunction::MonthDayCount => false,
317 BuiltinFunction::MonthOffset => false,
318 BuiltinFunction::FormatDate => false,
319 BuiltinFunction::DateNow => false,
320 BuiltinFunction::ValidDate => false,
321 BuiltinFunction::ParseDate => false,
322 BuiltinFunction::Debug => true,
324 BuiltinFunction::Mod
325 | BuiltinFunction::Round
326 | BuiltinFunction::Ceil
327 | BuiltinFunction::Floor
328 | BuiltinFunction::Abs
329 | BuiltinFunction::Sqrt
330 | BuiltinFunction::Cos
331 | BuiltinFunction::Sin
332 | BuiltinFunction::Tan
333 | BuiltinFunction::ACos
334 | BuiltinFunction::ASin
335 | BuiltinFunction::Log
336 | BuiltinFunction::Ln
337 | BuiltinFunction::Pow
338 | BuiltinFunction::Exp
339 | BuiltinFunction::ATan
340 | BuiltinFunction::ATan2
341 | BuiltinFunction::ToFixed
342 | BuiltinFunction::ToPrecision => true,
343 BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
344 BuiltinFunction::ShowPopupWindow
345 | BuiltinFunction::ClosePopupWindow
346 | BuiltinFunction::ShowPopupMenu
347 | BuiltinFunction::ShowPopupMenuInternal => false,
348 BuiltinFunction::SetSelectionOffsets => false,
349 BuiltinFunction::ItemFontMetrics => false, BuiltinFunction::StringToFloat
351 | BuiltinFunction::StringIsFloat
352 | BuiltinFunction::StringIsEmpty
353 | BuiltinFunction::StringCharacterCount
354 | BuiltinFunction::StringToLowercase
355 | BuiltinFunction::StringToUppercase => true,
356 BuiltinFunction::ColorRgbaStruct
357 | BuiltinFunction::ColorHsvaStruct
358 | BuiltinFunction::ColorOklchStruct
359 | BuiltinFunction::ColorBrighter
360 | BuiltinFunction::ColorDarker
361 | BuiltinFunction::ColorTransparentize
362 | BuiltinFunction::ColorMix
363 | BuiltinFunction::ColorWithAlpha => true,
364 #[cfg(not(target_arch = "wasm32"))]
369 BuiltinFunction::ImageSize => true,
370 #[cfg(target_arch = "wasm32")]
371 BuiltinFunction::ImageSize => false,
372 BuiltinFunction::ArrayLength => true,
373 BuiltinFunction::Rgb => true,
374 BuiltinFunction::Hsv => true,
375 BuiltinFunction::Oklch => true,
376 BuiltinFunction::SetTextInputFocused => false,
377 BuiltinFunction::TextInputFocused => false,
378 BuiltinFunction::ImplicitLayoutInfo(_) => false,
379 BuiltinFunction::ItemAbsolutePosition => true,
380 BuiltinFunction::RegisterCustomFontByPath
381 | BuiltinFunction::RegisterCustomFontByMemory
382 | BuiltinFunction::RegisterBitmapFont => false,
383 BuiltinFunction::Translate => false,
384 BuiltinFunction::Use24HourFormat => false,
385 BuiltinFunction::UpdateTimers => false,
386 BuiltinFunction::DetectOperatingSystem => true,
387 BuiltinFunction::StartTimer => false,
388 BuiltinFunction::StopTimer => false,
389 BuiltinFunction::RestartTimer => false,
390 BuiltinFunction::ParseMarkdown | BuiltinFunction::EscapeMarkdown => false,
391 }
392 }
393
394 pub fn is_pure(&self) -> bool {
396 match self {
397 BuiltinFunction::GetWindowScaleFactor => true,
398 BuiltinFunction::GetWindowDefaultFontSize => true,
399 BuiltinFunction::AnimationTick => true,
400 BuiltinFunction::ColorScheme => true,
401 BuiltinFunction::SupportsNativeMenuBar => true,
402 BuiltinFunction::SetupMenuBar => false,
403 BuiltinFunction::MonthDayCount => true,
404 BuiltinFunction::MonthOffset => true,
405 BuiltinFunction::FormatDate => true,
406 BuiltinFunction::DateNow => true,
407 BuiltinFunction::ValidDate => true,
408 BuiltinFunction::ParseDate => true,
409 BuiltinFunction::Debug => true,
411 BuiltinFunction::Mod
412 | BuiltinFunction::Round
413 | BuiltinFunction::Ceil
414 | BuiltinFunction::Floor
415 | BuiltinFunction::Abs
416 | BuiltinFunction::Sqrt
417 | BuiltinFunction::Cos
418 | BuiltinFunction::Sin
419 | BuiltinFunction::Tan
420 | BuiltinFunction::ACos
421 | BuiltinFunction::ASin
422 | BuiltinFunction::Log
423 | BuiltinFunction::Ln
424 | BuiltinFunction::Pow
425 | BuiltinFunction::Exp
426 | BuiltinFunction::ATan
427 | BuiltinFunction::ATan2
428 | BuiltinFunction::ToFixed
429 | BuiltinFunction::ToPrecision => true,
430 BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
431 BuiltinFunction::ShowPopupWindow
432 | BuiltinFunction::ClosePopupWindow
433 | BuiltinFunction::ShowPopupMenu
434 | BuiltinFunction::ShowPopupMenuInternal => false,
435 BuiltinFunction::SetSelectionOffsets => false,
436 BuiltinFunction::ItemFontMetrics => true,
437 BuiltinFunction::StringToFloat
438 | BuiltinFunction::StringIsFloat
439 | BuiltinFunction::StringIsEmpty
440 | BuiltinFunction::StringCharacterCount
441 | BuiltinFunction::StringToLowercase
442 | BuiltinFunction::StringToUppercase => true,
443 BuiltinFunction::ColorRgbaStruct
444 | BuiltinFunction::ColorHsvaStruct
445 | BuiltinFunction::ColorOklchStruct
446 | BuiltinFunction::ColorBrighter
447 | BuiltinFunction::ColorDarker
448 | BuiltinFunction::ColorTransparentize
449 | BuiltinFunction::ColorMix
450 | BuiltinFunction::ColorWithAlpha => true,
451 BuiltinFunction::ImageSize => true,
452 BuiltinFunction::ArrayLength => true,
453 BuiltinFunction::Rgb => true,
454 BuiltinFunction::Hsv => true,
455 BuiltinFunction::Oklch => true,
456 BuiltinFunction::ImplicitLayoutInfo(_) => true,
457 BuiltinFunction::ItemAbsolutePosition => true,
458 BuiltinFunction::SetTextInputFocused => false,
459 BuiltinFunction::TextInputFocused => true,
460 BuiltinFunction::RegisterCustomFontByPath
461 | BuiltinFunction::RegisterCustomFontByMemory
462 | BuiltinFunction::RegisterBitmapFont => false,
463 BuiltinFunction::Translate => true,
464 BuiltinFunction::Use24HourFormat => true,
465 BuiltinFunction::UpdateTimers => false,
466 BuiltinFunction::DetectOperatingSystem => true,
467 BuiltinFunction::StartTimer => false,
468 BuiltinFunction::StopTimer => false,
469 BuiltinFunction::RestartTimer => false,
470 BuiltinFunction::ParseMarkdown | BuiltinFunction::EscapeMarkdown => true,
471 }
472 }
473}
474
475#[derive(Debug, Clone)]
477pub enum Callable {
478 Callback(NamedReference),
479 Function(NamedReference),
480 Builtin(BuiltinFunction),
481}
482impl Callable {
483 pub fn ty(&self) -> Type {
484 match self {
485 Callable::Callback(nr) => nr.ty(),
486 Callable::Function(nr) => nr.ty(),
487 Callable::Builtin(b) => Type::Function(b.ty()),
488 }
489 }
490}
491impl From<BuiltinFunction> for Callable {
492 fn from(function: BuiltinFunction) -> Self {
493 Self::Builtin(function)
494 }
495}
496
497#[derive(Debug, Clone, Eq, PartialEq)]
498pub enum OperatorClass {
499 ComparisonOp,
500 LogicalOp,
501 ArithmeticOp,
502}
503
504pub fn operator_class(op: char) -> OperatorClass {
506 match op {
507 '=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp,
508 '&' | '|' => OperatorClass::LogicalOp,
509 '+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp,
510 _ => panic!("Invalid operator {op:?}"),
511 }
512}
513
514macro_rules! declare_units {
515 ($( $(#[$m:meta])* $ident:ident = $string:literal -> $ty:ident $(* $factor:expr)? ,)*) => {
516 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
518 pub enum Unit {
519 $($(#[$m])* $ident,)*
520 }
521
522 impl std::fmt::Display for Unit {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 match self {
525 $(Self::$ident => write!(f, $string), )*
526 }
527 }
528 }
529
530 impl std::str::FromStr for Unit {
531 type Err = ();
532 fn from_str(s: &str) -> Result<Self, Self::Err> {
533 match s {
534 $($string => Ok(Self::$ident), )*
535 _ => Err(())
536 }
537 }
538 }
539
540 impl Unit {
541 pub fn ty(self) -> Type {
542 match self {
543 $(Self::$ident => Type::$ty, )*
544 }
545 }
546
547 pub fn normalize(self, x: f64) -> f64 {
548 match self {
549 $(Self::$ident => x $(* $factor as f64)?, )*
550 }
551 }
552
553 }
554 };
555}
556
557declare_units! {
558 None = "" -> Float32,
560 Percent = "%" -> Percent,
562
563 Phx = "phx" -> PhysicalLength,
567 Px = "px" -> LogicalLength,
569 Cm = "cm" -> LogicalLength * 37.8,
571 Mm = "mm" -> LogicalLength * 3.78,
573 In = "in" -> LogicalLength * 96,
575 Pt = "pt" -> LogicalLength * 96./72.,
577 Rem = "rem" -> Rem,
579
580 S = "s" -> Duration * 1000,
584 Ms = "ms" -> Duration,
586
587 Deg = "deg" -> Angle,
591 Grad = "grad" -> Angle * 360./180.,
593 Turn = "turn" -> Angle * 360.,
595 Rad = "rad" -> Angle * 360./std::f32::consts::TAU,
597}
598
599impl Default for Unit {
600 fn default() -> Self {
601 Self::None
602 }
603}
604
605#[derive(Debug, Clone, Copy)]
606pub enum MinMaxOp {
607 Min,
608 Max,
609}
610
611#[derive(Debug, Clone, Default)]
613pub enum Expression {
614 #[default]
616 Invalid,
617 Uncompiled(SyntaxNode),
619
620 StringLiteral(SmolStr),
622 NumberLiteral(f64, Unit),
624 BoolLiteral(bool),
626
627 PropertyReference(NamedReference),
629
630 ElementReference(Weak<RefCell<Element>>),
633
634 RepeaterIndexReference {
639 element: Weak<RefCell<Element>>,
640 },
641
642 RepeaterModelReference {
647 element: Weak<RefCell<Element>>,
648 },
649
650 FunctionParameterReference {
652 index: usize,
653 ty: Type,
654 },
655
656 StoreLocalVariable {
658 name: SmolStr,
659 value: Box<Expression>,
660 },
661
662 ReadLocalVariable {
665 name: SmolStr,
666 ty: Type,
667 },
668
669 StructFieldAccess {
671 base: Box<Expression>,
673 name: SmolStr,
674 },
675
676 ArrayIndex {
678 array: Box<Expression>,
680 index: Box<Expression>,
681 },
682
683 Cast {
685 from: Box<Expression>,
686 to: Type,
687 },
688
689 CodeBlock(Vec<Expression>),
691
692 FunctionCall {
694 function: Callable,
695 arguments: Vec<Expression>,
696 source_location: Option<SourceLocation>,
697 },
698
699 SelfAssignment {
701 lhs: Box<Expression>,
702 rhs: Box<Expression>,
703 op: char,
705 node: Option<NodeOrToken>,
706 },
707
708 BinaryExpression {
709 lhs: Box<Expression>,
710 rhs: Box<Expression>,
711 op: char,
713 },
714
715 UnaryOp {
716 sub: Box<Expression>,
717 op: char,
719 },
720
721 ImageReference {
722 resource_ref: ImageReference,
723 source_location: Option<SourceLocation>,
724 nine_slice: Option<[u16; 4]>,
725 },
726
727 Condition {
728 condition: Box<Expression>,
729 true_expr: Box<Expression>,
730 false_expr: Box<Expression>,
731 },
732
733 Array {
734 element_ty: Type,
735 values: Vec<Expression>,
736 },
737 Struct {
738 ty: Rc<Struct>,
739 values: HashMap<SmolStr, Expression>,
740 },
741
742 PathData(Path),
743
744 EasingCurve(EasingCurve),
745
746 LinearGradient {
747 angle: Box<Expression>,
748 stops: Vec<(Expression, Expression)>,
750 },
751
752 RadialGradient {
753 stops: Vec<(Expression, Expression)>,
755 },
756
757 ConicGradient {
758 from_angle: Box<Expression>,
760 stops: Vec<(Expression, Expression)>,
762 },
763
764 EnumerationValue(EnumerationValue),
765
766 ReturnStatement(Option<Box<Expression>>),
767
768 LayoutCacheAccess {
769 layout_cache_prop: NamedReference,
771 index: usize,
773 repeater_index: Option<Box<Expression>>,
776 entries_per_item: usize,
781 },
782
783 OrganizeGridLayout(crate::layout::GridLayout),
785
786 ComputeLayoutInfo(crate::layout::Layout, crate::layout::Orientation),
789 ComputeGridLayoutInfo {
790 layout_organized_data_prop: NamedReference,
791 layout: crate::layout::GridLayout,
792 orientation: crate::layout::Orientation,
793 },
794 SolveLayout(crate::layout::Layout, crate::layout::Orientation),
795 SolveGridLayout {
796 layout_organized_data_prop: NamedReference,
797 layout: crate::layout::GridLayout,
798 orientation: crate::layout::Orientation,
799 },
800
801 MinMax {
802 ty: Type,
803 op: MinMaxOp,
804 lhs: Box<Expression>,
805 rhs: Box<Expression>,
806 },
807
808 DebugHook {
809 expression: Box<Expression>,
810 id: SmolStr,
811 },
812
813 EmptyComponentFactory,
814}
815
816impl Expression {
817 pub fn ty(&self) -> Type {
819 match self {
820 Expression::Invalid => Type::Invalid,
821 Expression::Uncompiled(_) => Type::Invalid,
822 Expression::StringLiteral(_) => Type::String,
823 Expression::NumberLiteral(_, unit) => unit.ty(),
824 Expression::BoolLiteral(_) => Type::Bool,
825 Expression::PropertyReference(nr) => nr.ty(),
826 Expression::ElementReference(_) => Type::ElementReference,
827 Expression::RepeaterIndexReference { .. } => Type::Int32,
828 Expression::RepeaterModelReference { element } => element
829 .upgrade()
830 .unwrap()
831 .borrow()
832 .repeated
833 .as_ref()
834 .map_or(Type::Invalid, |e| model_inner_type(&e.model)),
835 Expression::FunctionParameterReference { ty, .. } => ty.clone(),
836 Expression::StructFieldAccess { base, name } => match base.ty() {
837 Type::Struct(s) => s.fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone(),
838 _ => Type::Invalid,
839 },
840 Expression::ArrayIndex { array, .. } => match array.ty() {
841 Type::Array(ty) => (*ty).clone(),
842 _ => Type::Invalid,
843 },
844 Expression::Cast { to, .. } => to.clone(),
845 Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
846 Expression::FunctionCall { function, .. } => match function.ty() {
847 Type::Function(f) | Type::Callback(f) => f.return_type.clone(),
848 _ => Type::Invalid,
849 },
850 Expression::SelfAssignment { .. } => Type::Void,
851 Expression::ImageReference { .. } => Type::Image,
852 Expression::Condition { condition: _, true_expr, false_expr } => {
853 let true_type = true_expr.ty();
854 let false_type = false_expr.ty();
855 if true_type == false_type {
856 true_type
857 } else if true_type == Type::Invalid {
858 false_type
859 } else if false_type == Type::Invalid {
860 true_type
861 } else {
862 Type::Void
863 }
864 }
865 Expression::BinaryExpression { op, lhs, rhs } => {
866 if operator_class(*op) != OperatorClass::ArithmeticOp {
867 Type::Bool
868 } else if *op == '+' || *op == '-' {
869 let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty());
870 if rhs_ty == lhs_ty { rhs_ty } else { Type::Invalid }
871 } else {
872 debug_assert!(*op == '*' || *op == '/');
873 let unit_vec = |ty| {
874 if let Type::UnitProduct(v) = ty {
875 v
876 } else if let Some(u) = ty.default_unit() {
877 vec![(u, 1)]
878 } else {
879 Vec::new()
880 }
881 };
882 let mut l_units = unit_vec(lhs.ty());
883 let mut r_units = unit_vec(rhs.ty());
884 if *op == '/' {
885 for (_, power) in &mut r_units {
886 *power = -*power;
887 }
888 }
889 for (unit, power) in r_units {
890 if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) {
891 *p += power;
892 } else {
893 l_units.push((unit, power));
894 }
895 }
896
897 l_units.retain(|(_, p)| *p != 0);
899 l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) {
900 std::cmp::Ordering::Equal => u1.cmp(u2),
901 x => x,
902 });
903
904 if l_units.is_empty() {
905 Type::Float32
906 } else if l_units.len() == 1 && l_units[0].1 == 1 {
907 l_units[0].0.ty()
908 } else {
909 Type::UnitProduct(l_units)
910 }
911 }
912 }
913 Expression::UnaryOp { sub, .. } => sub.ty(),
914 Expression::Array { element_ty, .. } => Type::Array(Rc::new(element_ty.clone())),
915 Expression::Struct { ty, .. } => ty.clone().into(),
916 Expression::PathData { .. } => Type::PathData,
917 Expression::StoreLocalVariable { .. } => Type::Void,
918 Expression::ReadLocalVariable { ty, .. } => ty.clone(),
919 Expression::EasingCurve(_) => Type::Easing,
920 Expression::LinearGradient { .. } => Type::Brush,
921 Expression::RadialGradient { .. } => Type::Brush,
922 Expression::ConicGradient { .. } => Type::Brush,
923 Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
924 Expression::ReturnStatement(_) => Type::Invalid,
926 Expression::LayoutCacheAccess { .. } => Type::LogicalLength,
927 Expression::OrganizeGridLayout(..) => Type::ArrayOfU16,
928 Expression::ComputeLayoutInfo(..) => typeregister::layout_info_type().into(),
929 Expression::ComputeGridLayoutInfo { .. } => typeregister::layout_info_type().into(),
930 Expression::SolveLayout(..) => Type::LayoutCache,
931 Expression::SolveGridLayout { .. } => Type::LayoutCache,
932 Expression::MinMax { ty, .. } => ty.clone(),
933 Expression::EmptyComponentFactory => Type::ComponentFactory,
934 Expression::DebugHook { expression, .. } => expression.ty(),
935 }
936 }
937
938 pub fn visit(&self, mut visitor: impl FnMut(&Self)) {
940 match self {
941 Expression::Invalid => {}
942 Expression::Uncompiled(_) => {}
943 Expression::StringLiteral(_) => {}
944 Expression::NumberLiteral(_, _) => {}
945 Expression::BoolLiteral(_) => {}
946 Expression::PropertyReference { .. } => {}
947 Expression::FunctionParameterReference { .. } => {}
948 Expression::ElementReference(_) => {}
949 Expression::StructFieldAccess { base, .. } => visitor(base),
950 Expression::ArrayIndex { array, index } => {
951 visitor(array);
952 visitor(index);
953 }
954 Expression::RepeaterIndexReference { .. } => {}
955 Expression::RepeaterModelReference { .. } => {}
956 Expression::Cast { from, .. } => visitor(from),
957 Expression::CodeBlock(sub) => {
958 sub.iter().for_each(visitor);
959 }
960 Expression::FunctionCall { function: _, arguments, source_location: _ } => {
961 arguments.iter().for_each(visitor);
962 }
963 Expression::SelfAssignment { lhs, rhs, .. } => {
964 visitor(lhs);
965 visitor(rhs);
966 }
967 Expression::ImageReference { .. } => {}
968 Expression::Condition { condition, true_expr, false_expr } => {
969 visitor(condition);
970 visitor(true_expr);
971 visitor(false_expr);
972 }
973 Expression::BinaryExpression { lhs, rhs, .. } => {
974 visitor(lhs);
975 visitor(rhs);
976 }
977 Expression::UnaryOp { sub, .. } => visitor(sub),
978 Expression::Array { values, .. } => {
979 for x in values {
980 visitor(x);
981 }
982 }
983 Expression::Struct { values, .. } => {
984 for x in values.values() {
985 visitor(x);
986 }
987 }
988 Expression::PathData(data) => match data {
989 Path::Elements(elements) => {
990 for element in elements {
991 element.bindings.values().for_each(|binding| visitor(&binding.borrow()))
992 }
993 }
994 Path::Events(events, coordinates) => {
995 events.iter().chain(coordinates.iter()).for_each(visitor);
996 }
997 Path::Commands(commands) => visitor(commands),
998 },
999 Expression::StoreLocalVariable { value, .. } => visitor(value),
1000 Expression::ReadLocalVariable { .. } => {}
1001 Expression::EasingCurve(_) => {}
1002 Expression::LinearGradient { angle, stops } => {
1003 visitor(angle);
1004 for (c, s) in stops {
1005 visitor(c);
1006 visitor(s);
1007 }
1008 }
1009 Expression::RadialGradient { stops } => {
1010 for (c, s) in stops {
1011 visitor(c);
1012 visitor(s);
1013 }
1014 }
1015 Expression::ConicGradient { from_angle, stops } => {
1016 visitor(from_angle);
1017 for (c, s) in stops {
1018 visitor(c);
1019 visitor(s);
1020 }
1021 }
1022 Expression::EnumerationValue(_) => {}
1023 Expression::ReturnStatement(expr) => {
1024 expr.as_deref().map(visitor);
1025 }
1026 Expression::LayoutCacheAccess { repeater_index, .. } => {
1027 repeater_index.as_deref().map(visitor);
1028 }
1029 Expression::OrganizeGridLayout(..) => {}
1030 Expression::ComputeLayoutInfo(..) => {}
1031 Expression::ComputeGridLayoutInfo { .. } => {}
1032 Expression::SolveLayout(..) => {}
1033 Expression::SolveGridLayout { .. } => {}
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 pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) {
1044 match self {
1045 Expression::Invalid => {}
1046 Expression::Uncompiled(_) => {}
1047 Expression::StringLiteral(_) => {}
1048 Expression::NumberLiteral(_, _) => {}
1049 Expression::BoolLiteral(_) => {}
1050 Expression::PropertyReference { .. } => {}
1051 Expression::FunctionParameterReference { .. } => {}
1052 Expression::ElementReference(_) => {}
1053 Expression::StructFieldAccess { base, .. } => visitor(base),
1054 Expression::ArrayIndex { array, index } => {
1055 visitor(array);
1056 visitor(index);
1057 }
1058 Expression::RepeaterIndexReference { .. } => {}
1059 Expression::RepeaterModelReference { .. } => {}
1060 Expression::Cast { from, .. } => visitor(from),
1061 Expression::CodeBlock(sub) => {
1062 sub.iter_mut().for_each(visitor);
1063 }
1064 Expression::FunctionCall { function: _, arguments, source_location: _ } => {
1065 arguments.iter_mut().for_each(visitor);
1066 }
1067 Expression::SelfAssignment { lhs, rhs, .. } => {
1068 visitor(lhs);
1069 visitor(rhs);
1070 }
1071 Expression::ImageReference { .. } => {}
1072 Expression::Condition { condition, true_expr, false_expr } => {
1073 visitor(condition);
1074 visitor(true_expr);
1075 visitor(false_expr);
1076 }
1077 Expression::BinaryExpression { lhs, rhs, .. } => {
1078 visitor(lhs);
1079 visitor(rhs);
1080 }
1081 Expression::UnaryOp { sub, .. } => visitor(sub),
1082 Expression::Array { values, .. } => {
1083 for x in values {
1084 visitor(x);
1085 }
1086 }
1087 Expression::Struct { values, .. } => {
1088 for x in values.values_mut() {
1089 visitor(x);
1090 }
1091 }
1092 Expression::PathData(data) => match data {
1093 Path::Elements(elements) => {
1094 for element in elements {
1095 element
1096 .bindings
1097 .values_mut()
1098 .for_each(|binding| visitor(&mut binding.borrow_mut()))
1099 }
1100 }
1101 Path::Events(events, coordinates) => {
1102 events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
1103 }
1104 Path::Commands(commands) => visitor(commands),
1105 },
1106 Expression::StoreLocalVariable { value, .. } => visitor(value),
1107 Expression::ReadLocalVariable { .. } => {}
1108 Expression::EasingCurve(_) => {}
1109 Expression::LinearGradient { angle, stops } => {
1110 visitor(angle);
1111 for (c, s) in stops {
1112 visitor(c);
1113 visitor(s);
1114 }
1115 }
1116 Expression::RadialGradient { stops } => {
1117 for (c, s) in stops {
1118 visitor(c);
1119 visitor(s);
1120 }
1121 }
1122 Expression::ConicGradient { from_angle, stops } => {
1123 visitor(from_angle);
1124 for (c, s) in stops {
1125 visitor(c);
1126 visitor(s);
1127 }
1128 }
1129 Expression::EnumerationValue(_) => {}
1130 Expression::ReturnStatement(expr) => {
1131 expr.as_deref_mut().map(visitor);
1132 }
1133 Expression::LayoutCacheAccess { repeater_index, .. } => {
1134 repeater_index.as_deref_mut().map(visitor);
1135 }
1136 Expression::OrganizeGridLayout(..) => {}
1137 Expression::ComputeLayoutInfo(..) => {}
1138 Expression::ComputeGridLayoutInfo { .. } => {}
1139 Expression::SolveLayout(..) => {}
1140 Expression::SolveGridLayout { .. } => {}
1141 Expression::MinMax { lhs, rhs, .. } => {
1142 visitor(lhs);
1143 visitor(rhs);
1144 }
1145 Expression::EmptyComponentFactory => {}
1146 Expression::DebugHook { expression, .. } => visitor(expression),
1147 }
1148 }
1149
1150 pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) {
1152 visitor(self);
1153 self.visit(|e| e.visit_recursive(visitor));
1154 }
1155
1156 pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
1158 visitor(self);
1159 self.visit_mut(|e| e.visit_recursive_mut(visitor));
1160 }
1161
1162 pub fn is_constant(&self, ga: Option<&crate::passes::GlobalAnalysis>) -> bool {
1163 match self {
1164 Expression::Invalid => true,
1165 Expression::Uncompiled(_) => false,
1166 Expression::StringLiteral(_) => true,
1167 Expression::NumberLiteral(_, _) => true,
1168 Expression::BoolLiteral(_) => true,
1169 Expression::PropertyReference(nr) => nr.is_constant(),
1170 Expression::ElementReference(_) => false,
1171 Expression::RepeaterIndexReference { .. } => false,
1172 Expression::RepeaterModelReference { .. } => false,
1173 Expression::FunctionParameterReference { .. } => true,
1175 Expression::StructFieldAccess { base, .. } => base.is_constant(ga),
1176 Expression::ArrayIndex { array, index } => {
1177 array.is_constant(ga) && index.is_constant(ga)
1178 }
1179 Expression::Cast { from, .. } => from.is_constant(ga),
1180 Expression::CodeBlock(sub) => sub.iter().all(|s| s.is_constant(ga)),
1183 Expression::FunctionCall { function, arguments, .. } => {
1184 let is_const = match function {
1185 Callable::Builtin(b) => b.is_const(ga),
1186 Callable::Function(nr) => nr.is_constant(),
1187 Callable::Callback(..) => false,
1188 };
1189 is_const && arguments.iter().all(|a| a.is_constant(ga))
1190 }
1191 Expression::SelfAssignment { .. } => false,
1192 Expression::ImageReference { .. } => true,
1193 Expression::Condition { condition, false_expr, true_expr } => {
1194 condition.is_constant(ga) && false_expr.is_constant(ga) && true_expr.is_constant(ga)
1195 }
1196 Expression::BinaryExpression { lhs, rhs, .. } => {
1197 lhs.is_constant(ga) && rhs.is_constant(ga)
1198 }
1199 Expression::UnaryOp { sub, .. } => sub.is_constant(ga),
1200 Expression::Array { .. } => false,
1204 Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant(ga)),
1205 Expression::PathData(data) => match data {
1206 Path::Elements(elements) => elements
1207 .iter()
1208 .all(|element| element.bindings.values().all(|v| v.borrow().is_constant(ga))),
1209 Path::Events(_, _) => true,
1210 Path::Commands(_) => false,
1211 },
1212 Expression::StoreLocalVariable { value, .. } => value.is_constant(ga),
1213 Expression::ReadLocalVariable { .. } => true,
1215 Expression::EasingCurve(_) => true,
1216 Expression::LinearGradient { angle, stops } => {
1217 angle.is_constant(ga)
1218 && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1219 }
1220 Expression::RadialGradient { stops } => {
1221 stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1222 }
1223 Expression::ConicGradient { from_angle, stops } => {
1224 from_angle.is_constant(ga)
1225 && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1226 }
1227 Expression::EnumerationValue(_) => true,
1228 Expression::ReturnStatement(expr) => {
1229 expr.as_ref().is_none_or(|expr| expr.is_constant(ga))
1230 }
1231 Expression::LayoutCacheAccess { .. } => false,
1233 Expression::OrganizeGridLayout { .. } => false,
1234 Expression::ComputeLayoutInfo(..) => false,
1235 Expression::ComputeGridLayoutInfo { .. } => false,
1236 Expression::SolveLayout(..) => false,
1237 Expression::SolveGridLayout { .. } => false,
1238 Expression::MinMax { lhs, rhs, .. } => lhs.is_constant(ga) && rhs.is_constant(ga),
1239 Expression::EmptyComponentFactory => true,
1240 Expression::DebugHook { .. } => false,
1241 }
1242 }
1243
1244 #[must_use]
1246 pub fn maybe_convert_to(
1247 self,
1248 target_type: Type,
1249 node: &dyn Spanned,
1250 diag: &mut BuildDiagnostics,
1251 ) -> Expression {
1252 let ty = self.ty();
1253 if ty == target_type
1254 || target_type == Type::Void
1255 || target_type == Type::Invalid
1256 || ty == Type::Invalid
1257 {
1258 self
1259 } else if ty.can_convert(&target_type) {
1260 let from = match (ty, &target_type) {
1261 (Type::Brush, Type::Color) => match self {
1262 Expression::LinearGradient { .. }
1263 | Expression::RadialGradient { .. }
1264 | Expression::ConicGradient { .. } => {
1265 let message = format!(
1266 "Narrowing conversion from {0} to {1}. This can lead to unexpected behavior because the {0} is a gradient",
1267 Type::Brush,
1268 Type::Color
1269 );
1270 diag.push_warning(message, node);
1271 self
1272 }
1273 _ => self,
1274 },
1275 (Type::Percent, Type::Float32) => Expression::BinaryExpression {
1276 lhs: Box::new(self),
1277 rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
1278 op: '*',
1279 },
1280 (ref from_ty @ Type::Struct(ref left), Type::Struct(right))
1281 if left.fields != right.fields =>
1282 {
1283 if let Expression::Struct { mut values, .. } = self {
1284 let mut new_values = HashMap::new();
1285 for (key, ty) in &right.fields {
1286 let (key, expression) = values.remove_entry(key).map_or_else(
1287 || (key.clone(), Expression::default_value_for_type(ty)),
1288 |(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)),
1289 );
1290 new_values.insert(key, expression);
1291 }
1292 return Expression::Struct { values: new_values, ty: right.clone() };
1293 }
1294 static COUNT: std::sync::atomic::AtomicUsize =
1295 std::sync::atomic::AtomicUsize::new(0);
1296 let var_name = format_smolstr!(
1297 "tmpobj_conv_{}",
1298 COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
1299 );
1300 let mut new_values = HashMap::new();
1301 for (key, ty) in &right.fields {
1302 let expression = if left.fields.contains_key(key) {
1303 Expression::StructFieldAccess {
1304 base: Box::new(Expression::ReadLocalVariable {
1305 name: var_name.clone(),
1306 ty: from_ty.clone(),
1307 }),
1308 name: key.clone(),
1309 }
1310 .maybe_convert_to(ty.clone(), node, diag)
1311 } else {
1312 Expression::default_value_for_type(ty)
1313 };
1314 new_values.insert(key.clone(), expression);
1315 }
1316 return Expression::CodeBlock(vec![
1317 Expression::StoreLocalVariable { name: var_name, value: Box::new(self) },
1318 Expression::Struct { values: new_values, ty: right.clone() },
1319 ]);
1320 }
1321 (left, right) => match (left.as_unit_product(), right.as_unit_product()) {
1322 (Some(left), Some(right)) => {
1323 if let Some(conversion_powers) =
1324 crate::langtype::unit_product_length_conversion(&left, &right)
1325 {
1326 let apply_power =
1327 |mut result, power: i8, builtin_fn: BuiltinFunction| {
1328 let op = if power < 0 { '*' } else { '/' };
1329 for _ in 0..power.abs() {
1330 result = Expression::BinaryExpression {
1331 lhs: Box::new(result),
1332 rhs: Box::new(Expression::FunctionCall {
1333 function: Callable::Builtin(builtin_fn.clone()),
1334 arguments: Vec::new(),
1335 source_location: Some(node.to_source_location()),
1336 }),
1337 op,
1338 }
1339 }
1340 result
1341 };
1342
1343 let mut result = self;
1344
1345 if conversion_powers.rem_to_px_power != 0 {
1346 result = apply_power(
1347 result,
1348 conversion_powers.rem_to_px_power,
1349 BuiltinFunction::GetWindowDefaultFontSize,
1350 )
1351 }
1352 if conversion_powers.px_to_phx_power != 0 {
1353 result = apply_power(
1354 result,
1355 conversion_powers.px_to_phx_power,
1356 BuiltinFunction::GetWindowScaleFactor,
1357 )
1358 }
1359
1360 result
1361 } else {
1362 self
1363 }
1364 }
1365 _ => self,
1366 },
1367 };
1368 Expression::Cast { from: Box::new(from), to: target_type }
1369 } else if matches!(
1370 (&ty, &target_type, &self),
1371 (Type::Array(_), Type::Array(_), Expression::Array { .. })
1372 ) {
1373 match (self, target_type) {
1375 (Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array {
1376 values: values
1377 .into_iter()
1378 .map(|e| e.maybe_convert_to((*target_type).clone(), node, diag))
1379 .take_while(|e| !matches!(e, Expression::Invalid))
1380 .collect(),
1381 element_ty: (*target_type).clone(),
1382 },
1383 _ => unreachable!(),
1384 }
1385 } else if let (Type::Struct(struct_type), Expression::Struct { values, .. }) =
1386 (&target_type, &self)
1387 {
1388 let mut fields = struct_type.fields.clone();
1390 let mut new_values = HashMap::new();
1391 for (f, v) in values {
1392 if let Some(t) = fields.remove(f) {
1393 new_values.insert(f.clone(), v.clone().maybe_convert_to(t, node, diag));
1394 } else {
1395 diag.push_error(format!("Cannot convert {ty} to {target_type}"), node);
1396 return self;
1397 }
1398 }
1399 for (f, t) in fields {
1400 new_values.insert(f, Expression::default_value_for_type(&t));
1401 }
1402 Expression::Struct { ty: struct_type.clone(), values: new_values }
1403 } else {
1404 let mut message = format!("Cannot convert {ty} to {target_type}");
1405 if let Some(from_unit) = ty.default_unit() {
1407 if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) {
1408 message =
1409 format!("{message}. Divide by 1{from_unit} to convert to a plain number");
1410 }
1411 } else if let Some(to_unit) = target_type.default_unit()
1412 && matches!(ty, Type::Int32 | Type::Float32)
1413 {
1414 if let Expression::NumberLiteral(value, Unit::None) = self
1415 && value == 0.
1416 {
1417 return Expression::NumberLiteral(0., to_unit);
1419 }
1420 message = format!(
1421 "{message}. Use an unit, or multiply by 1{to_unit} to convert explicitly"
1422 );
1423 }
1424 diag.push_error(message, node);
1425 self
1426 }
1427 }
1428
1429 pub fn default_value_for_type(ty: &Type) -> Expression {
1431 match ty {
1432 Type::Invalid
1433 | Type::Callback { .. }
1434 | Type::Function { .. }
1435 | Type::InferredProperty
1436 | Type::InferredCallback
1437 | Type::ElementReference
1438 | Type::LayoutCache
1439 | Type::ArrayOfU16 => Expression::Invalid,
1440 Type::Void => Expression::CodeBlock(Vec::new()),
1441 Type::Float32 => Expression::NumberLiteral(0., Unit::None),
1442 Type::String => Expression::StringLiteral(SmolStr::default()),
1443 Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast {
1444 from: Box::new(Expression::NumberLiteral(0., Unit::None)),
1445 to: ty.clone(),
1446 },
1447 Type::Duration => Expression::NumberLiteral(0., Unit::Ms),
1448 Type::Angle => Expression::NumberLiteral(0., Unit::Deg),
1449 Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx),
1450 Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px),
1451 Type::Rem => Expression::NumberLiteral(0., Unit::Rem),
1452 Type::Percent => Expression::NumberLiteral(100., Unit::Percent),
1453 Type::Image => Expression::ImageReference {
1454 resource_ref: ImageReference::None,
1455 source_location: None,
1456 nine_slice: None,
1457 },
1458 Type::Bool => Expression::BoolLiteral(false),
1459 Type::Model => Expression::Invalid,
1460 Type::PathData => Expression::PathData(Path::Elements(Vec::new())),
1461 Type::Array(element_ty) => {
1462 Expression::Array { element_ty: (**element_ty).clone(), values: Vec::new() }
1463 }
1464 Type::Struct(s) => Expression::Struct {
1465 ty: s.clone(),
1466 values: s
1467 .fields
1468 .iter()
1469 .map(|(k, v)| (k.clone(), Expression::default_value_for_type(v)))
1470 .collect(),
1471 },
1472 Type::Easing => Expression::EasingCurve(EasingCurve::default()),
1473 Type::Brush => Expression::Cast {
1474 from: Box::new(Expression::default_value_for_type(&Type::Color)),
1475 to: Type::Brush,
1476 },
1477 Type::Enumeration(enumeration) => {
1478 Expression::EnumerationValue(enumeration.clone().default_value())
1479 }
1480 Type::ComponentFactory => Expression::EmptyComponentFactory,
1481 Type::StyledText => Expression::Invalid,
1482 }
1483 }
1484
1485 pub fn try_set_rw(
1489 &mut self,
1490 ctx: &mut LookupCtx,
1491 what: &'static str,
1492 node: &dyn Spanned,
1493 ) -> bool {
1494 match self {
1495 Expression::PropertyReference(nr) => {
1496 nr.mark_as_set();
1497 let mut lookup = nr.element().borrow().lookup_property(nr.name());
1498 lookup.is_local_to_component &= ctx.is_local_element(&nr.element());
1499 if lookup.property_visibility == PropertyVisibility::Constexpr {
1500 ctx.diag.push_error(
1501 "The property must be known at compile time and cannot be changed at runtime"
1502 .into(),
1503 node,
1504 );
1505 false
1506 } else if lookup.is_valid_for_assignment() {
1507 if !nr
1508 .element()
1509 .borrow()
1510 .property_analysis
1511 .borrow()
1512 .get(nr.name())
1513 .is_some_and(|d| d.is_linked_to_read_only)
1514 {
1515 true
1516 } else if ctx.is_legacy_component() {
1517 ctx.diag.push_warning("Modifying a property that is linked to a read-only property is deprecated".into(), node);
1518 true
1519 } else {
1520 ctx.diag.push_error(
1521 "Cannot modify a property that is linked to a read-only property"
1522 .into(),
1523 node,
1524 );
1525 false
1526 }
1527 } else if ctx.is_legacy_component()
1528 && lookup.property_visibility == PropertyVisibility::Output
1529 {
1530 ctx.diag
1531 .push_warning(format!("{what} on an output property is deprecated"), node);
1532 true
1533 } else {
1534 ctx.diag.push_error(
1535 format!("{what} on a {} property", lookup.property_visibility),
1536 node,
1537 );
1538 false
1539 }
1540 }
1541 Expression::StructFieldAccess { base, .. } => base.try_set_rw(ctx, what, node),
1542 Expression::RepeaterModelReference { .. } => true,
1543 Expression::ArrayIndex { array, .. } => array.try_set_rw(ctx, what, node),
1544 _ => {
1545 ctx.diag.push_error(format!("{what} needs to be done on a property"), node);
1546 false
1547 }
1548 }
1549 }
1550
1551 pub fn ignore_debug_hooks(&self) -> &Expression {
1553 match self {
1554 Expression::DebugHook { expression, .. } => expression.as_ref(),
1555 _ => self,
1556 }
1557 }
1558}
1559
1560fn model_inner_type(model: &Expression) -> Type {
1561 match model {
1562 Expression::Cast { from, to: Type::Model } => model_inner_type(from),
1563 Expression::CodeBlock(cb) => cb.last().map_or(Type::Invalid, model_inner_type),
1564 _ => match model.ty() {
1565 Type::Float32 | Type::Int32 => Type::Int32,
1566 Type::Array(elem) => (*elem).clone(),
1567 _ => Type::Invalid,
1568 },
1569 }
1570}
1571
1572#[derive(Clone, Debug)]
1574pub struct TwoWayBinding {
1575 pub property: NamedReference,
1577 pub field_access: Vec<SmolStr>,
1580}
1581impl TwoWayBinding {
1582 pub fn ty(&self) -> Type {
1583 let mut ty = self.property.ty();
1584 for x in &self.field_access {
1585 ty = match ty {
1586 Type::InferredProperty | Type::InferredCallback => return ty,
1587 Type::Struct(s) => s.fields.get(x).cloned().unwrap_or_default(),
1588 _ => return Type::Invalid,
1589 };
1590 }
1591 ty
1592 }
1593}
1594
1595impl From<NamedReference> for TwoWayBinding {
1596 fn from(nr: NamedReference) -> Self {
1597 Self { property: nr, field_access: Vec::new() }
1598 }
1599}
1600
1601#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
1603pub struct BindingExpression {
1604 #[deref]
1605 #[deref_mut]
1606 pub expression: Expression,
1607 pub span: Option<SourceLocation>,
1609 pub priority: i32,
1614
1615 pub animation: Option<PropertyAnimation>,
1616
1617 pub analysis: Option<BindingAnalysis>,
1619
1620 pub two_way_bindings: Vec<TwoWayBinding>,
1622}
1623
1624impl std::convert::From<Expression> for BindingExpression {
1625 fn from(expression: Expression) -> Self {
1626 Self {
1627 expression,
1628 span: None,
1629 priority: 0,
1630 animation: Default::default(),
1631 analysis: Default::default(),
1632 two_way_bindings: Default::default(),
1633 }
1634 }
1635}
1636
1637impl BindingExpression {
1638 pub fn new_uncompiled(node: SyntaxNode) -> Self {
1639 Self {
1640 expression: Expression::Uncompiled(node.clone()),
1641 span: Some(node.to_source_location()),
1642 priority: 1,
1643 animation: Default::default(),
1644 analysis: Default::default(),
1645 two_way_bindings: Default::default(),
1646 }
1647 }
1648 pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self {
1649 Self {
1650 expression,
1651 span: Some(span),
1652 priority: 0,
1653 animation: Default::default(),
1654 analysis: Default::default(),
1655 two_way_bindings: Default::default(),
1656 }
1657 }
1658
1659 pub fn new_two_way(other: TwoWayBinding) -> Self {
1661 Self {
1662 expression: Expression::Invalid,
1663 span: None,
1664 priority: 0,
1665 animation: Default::default(),
1666 analysis: Default::default(),
1667 two_way_bindings: vec![other],
1668 }
1669 }
1670
1671 pub fn merge_with(&mut self, other: &Self) -> bool {
1679 if self.animation.is_none() {
1680 self.animation.clone_from(&other.animation);
1681 }
1682 let has_binding = self.has_binding();
1683 self.two_way_bindings.extend_from_slice(&other.two_way_bindings);
1684 if !has_binding {
1685 self.priority = other.priority;
1686 self.expression = other.expression.clone();
1687 true
1688 } else {
1689 false
1690 }
1691 }
1692
1693 pub fn has_binding(&self) -> bool {
1695 !matches!(self.expression, Expression::Invalid) || !self.two_way_bindings.is_empty()
1696 }
1697}
1698
1699impl Spanned for BindingExpression {
1700 fn span(&self) -> crate::diagnostics::Span {
1701 self.span.as_ref().map(|x| x.span()).unwrap_or_default()
1702 }
1703 fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
1704 self.span.as_ref().and_then(|x| x.source_file())
1705 }
1706}
1707
1708#[derive(Default, Debug, Clone)]
1709pub struct BindingAnalysis {
1710 pub is_in_binding_loop: Cell<bool>,
1712
1713 pub is_const: bool,
1715
1716 pub no_external_dependencies: bool,
1719}
1720
1721#[derive(Debug, Clone)]
1722pub enum Path {
1723 Elements(Vec<PathElement>),
1724 Events(Vec<Expression>, Vec<Expression>),
1725 Commands(Box<Expression>), }
1727
1728#[derive(Debug, Clone)]
1729pub struct PathElement {
1730 pub element_type: Rc<BuiltinElement>,
1731 pub bindings: BindingsMap,
1732}
1733
1734#[derive(Clone, Debug, Default)]
1735pub enum EasingCurve {
1736 #[default]
1737 Linear,
1738 CubicBezier(f32, f32, f32, f32),
1739 EaseInElastic,
1740 EaseOutElastic,
1741 EaseInOutElastic,
1742 EaseInBounce,
1743 EaseOutBounce,
1744 EaseInOutBounce,
1745 }
1748
1749#[derive(Clone, Debug)]
1752pub enum ImageReference {
1753 None,
1754 AbsolutePath(SmolStr),
1755 EmbeddedData { resource_id: usize, extension: String },
1756 EmbeddedTexture { resource_id: usize },
1757}
1758
1759pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result {
1761 match expression {
1762 Expression::Invalid => write!(f, "<invalid>"),
1763 Expression::Uncompiled(u) => write!(f, "{u:?}"),
1764 Expression::StringLiteral(s) => write!(f, "{s:?}"),
1765 Expression::NumberLiteral(vl, unit) => write!(f, "{vl}{unit}"),
1766 Expression::BoolLiteral(b) => write!(f, "{b:?}"),
1767 Expression::PropertyReference(a) => write!(f, "{a:?}"),
1768 Expression::ElementReference(a) => write!(f, "{a:?}"),
1769 Expression::RepeaterIndexReference { element } => {
1770 crate::namedreference::pretty_print_element_ref(f, element)
1771 }
1772 Expression::RepeaterModelReference { element } => {
1773 crate::namedreference::pretty_print_element_ref(f, element)?;
1774 write!(f, ".@model")
1775 }
1776 Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_{index}"),
1777 Expression::StoreLocalVariable { name, value } => {
1778 write!(f, "{name} = ")?;
1779 pretty_print(f, value)
1780 }
1781 Expression::ReadLocalVariable { name, ty: _ } => write!(f, "{name}"),
1782 Expression::StructFieldAccess { base, name } => {
1783 pretty_print(f, base)?;
1784 write!(f, ".{name}")
1785 }
1786 Expression::ArrayIndex { array, index } => {
1787 pretty_print(f, array)?;
1788 write!(f, "[")?;
1789 pretty_print(f, index)?;
1790 write!(f, "]")
1791 }
1792 Expression::Cast { from, to } => {
1793 write!(f, "(")?;
1794 pretty_print(f, from)?;
1795 write!(f, "/* as {to} */)")
1796 }
1797 Expression::CodeBlock(c) => {
1798 write!(f, "{{ ")?;
1799 for e in c {
1800 pretty_print(f, e)?;
1801 write!(f, "; ")?;
1802 }
1803 write!(f, "}}")
1804 }
1805 Expression::FunctionCall { function, arguments, source_location: _ } => {
1806 match function {
1807 Callable::Builtin(b) => write!(f, "{b:?}")?,
1808 Callable::Callback(nr) | Callable::Function(nr) => write!(f, "{nr:?}")?,
1809 }
1810 write!(f, "(")?;
1811 for e in arguments {
1812 pretty_print(f, e)?;
1813 write!(f, ", ")?;
1814 }
1815 write!(f, ")")
1816 }
1817 Expression::SelfAssignment { lhs, rhs, op, .. } => {
1818 pretty_print(f, lhs)?;
1819 write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
1820 pretty_print(f, rhs)
1821 }
1822 Expression::BinaryExpression { lhs, rhs, op } => {
1823 write!(f, "(")?;
1824 pretty_print(f, lhs)?;
1825 match *op {
1826 '=' | '!' => write!(f, " {op}= ")?,
1827 _ => write!(f, " {op} ")?,
1828 };
1829 pretty_print(f, rhs)?;
1830 write!(f, ")")
1831 }
1832 Expression::UnaryOp { sub, op } => {
1833 write!(f, "{op}")?;
1834 pretty_print(f, sub)
1835 }
1836 Expression::ImageReference { resource_ref, .. } => write!(f, "{resource_ref:?}"),
1837 Expression::Condition { condition, true_expr, false_expr } => {
1838 write!(f, "if (")?;
1839 pretty_print(f, condition)?;
1840 write!(f, ") {{ ")?;
1841 pretty_print(f, true_expr)?;
1842 write!(f, " }} else {{ ")?;
1843 pretty_print(f, false_expr)?;
1844 write!(f, " }}")
1845 }
1846 Expression::Array { element_ty: _, values } => {
1847 write!(f, "[")?;
1848 for e in values {
1849 pretty_print(f, e)?;
1850 write!(f, ", ")?;
1851 }
1852 write!(f, "]")
1853 }
1854 Expression::Struct { ty: _, values } => {
1855 write!(f, "{{ ")?;
1856 for (name, e) in values {
1857 write!(f, "{name}: ")?;
1858 pretty_print(f, e)?;
1859 write!(f, ", ")?;
1860 }
1861 write!(f, " }}")
1862 }
1863 Expression::PathData(data) => write!(f, "{data:?}"),
1864 Expression::EasingCurve(e) => write!(f, "{e:?}"),
1865 Expression::LinearGradient { angle, stops } => {
1866 write!(f, "@linear-gradient(")?;
1867 pretty_print(f, angle)?;
1868 for (c, s) in stops {
1869 write!(f, ", ")?;
1870 pretty_print(f, c)?;
1871 write!(f, " ")?;
1872 pretty_print(f, s)?;
1873 }
1874 write!(f, ")")
1875 }
1876 Expression::RadialGradient { stops } => {
1877 write!(f, "@radial-gradient(circle")?;
1878 for (c, s) in stops {
1879 write!(f, ", ")?;
1880 pretty_print(f, c)?;
1881 write!(f, " ")?;
1882 pretty_print(f, s)?;
1883 }
1884 write!(f, ")")
1885 }
1886 Expression::ConicGradient { from_angle, stops } => {
1887 write!(f, "@conic-gradient(from ")?;
1888 pretty_print(f, from_angle)?;
1889 for (c, s) in stops {
1890 write!(f, ", ")?;
1891 pretty_print(f, c)?;
1892 write!(f, " ")?;
1893 pretty_print(f, s)?;
1894 }
1895 write!(f, ")")
1896 }
1897 Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value) {
1898 Some(val) => write!(f, "{}.{}", e.enumeration.name, val),
1899 None => write!(f, "{}.{}", e.enumeration.name, e.value),
1900 },
1901 Expression::ReturnStatement(e) => {
1902 write!(f, "return ")?;
1903 e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(()))
1904 }
1905 Expression::LayoutCacheAccess {
1906 layout_cache_prop,
1907 index,
1908 repeater_index,
1909 entries_per_item,
1910 } => {
1911 if repeater_index.is_some() {
1912 write!(
1913 f,
1914 "{:?}[{:?}[{}] + $repeater_index * {}]",
1915 layout_cache_prop, layout_cache_prop, index, entries_per_item
1916 )
1917 } else {
1918 write!(f, "{:?}[{}]", layout_cache_prop, index)
1919 }
1920 }
1921 Expression::OrganizeGridLayout(..) => write!(f, "organize_grid_layout(..)"),
1922 Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)"),
1923 Expression::ComputeGridLayoutInfo { .. } => write!(f, "grid_layout_info(..)"),
1924 Expression::SolveLayout(..) => write!(f, "solve_layout(..)"),
1925 Expression::SolveGridLayout { .. } => write!(f, "solve_grid_layout(..)"),
1926 Expression::MinMax { ty: _, op, lhs, rhs } => {
1927 match op {
1928 MinMaxOp::Min => write!(f, "min(")?,
1929 MinMaxOp::Max => write!(f, "max(")?,
1930 }
1931 pretty_print(f, lhs)?;
1932 write!(f, ", ")?;
1933 pretty_print(f, rhs)?;
1934 write!(f, ")")
1935 }
1936 Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>"),
1937 Expression::DebugHook { expression, id } => {
1938 write!(f, "debug-hook(")?;
1939 pretty_print(f, expression)?;
1940 write!(f, "\"{id}\")")
1941 }
1942 }
1943}