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