1use std::collections::HashMap;
7
8use super::GlobalAnalysis;
9use crate::expression_tree::*;
10use crate::langtype::{BuiltinStruct, ElementType, StructName, Type};
11use crate::namedreference::NamedReference;
12use crate::object_tree::*;
13use smol_str::format_smolstr;
14
15type ConstPropCache = HashMap<NamedReference, Option<Expression>>;
16
17pub(crate) fn fold_const_expression(expr: &mut Expression) {
22 simplify_expression(expr, &GlobalAnalysis::default(), &mut ConstPropCache::default());
23}
24
25pub fn const_propagation(component: &Component, global_analysis: &GlobalAnalysis) {
26 let mut cache = ConstPropCache::new();
27 visit_all_expressions(component, |expr, _ty| {
28 simplify_expression(expr, global_analysis, &mut cache);
29 });
30
31 recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
36 for (_, binding) in elem.borrow().real_bindings() {
37 let Ok(mut binding) = binding.try_borrow_mut() else { continue };
38 let Some(analysis) = binding.analysis.as_ref() else { continue };
39 if analysis.is_const || matches!(binding.expression, Expression::Invalid) {
40 continue;
41 }
42 if binding.expression.is_constant(Some(global_analysis))
43 && binding.two_way_bindings.iter().all(|tw| tw.is_constant())
44 {
45 binding.analysis.as_mut().unwrap().is_const = true;
46 }
47 }
48 });
49}
50
51fn simplify_expression(
58 expr: &mut Expression,
59 ga: &GlobalAnalysis,
60 cache: &mut ConstPropCache,
61) -> bool {
62 match expr {
63 Expression::PropertyReference(..) => simplify_property_reference(expr, ga, cache),
64 Expression::BinaryExpression { .. } => simplify_binary_expression(expr, ga, cache),
65 Expression::UnaryOp { .. } => simplify_unary_op(expr, ga, cache),
66 Expression::StructFieldAccess { .. } => simplify_struct_field_access(expr, ga, cache),
67 Expression::Cast { .. } => simplify_cast(expr, ga, cache),
68 Expression::MinMax { .. } => simplify_min_max(expr, ga, cache),
69 Expression::Condition { .. } => simplify_condition(expr, ga, cache),
70 Expression::CodeBlock(stmts)
72 if stmts.len() == 1 && !matches!(stmts[0], Expression::StoreLocalVariable { .. }) =>
73 {
74 simplify_single_statement_code_block(expr, ga, cache)
75 }
76 Expression::FunctionCall { .. } => simplify_function_call(expr, ga, cache),
77 Expression::ElementReference { .. } => false,
78 Expression::LayoutCacheAccess { .. } => false,
79 Expression::OrganizeGridLayout { .. } => false,
80 Expression::SolveBoxLayout { .. } => false,
81 Expression::SolveGridLayout { .. } => false,
82 Expression::SolveFlexboxLayout { .. } => false,
83 Expression::ComputeBoxLayoutInfo { .. } => false,
84 Expression::ComputeGridLayoutInfo { .. } => false,
85 Expression::ComputeFlexboxLayoutInfo { .. } => false,
86 _ => {
87 let mut result = true;
88 expr.visit_mut(|expr| result &= simplify_expression(expr, ga, cache));
89 result
90 }
91 }
92}
93
94#[inline(never)]
95fn simplify_property_reference(
96 expr: &mut Expression,
97 ga: &GlobalAnalysis,
98 cache: &mut ConstPropCache,
99) -> bool {
100 let Expression::PropertyReference(nr) = expr else { unreachable!() };
101 if nr.is_constant()
102 && !match nr.ty() {
103 Type::Struct(s) => {
104 matches!(s.name, StructName::Builtin(BuiltinStruct::StateInfo))
105 }
106 _ => false,
107 }
108 {
109 if let Some(result) = extract_constant_property_reference(nr, ga, cache) {
111 *expr = result;
112 return true;
113 }
114 }
115 false
116}
117
118#[inline(never)]
119fn simplify_binary_expression(
120 expr: &mut Expression,
121 ga: &GlobalAnalysis,
122 cache: &mut ConstPropCache,
123) -> bool {
124 let Expression::BinaryExpression { lhs, op, rhs, .. } = expr else { unreachable!() };
125 let mut can_inline = simplify_expression(lhs, ga, cache);
126 can_inline &= simplify_expression(rhs, ga, cache);
127
128 let new = fold_binary_expression(*op, lhs, rhs, &mut can_inline);
132 if let Some(new) = new {
133 *expr = new;
134 }
135 can_inline
136}
137
138#[inline(never)]
139fn fold_binary_expression(
140 op: char,
141 lhs: &mut Expression,
142 rhs: &mut Expression,
143 can_inline: &mut bool,
144) -> Option<Expression> {
145 match (op, lhs, rhs) {
146 ('+', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
148 Some(Expression::StringLiteral(format_smolstr!("{}{}", a, b)))
149 }
150 ('+', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, _)) => {
151 Some(Expression::NumberLiteral(*a + *b, *un1))
152 }
153 ('+', Expression::Struct { ty, values: a }, Expression::Struct { values: b, .. })
159 if matches!(ty.name, StructName::Builtin(BuiltinStruct::LayoutInfo)) =>
160 {
161 let ty = ty.clone();
162 ty.fields
163 .keys()
164 .map(|name| {
165 let Some(Expression::NumberLiteral(x, u)) = a.get(name) else { return None };
166 let Some(Expression::NumberLiteral(y, _)) = b.get(name) else { return None };
167 let v = match name.as_str() {
168 "min" | "min_percent" | "preferred" => x.max(*y),
169 "max" | "max_percent" | "stretch" => x.min(*y),
170 _ => return None,
171 };
172 Some((name.clone(), Expression::NumberLiteral(v, *u)))
173 })
174 .collect::<Option<_>>()
175 .map(|values| Expression::Struct { ty, values })
176 }
177 ('-', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, _)) => {
178 Some(Expression::NumberLiteral(*a - *b, *un1))
179 }
180 ('*', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
181 if *un1 == Unit::None || *un2 == Unit::None =>
182 {
183 let preserved_unit = if *un1 == Unit::None { *un2 } else { *un1 };
184 Some(Expression::NumberLiteral(*a * *b, preserved_unit))
185 }
186 ('/', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, Unit::None)) => {
187 Some(Expression::NumberLiteral(*a / *b, *un1))
188 }
189 ('/', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
190 if un1 == un2 =>
191 {
192 Some(Expression::NumberLiteral(*a / *b, Unit::None))
193 }
194 ('+', e, Expression::NumberLiteral(n, _))
198 | ('+', Expression::NumberLiteral(n, _), e)
199 | ('-', e, Expression::NumberLiteral(n, _))
200 if *n == 0. =>
201 {
202 Some(std::mem::take(e))
203 }
204 ('*', e, Expression::NumberLiteral(n, Unit::None))
205 | ('*', Expression::NumberLiteral(n, Unit::None), e)
206 | ('/', e, Expression::NumberLiteral(n, Unit::None))
207 if *n == 1. =>
208 {
209 Some(std::mem::take(e))
210 }
211
212 (
214 '=' | '!' | '<' | '>' | '≤' | '≥',
215 Expression::NumberLiteral(a, _),
216 Expression::NumberLiteral(b, _),
217 ) => Some(Expression::BoolLiteral(match op {
218 '=' => a == b,
219 '!' => a != b,
220 '<' => a < b,
221 '>' => a > b,
222 '≤' => a <= b,
223 _ => a >= b,
224 })),
225 ('=' | '!', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
226 Some(Expression::BoolLiteral((a == b) == (op == '=')))
227 }
228 ('=' | '!', Expression::EnumerationValue(a), Expression::EnumerationValue(b)) => {
229 Some(Expression::BoolLiteral((a == b) == (op == '=')))
230 }
231 ('=' | '!', Expression::BoolLiteral(a), Expression::BoolLiteral(b)) => {
232 Some(Expression::BoolLiteral((a == b) == (op == '=')))
233 }
234 ('&', Expression::BoolLiteral(a), Expression::BoolLiteral(b)) => {
238 Some(Expression::BoolLiteral(*a && *b))
239 }
240 ('|', Expression::BoolLiteral(a), Expression::BoolLiteral(b)) => {
241 Some(Expression::BoolLiteral(*a || *b))
242 }
243 ('&', Expression::BoolLiteral(false), _) => {
244 *can_inline = true;
245 Some(Expression::BoolLiteral(false))
246 }
247 ('|', Expression::BoolLiteral(true), _) => {
248 *can_inline = true;
249 Some(Expression::BoolLiteral(true))
250 }
251 ('&', Expression::BoolLiteral(true), e)
252 | ('&', e, Expression::BoolLiteral(true))
253 | ('|', Expression::BoolLiteral(false), e)
254 | ('|', e, Expression::BoolLiteral(false)) => Some(std::mem::take(e)),
255 _ => None,
256 }
257}
258
259#[inline(never)]
260fn simplify_unary_op(
261 expr: &mut Expression,
262 ga: &GlobalAnalysis,
263 cache: &mut ConstPropCache,
264) -> bool {
265 let Expression::UnaryOp { sub, op } = expr else { unreachable!() };
266 let can_inline = simplify_expression(sub, ga, cache);
267 let new = match (*op, &mut **sub) {
268 ('!', Expression::BoolLiteral(b)) => Some(Expression::BoolLiteral(!*b)),
269 ('-', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(-*n, *u)),
270 ('+', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(*n, *u)),
271 _ => None,
272 };
273 if let Some(new) = new {
274 *expr = new;
275 }
276 can_inline
277}
278
279#[inline(never)]
280fn simplify_struct_field_access(
281 expr: &mut Expression,
282 ga: &GlobalAnalysis,
283 cache: &mut ConstPropCache,
284) -> bool {
285 let Expression::StructFieldAccess { base, name } = expr else { unreachable!() };
286 if let Expression::PropertyReference(nr) = &**base
287 && nr.is_constant()
288 && let Some(field_expr) = extract_struct_field_from_constant(nr, name, ga, cache)
289 {
290 *expr = field_expr;
291 return simplify_expression(expr, ga, cache);
292 }
293 let r = simplify_expression(base, ga, cache);
294 if let Expression::Struct { values, .. } = &mut **base
295 && let Some(e) = values.remove(name)
296 {
297 *expr = e;
298 return simplify_expression(expr, ga, cache);
299 }
300 r
301}
302
303#[inline(never)]
304fn simplify_cast(expr: &mut Expression, ga: &GlobalAnalysis, cache: &mut ConstPropCache) -> bool {
305 let Expression::Cast { from, to } = expr else { unreachable!() };
306 let can_inline = simplify_expression(from, ga, cache);
307 let new = if from.ty() == *to {
308 Some(std::mem::take(&mut **from))
309 } else {
310 match (&**from, &*to) {
311 (Expression::NumberLiteral(x, Unit::None), Type::String) => {
312 locale_independent_number_to_string(*x).map(Expression::StringLiteral)
313 }
314 (Expression::NumberLiteral(x, _), Type::Float32) => {
315 Some(Expression::NumberLiteral(*x, Unit::None))
316 }
317 (Expression::Struct { values, .. }, Type::Struct(ty)) => {
318 Some(Expression::Struct { ty: ty.clone(), values: values.clone() })
319 }
320 _ => None,
321 }
322 };
323 if let Some(new) = new {
324 *expr = new;
325 }
326 can_inline
327}
328
329#[inline(never)]
330fn simplify_min_max(
331 expr: &mut Expression,
332 ga: &GlobalAnalysis,
333 cache: &mut ConstPropCache,
334) -> bool {
335 let Expression::MinMax { op, lhs, rhs, ty: _ } = expr else { unreachable!() };
336 let can_inline = simplify_expression(lhs, ga, cache) & simplify_expression(rhs, ga, cache);
337 if let (Expression::NumberLiteral(lhs, u), Expression::NumberLiteral(rhs, _)) = (&**lhs, &**rhs)
338 {
339 let v = match op {
340 MinMaxOp::Min => lhs.min(*rhs),
341 MinMaxOp::Max => lhs.max(*rhs),
342 };
343 *expr = Expression::NumberLiteral(v, *u);
344 }
345 can_inline
346}
347
348#[inline(never)]
349fn simplify_condition(
350 expr: &mut Expression,
351 ga: &GlobalAnalysis,
352 cache: &mut ConstPropCache,
353) -> bool {
354 let Expression::Condition { condition, true_expr, false_expr, .. } = expr else {
355 unreachable!()
356 };
357 let mut can_inline = simplify_expression(condition, ga, cache);
358 can_inline &= match &**condition {
359 Expression::BoolLiteral(true) => {
360 *expr = *true_expr.clone();
361 simplify_expression(expr, ga, cache)
362 }
363 Expression::BoolLiteral(false) => {
364 *expr = *false_expr.clone();
365 simplify_expression(expr, ga, cache)
366 }
367 _ => simplify_expression(true_expr, ga, cache) & simplify_expression(false_expr, ga, cache),
368 };
369 can_inline
370}
371
372#[inline(never)]
373fn simplify_single_statement_code_block(
374 expr: &mut Expression,
375 ga: &GlobalAnalysis,
376 cache: &mut ConstPropCache,
377) -> bool {
378 let Expression::CodeBlock(stmts) = expr else { unreachable!() };
379 *expr = stmts[0].clone();
380 simplify_expression(expr, ga, cache)
381}
382
383#[inline(never)]
384fn simplify_function_call(
385 expr: &mut Expression,
386 ga: &GlobalAnalysis,
387 cache: &mut ConstPropCache,
388) -> bool {
389 let Expression::FunctionCall { function, arguments, .. } = expr else { unreachable!() };
390 let mut args_can_inline = true;
391 for arg in arguments.iter_mut() {
392 args_can_inline &= simplify_expression(arg, ga, cache);
393 }
394 if args_can_inline && let Some(inlined) = try_inline_function(function, arguments, ga, cache) {
395 *expr = inlined;
396 return true;
397 }
398 false
399}
400
401fn extract_constant_property_reference(
405 nr: &NamedReference,
406 ga: &GlobalAnalysis,
407 cache: &mut ConstPropCache,
408) -> Option<Expression> {
409 debug_assert!(nr.is_constant());
410 if let Some(cached) = cache.get(nr) {
411 return cached.clone();
412 }
413 let result = extract_constant_property_reference_impl(nr, ga, cache);
414 cache.insert(nr.clone(), result.clone());
415 result
416}
417
418fn extract_struct_field_from_constant(
421 nr: &NamedReference,
422 field_name: &str,
423 ga: &GlobalAnalysis,
424 cache: &mut ConstPropCache,
425) -> Option<Expression> {
426 let _ = extract_constant_property_reference(nr, ga, cache);
428 if let Some(Some(Expression::Struct { values, .. })) = cache.get(nr) {
429 values.get(field_name).cloned()
430 } else {
431 None
432 }
433}
434
435fn extract_constant_property_reference_impl(
436 nr: &NamedReference,
437 ga: &GlobalAnalysis,
438 cache: &mut ConstPropCache,
439) -> Option<Expression> {
440 let mut element = nr.element();
442 let mut expression = loop {
443 if let Some(binding) = element.borrow().binding(nr.name()) {
444 if !binding.two_way_bindings.is_empty() {
445 return None;
448 }
449 if !matches!(binding.value_expression(), Expression::Invalid) {
450 break binding.expression.clone();
451 }
452 };
453 if let Some(decl) = element.clone().borrow().property_declarations.get(nr.name()) {
454 if let Some(alias) = &decl.is_alias {
455 return extract_constant_property_reference(alias, ga, cache);
456 }
457 } else if let ElementType::Component(c) = &element.clone().borrow().base_type {
458 element = c.root_element.clone();
459 continue;
460 }
461
462 let ty = nr.ty();
464 debug_assert!(!matches!(ty, Type::Invalid));
465 return Some(Expression::default_value_for_type(&ty));
466 };
467 if !(simplify_expression(&mut expression, ga, cache)) {
468 return None;
469 }
470 Some(expression)
471}
472
473fn try_inline_function(
474 function: &Callable,
475 arguments: &[Expression],
476 ga: &GlobalAnalysis,
477 cache: &mut ConstPropCache,
478) -> Option<Expression> {
479 let function = match function {
480 Callable::Function(function) => function,
481 Callable::Builtin(b) => return try_inline_builtin_function(b, arguments, ga),
482 _ => return None,
483 };
484 if !function.is_constant() {
485 return None;
486 }
487 let mut body = extract_constant_property_reference(function, ga, cache)?;
488
489 fn substitute_arguments_recursive(e: &mut Expression, arguments: &[Expression]) {
490 if let Expression::FunctionParameterReference { index, ty } = e {
491 let e_new = arguments.get(*index).expect("reference to invalid arg").clone();
492 debug_assert_eq!(e_new.ty(), *ty);
493 *e = e_new;
494 } else {
495 e.visit_mut(|e| substitute_arguments_recursive(e, arguments));
496 }
497 }
498 substitute_arguments_recursive(&mut body, arguments);
499
500 if simplify_expression(&mut body, ga, cache) { Some(body) } else { None }
501}
502
503fn try_inline_builtin_function(
504 b: &BuiltinFunction,
505 args: &[Expression],
506 ga: &GlobalAnalysis,
507) -> Option<Expression> {
508 let a = |idx: usize| -> Option<f64> {
509 match args.get(idx)? {
510 Expression::NumberLiteral(n, Unit::None) => Some(*n),
511 _ => None,
512 }
513 };
514 let num = |n: f64| Some(Expression::NumberLiteral(n, Unit::None));
515
516 match b {
517 BuiltinFunction::GetWindowScaleFactor => {
518 ga.const_scale_factor.map(|factor| Expression::NumberLiteral(factor as _, Unit::None))
519 }
520 BuiltinFunction::GetWindowDefaultFontSize => match ga.default_font_size {
521 crate::passes::binding_analysis::DefaultFontSize::LogicalValue(val) => {
522 Some(Expression::NumberLiteral(val as _, Unit::Px))
523 }
524 _ => None,
525 },
526 BuiltinFunction::Mod => num(a(0)?.rem_euclid(a(1)?)),
527 BuiltinFunction::Round => num(a(0)?.round()),
528 BuiltinFunction::Ceil => num(a(0)?.ceil()),
529 BuiltinFunction::Floor => num(a(0)?.floor()),
530 BuiltinFunction::Abs => num(a(0)?.abs()),
531 BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => {
532 let Some(Expression::StringLiteral(s)) = args.first() else { return None };
533 if !s.chars().all(|c| c.is_ascii_digit() || matches!(c, '+' | '-' | 'e' | 'E')) {
536 return None;
537 }
538 let value = s.parse::<f32>().ok();
539 Some(match b {
540 BuiltinFunction::StringToFloat => {
541 Expression::NumberLiteral(value.unwrap_or(0.) as f64, Unit::None)
542 }
543 _ => Expression::BoolLiteral(value.is_some()),
544 })
545 }
546 _ => None,
547 }
548}
549
550#[test]
551fn test() {
552 let mut compiler_config =
553 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
554 compiler_config.style = Some("fluent".into());
555 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
556 let doc_node = crate::parser::parse(
557 r#"
558/* ... */
559struct Hello { s: string, v: float }
560enum Enum { aa, bb, cc }
561global G {
562 pure function complicated(a: float ) -> bool { if a > 5 { return true; }; if a < 1 { return true; }; uncomplicated() }
563 pure function uncomplicated( ) -> bool { false }
564 out property <float> p : 3 * 2 + 15 ;
565 property <string> q: "foo " + 42;
566 out property <float> w : -p / 2;
567 out property <Hello> out: { s: q, v: complicated(w + 15) ? -123 : p };
568
569 in-out property <Enum> e: Enum.bb;
570}
571export component Foo {
572 in property <int> input;
573 out property<float> out1: G.w;
574 out property<float> out2: G.out.v;
575 out property<bool> out3: false ? input == 12 : input > 0 ? input == 11 : G.e == Enum.bb;
576}
577"#
578 .into(),
579 Some(std::path::Path::new("HELLO")),
580 &mut test_diags,
581 );
582 let (doc, diag, _) =
583 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
584 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
585
586 let expected_p = 3.0 * 2.0 + 15.0;
587 let expected_w = -expected_p / 2.0;
588 let root_element = doc.inner_components.last().unwrap().root_element.clone();
589 let out1_binding = root_element.borrow().binding("out1").unwrap().expression.clone();
590 match &out1_binding {
591 Expression::NumberLiteral(n, _) => assert_eq!(*n, expected_w),
592 _ => panic!("not number {out1_binding:?}"),
593 }
594 let out2_binding = root_element.borrow().binding("out2").unwrap().expression.clone();
595 match &out2_binding {
596 Expression::NumberLiteral(n, _) => assert_eq!(*n, expected_p),
597 _ => panic!("not number {out2_binding:?}"),
598 }
599 let out3_binding = root_element.borrow().binding("out3").unwrap().expression.clone();
600 match &out3_binding {
601 Expression::CodeBlock(stmts) => match &stmts[1] {
603 Expression::Condition { condition: _, true_expr: _, false_expr, .. } => {
604 match &**false_expr {
605 Expression::BoolLiteral(b) => assert!(*b),
606 _ => panic!("false_expr not optimized in : {out3_binding:?}"),
607 }
608 }
609 _ => panic!("not condition: {out3_binding:?}"),
610 },
611 _ => panic!("not code block: {out3_binding:?}"),
612 };
613}
614
615#[test]
616fn test_locale_dependent_string_conversion() {
617 let mut compiler_config =
618 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
619 compiler_config.style = Some("fluent".into());
620 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
621 let doc_node = crate::parser::parse(
622 r#"
623export component Foo {
624 out property <string> int-str: "n=" + 42;
625 out property <string> calc-str: "n=" + (6 * 7);
626 out property <string> float-str: "n=" + 4.5;
627 out property <float> int-float: "42".to-float();
628 out property <bool> int-is-float: "42".is-float();
629 out property <float> frac-float: "4,2".to-float();
630}
631"#
632 .into(),
633 Some(std::path::Path::new("HELLO")),
634 &mut test_diags,
635 );
636 let (doc, diag, _) =
637 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
638 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
639
640 let root_element = doc.inner_components.last().unwrap().root_element.clone();
641 let binding = |name: &str| root_element.borrow().binding(name).unwrap().clone();
642 let is_const = |name: &str| {
643 root_element.borrow().binding(name).unwrap().analysis.as_ref().unwrap().is_const
644 };
645
646 assert!(
648 matches!(&binding("int-str").expression, Expression::StringLiteral(s) if s == "n=42"),
649 "{:?}",
650 binding("int-str").expression
651 );
652 assert!(is_const("int-str"));
653 assert!(matches!(&binding("calc-str").expression, Expression::StringLiteral(s) if s == "n=42"));
654 assert!(is_const("calc-str"));
655 assert!(
656 matches!(&binding("int-float").expression, Expression::NumberLiteral(n, _) if *n == 42.)
657 );
658 assert!(is_const("int-float"));
659 assert!(matches!(&binding("int-is-float").expression, Expression::BoolLiteral(true)));
660 assert!(is_const("int-is-float"));
661
662 assert!(
665 !matches!(&binding("float-str").expression, Expression::StringLiteral(_)),
666 "{:?}",
667 binding("float-str").expression
668 );
669 assert!(!is_const("float-str"));
670 assert!(matches!(&binding("frac-float").expression, Expression::FunctionCall { .. }));
671 assert!(!is_const("frac-float"));
672}
673
674#[test]
675fn test_propagate_font_size() {
676 struct Case {
677 default_font_size: &'static str,
678 another_window: &'static str,
679 check_expression: fn(&Expression),
680 }
681
682 #[track_caller]
683 fn assert_expr_is_mul(e: &Expression, l: f64, r: f64) {
684 assert!(
685 matches!(e, Expression::Cast { from, .. }
686 if matches!(from.as_ref(), Expression::BinaryExpression { lhs, rhs, op: '*', ..}
687 if matches!((lhs.as_ref(), rhs.as_ref()), (Expression::NumberLiteral(lhs, _), Expression::NumberLiteral(rhs, _)) if *lhs == l && *rhs == r ))),
688 "Expression {e:?} is not a {l} * {r} expected"
689 );
690 }
691
692 for Case { default_font_size, another_window, check_expression } in [
693 Case {
694 default_font_size: "default-font-size: 12px;",
695 another_window: "",
696 check_expression: |e| assert_expr_is_mul(e, 5.0, 12.0),
697 },
698 Case {
699 default_font_size: "default-font-size: some-value;",
700 another_window: "",
701 check_expression: |e| {
702 assert!(
703 !e.is_constant(None),
704 "{e:?} should not be constant since some-value can vary at runtime"
705 );
706 },
707 },
708 Case {
709 default_font_size: "default-font-size: 25px;",
710 another_window: "export component AnotherWindow inherits Window { default-font-size: 8px; }",
711 check_expression: |e| {
712 assert!(
713 e.is_constant(None) && !matches!(e, Expression::NumberLiteral(_, _)),
714 "{e:?} should be constant but not known at compile time since there are two windows"
715 );
716 },
717 },
718 Case {
719 default_font_size: "default-font-size: 25px;",
720 another_window: "export component AnotherWindow inherits Window { }",
721 check_expression: |e| {
722 assert!(
723 !e.is_constant(None),
724 "should not be const since at least one window has it unset"
725 );
726 },
727 },
728 Case {
729 default_font_size: "default-font-size: 20px;",
730 another_window: "export component AnotherWindow inherits Window { default-font-size: 20px; }",
731 check_expression: |e| assert_expr_is_mul(e, 5.0, 20.0),
732 },
733 Case {
734 default_font_size: "default-font-size: 20px;",
735 another_window: "export component AnotherWindow inherits Window { in property <float> f: 1; default-font-size: 20px*f; }",
736 check_expression: |e| {
737 assert!(
738 !e.is_constant(None),
739 "{e:?} should not be constant since 'f' can vary at runtime"
740 );
741 },
742 },
743 ] {
744 let source = format!(
745 r#"
746component SomeComponent {{
747 in-out property <length> rem-prop: 5rem;
748}}
749
750{another_window}
751
752export component Foo inherits Window {{
753 in property <length> some-value: 45px;
754 {default_font_size}
755 sc1 := SomeComponent {{}}
756 sc2 := SomeComponent {{}}
757
758 out property <length> test: sc1.rem-prop;
759}}
760"#
761 );
762
763 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
764
765 let doc_node = crate::parser::parse(
766 source.clone(),
767 Some(std::path::Path::new("HELLO")),
768 &mut test_diags,
769 );
770 let mut compiler_config =
771 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
772 compiler_config.style = Some("fluent".into());
773 let (doc, diag, _) =
774 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
775 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
776
777 let root_element = doc.inner_components.last().unwrap().root_element.clone();
778 let out1_binding = root_element.borrow().binding("test").unwrap().expression.clone();
779 check_expression(&out1_binding);
780 }
781}
782
783#[test]
784fn test_const_scale_factor() {
785 let source = r#"
786export component Foo inherits Window {
787 out property <length> test: 10phx;
788}"#;
789
790 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
791 let doc_node = crate::parser::parse(
792 source.to_string(),
793 Some(std::path::Path::new("HELLO")),
794 &mut test_diags,
795 );
796 let mut compiler_config =
797 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
798 compiler_config.style = Some("fluent".into());
799 compiler_config.const_scale_factor = Some(2.);
800 let (doc, diag, _) =
801 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
802 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
803
804 let root_element = doc.inner_components.last().unwrap().root_element.clone();
805 let mut test_binding = root_element.borrow().binding("test").unwrap().expression.clone();
806 if let Expression::Cast { from, to: _ } = test_binding {
807 test_binding = *from;
808 }
809 assert!(
810 matches!(test_binding, Expression::NumberLiteral(val, _) if val == 5.0),
811 "Expression should be 5.0: {test_binding:?}"
812 );
813}
814
815#[test]
816fn test_unit_normalization() {
817 fn fold(ty: &str, expr: &str) -> Expression {
819 let mut config =
820 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
821 config.style = Some("fluent".into());
822 let mut diags = crate::diagnostics::BuildDiagnostics::default();
823 let doc_node = crate::parser::parse(
824 format!("export component Foo {{ out property <{ty}> a: {expr}; }}").into(),
825 Some(std::path::Path::new("HELLO")),
826 &mut diags,
827 );
828 let (doc, diag, _) = spin_on::spin_on(crate::compile_syntax_node(doc_node, diags, config));
829 assert!(!diag.has_errors(), "{expr}: {:#?}", diag.to_string_vec());
830 let root_element = doc.inner_components.last().unwrap().root_element.clone();
831 root_element.borrow().binding("a").unwrap().expression.clone()
832 }
833
834 assert!(matches!(fold("length", "1in"), Expression::NumberLiteral(v, Unit::Px) if v == 96.0));
836 assert!(
837 matches!(fold("duration", "2s"), Expression::NumberLiteral(v, Unit::Ms) if v == 2000.0)
838 );
839
840 assert!(
842 matches!(fold("length", "5px + 5cm"), Expression::NumberLiteral(v, Unit::Px) if v == 194.0),
843 "{:?}",
844 fold("length", "5px + 5cm")
845 );
846 assert!(
847 matches!(fold("duration", "1s - 500ms"), Expression::NumberLiteral(v, Unit::Ms) if v == 500.0)
848 );
849 assert!(
850 matches!(fold("length", "max(12cm, 12px)"), Expression::NumberLiteral(v, Unit::Px) if v == 12.0 * 37.8),
851 "{:?}",
852 fold("length", "max(12cm, 12px)")
853 );
854
855 assert!(matches!(fold("bool", "12cm == 12px"), Expression::BoolLiteral(false)));
857 assert!(matches!(fold("bool", "1s == 1000ms"), Expression::BoolLiteral(true)));
858 assert!(matches!(fold("bool", "12cm < 12px"), Expression::BoolLiteral(false)));
859 assert!(matches!(fold("bool", "1turn == 360deg"), Expression::BoolLiteral(true)));
860
861 assert!(
863 matches!(fold("length", "10px * 0.0"), Expression::NumberLiteral(v, Unit::Px) if v == 0.0)
864 );
865 assert!(
866 matches!(fold("float", "10 * 0.0"), Expression::NumberLiteral(v, Unit::None) if v == 0.0)
867 );
868
869 assert!(
871 matches!(fold("float", "3px / 6px"), Expression::NumberLiteral(v, Unit::None) if v == 0.5)
872 );
873 assert!(matches!(fold("bool", "1px != 2px"), Expression::BoolLiteral(true)));
875 assert!(matches!(fold("bool", "true == false"), Expression::BoolLiteral(false)));
876}
877
878#[test]
879fn test_fold_layout_info_merge() {
880 use smol_str::SmolStr;
881 let ty = crate::typeregister::layout_info_type();
882 let info = |min: f64, max: f64, preferred: f64, stretch: f64| Expression::Struct {
883 ty: ty.clone(),
884 values: IntoIterator::into_iter([
885 ("min", Expression::NumberLiteral(min, Unit::Px)),
886 ("max", Expression::NumberLiteral(max, Unit::Px)),
887 ("preferred", Expression::NumberLiteral(preferred, Unit::Px)),
888 ("min_percent", Expression::NumberLiteral(0., Unit::None)),
889 ("max_percent", Expression::NumberLiteral(100., Unit::None)),
890 ("stretch", Expression::NumberLiteral(stretch, Unit::None)),
891 ])
892 .map(|(k, v)| (SmolStr::new_static(k), v))
893 .collect(),
894 };
895
896 let mut expr = Expression::BinaryExpression {
897 lhs: Box::new(info(10., 200., 50., 1.)),
898 rhs: Box::new(info(20., 100., 30., 0.)),
899 op: '+',
900 source_location: None,
901 };
902 fold_const_expression(&mut expr);
903 let Expression::Struct { values, .. } = expr else { panic!("not folded: {expr:?}") };
906 let field = |name: &str| match values.get(name) {
907 Some(Expression::NumberLiteral(v, _)) => *v,
908 other => panic!("field {name} not a literal: {other:?}"),
909 };
910 assert_eq!(field("min"), 20.);
911 assert_eq!(field("max"), 100.);
912 assert_eq!(field("preferred"), 50.);
913 assert_eq!(field("stretch"), 0.);
914
915 let non_literal = Expression::Struct {
917 ty: ty.clone(),
918 values: IntoIterator::into_iter([(
919 SmolStr::new_static("min"),
920 Expression::FunctionParameterReference { index: 0, ty: Type::LogicalLength },
921 )])
922 .collect(),
923 };
924 let mut expr = Expression::BinaryExpression {
925 lhs: Box::new(info(10., 200., 50., 1.)),
926 rhs: Box::new(non_literal),
927 op: '+',
928 source_location: None,
929 };
930 fold_const_expression(&mut expr);
931 assert!(matches!(expr, Expression::BinaryExpression { .. }), "{expr:?}");
932}