1use super::GlobalAnalysis;
7use crate::expression_tree::*;
8use crate::langtype::{BuiltinPrivateStruct, ElementType, StructName, Type};
9use crate::object_tree::*;
10use smol_str::{ToSmolStr, format_smolstr};
11
12pub fn const_propagation(component: &Component, global_analysis: &GlobalAnalysis) {
13 visit_all_expressions(component, |expr, ty| {
14 if matches!(ty(), Type::Callback { .. }) {
15 return;
16 }
17 simplify_expression(expr, global_analysis);
18 });
19}
20
21fn simplify_expression(expr: &mut Expression, ga: &GlobalAnalysis) -> bool {
23 match expr {
24 Expression::PropertyReference(nr) => {
25 if nr.is_constant()
26 && !match nr.ty() {
27 Type::Struct(s) => {
28 matches!(
29 s.name,
30 StructName::BuiltinPrivate(BuiltinPrivateStruct::StateInfo)
31 )
32 }
33 _ => false,
34 }
35 {
36 if let Some(result) = extract_constant_property_reference(nr, ga) {
38 *expr = result;
39 return true;
40 }
41 }
42 false
43 }
44 Expression::BinaryExpression { lhs, op, rhs } => {
45 let mut can_inline = simplify_expression(lhs, ga);
46 can_inline &= simplify_expression(rhs, ga);
47
48 let new = match (*op, &mut **lhs, &mut **rhs) {
49 ('+', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
50 Some(Expression::StringLiteral(format_smolstr!("{}{}", a, b)))
51 }
52 ('+', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
53 if un1 == un2 =>
54 {
55 Some(Expression::NumberLiteral(*a + *b, *un1))
56 }
57 ('-', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
58 if un1 == un2 =>
59 {
60 Some(Expression::NumberLiteral(*a - *b, *un1))
61 }
62 ('*', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
63 if *un1 == Unit::None || *un2 == Unit::None =>
64 {
65 let preserved_unit = if *un1 == Unit::None { *un2 } else { *un1 };
66 Some(Expression::NumberLiteral(*a * *b, preserved_unit))
67 }
68 (
69 '/',
70 Expression::NumberLiteral(a, un1),
71 Expression::NumberLiteral(b, Unit::None),
72 ) => Some(Expression::NumberLiteral(*a / *b, *un1)),
73 ('=' | '!', Expression::NumberLiteral(a, _), Expression::NumberLiteral(b, _)) => {
75 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
76 }
77 ('=' | '!', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
78 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
79 }
80 ('=' | '!', Expression::EnumerationValue(a), Expression::EnumerationValue(b)) => {
81 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
82 }
83 ('&', Expression::BoolLiteral(false), _) => {
85 can_inline = true;
86 Some(Expression::BoolLiteral(false))
87 }
88 ('&', _, Expression::BoolLiteral(false)) => {
89 can_inline = true;
90 Some(Expression::BoolLiteral(false))
91 }
92 ('&', Expression::BoolLiteral(true), e) => Some(std::mem::take(e)),
93 ('&', e, Expression::BoolLiteral(true)) => Some(std::mem::take(e)),
94 ('|', Expression::BoolLiteral(true), _) => {
95 can_inline = true;
96 Some(Expression::BoolLiteral(true))
97 }
98 ('|', _, Expression::BoolLiteral(true)) => {
99 can_inline = true;
100 Some(Expression::BoolLiteral(true))
101 }
102 ('|', Expression::BoolLiteral(false), e) => Some(std::mem::take(e)),
103 ('|', e, Expression::BoolLiteral(false)) => Some(std::mem::take(e)),
104 ('>', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
105 if un1 == un2 =>
106 {
107 Some(Expression::BoolLiteral(*a > *b))
108 }
109 ('<', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
110 if un1 == un2 =>
111 {
112 Some(Expression::BoolLiteral(*a < *b))
113 }
114 _ => None,
115 };
116 if let Some(new) = new {
117 *expr = new;
118 }
119 can_inline
120 }
121 Expression::UnaryOp { sub, op } => {
122 let can_inline = simplify_expression(sub, ga);
123 let new = match (*op, &mut **sub) {
124 ('!', Expression::BoolLiteral(b)) => Some(Expression::BoolLiteral(!*b)),
125 ('-', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(-*n, *u)),
126 ('+', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(*n, *u)),
127 _ => None,
128 };
129 if let Some(new) = new {
130 *expr = new;
131 }
132 can_inline
133 }
134 Expression::StructFieldAccess { base, name } => {
135 let r = simplify_expression(base, ga);
136 if let Expression::Struct { values, .. } = &mut **base
137 && let Some(e) = values.remove(name)
138 {
139 *expr = e;
140 return simplify_expression(expr, ga);
141 }
142 r
143 }
144 Expression::Cast { from, to } => {
145 let can_inline = simplify_expression(from, ga);
146 let new = if from.ty() == *to {
147 Some(std::mem::take(&mut **from))
148 } else {
149 match (&**from, to) {
150 (Expression::NumberLiteral(x, Unit::None), Type::String) => {
151 Some(Expression::StringLiteral(x.to_smolstr()))
152 }
153 (Expression::Struct { values, .. }, Type::Struct(ty)) => {
154 Some(Expression::Struct { ty: ty.clone(), values: values.clone() })
155 }
156 _ => None,
157 }
158 };
159 if let Some(new) = new {
160 *expr = new;
161 }
162 can_inline
163 }
164 Expression::MinMax { op, lhs, rhs, ty: _ } => {
165 let can_inline = simplify_expression(lhs, ga) & simplify_expression(rhs, ga);
166 if let (Expression::NumberLiteral(lhs, u), Expression::NumberLiteral(rhs, _)) =
167 (&**lhs, &**rhs)
168 {
169 let v = match op {
170 MinMaxOp::Min => lhs.min(*rhs),
171 MinMaxOp::Max => lhs.max(*rhs),
172 };
173 *expr = Expression::NumberLiteral(v, *u);
174 }
175 can_inline
176 }
177 Expression::Condition { condition, true_expr, false_expr } => {
178 let mut can_inline = simplify_expression(condition, ga);
179 can_inline &= match &**condition {
180 Expression::BoolLiteral(true) => {
181 *expr = *true_expr.clone();
182 simplify_expression(expr, ga)
183 }
184 Expression::BoolLiteral(false) => {
185 *expr = *false_expr.clone();
186 simplify_expression(expr, ga)
187 }
188 _ => simplify_expression(true_expr, ga) & simplify_expression(false_expr, ga),
189 };
190 can_inline
191 }
192 Expression::CodeBlock(stmts)
194 if stmts.len() == 1 && !matches!(stmts[0], Expression::StoreLocalVariable { .. }) =>
195 {
196 *expr = stmts[0].clone();
197 simplify_expression(expr, ga)
198 }
199 Expression::FunctionCall { function, arguments, .. } => {
200 let mut args_can_inline = true;
201 for arg in arguments.iter_mut() {
202 args_can_inline &= simplify_expression(arg, ga);
203 }
204 if args_can_inline && let Some(inlined) = try_inline_function(function, arguments, ga) {
205 *expr = inlined;
206 return true;
207 }
208 false
209 }
210 Expression::ElementReference { .. } => false,
211 Expression::LayoutCacheAccess { .. } => false,
212 Expression::OrganizeGridLayout { .. } => false,
213 Expression::SolveLayout { .. } => false,
214 Expression::ComputeLayoutInfo { .. } => false,
215 _ => {
216 let mut result = true;
217 expr.visit_mut(|expr| result &= simplify_expression(expr, ga));
218 result
219 }
220 }
221}
222
223fn extract_constant_property_reference(
227 nr: &NamedReference,
228 ga: &GlobalAnalysis,
229) -> Option<Expression> {
230 debug_assert!(nr.is_constant());
231 let mut element = nr.element();
233 let mut expression = loop {
234 if let Some(binding) = element.borrow().bindings.get(nr.name()) {
235 let binding = binding.borrow();
236 if !binding.two_way_bindings.is_empty() {
237 return None;
240 }
241 if !matches!(binding.expression, Expression::Invalid) {
242 break binding.expression.clone();
243 }
244 };
245 if let Some(decl) = element.clone().borrow().property_declarations.get(nr.name()) {
246 if let Some(alias) = &decl.is_alias {
247 return extract_constant_property_reference(alias, ga);
248 }
249 } else if let ElementType::Component(c) = &element.clone().borrow().base_type {
250 element = c.root_element.clone();
251 continue;
252 }
253
254 let ty = nr.ty();
256 debug_assert!(!matches!(ty, Type::Invalid));
257 return Some(Expression::default_value_for_type(&ty));
258 };
259 if !(simplify_expression(&mut expression, ga)) {
260 return None;
261 }
262 Some(expression)
263}
264
265fn try_inline_function(
266 function: &Callable,
267 arguments: &[Expression],
268 ga: &GlobalAnalysis,
269) -> Option<Expression> {
270 let function = match function {
271 Callable::Function(function) => function,
272 Callable::Builtin(b) => return try_inline_builtin_function(b, arguments, ga),
273 _ => return None,
274 };
275 if !function.is_constant() {
276 return None;
277 }
278 let mut body = extract_constant_property_reference(function, ga)?;
279
280 fn substitute_arguments_recursive(e: &mut Expression, arguments: &[Expression]) {
281 if let Expression::FunctionParameterReference { index, ty } = e {
282 let e_new = arguments.get(*index).expect("reference to invalid arg").clone();
283 debug_assert_eq!(e_new.ty(), *ty);
284 *e = e_new;
285 } else {
286 e.visit_mut(|e| substitute_arguments_recursive(e, arguments));
287 }
288 }
289 substitute_arguments_recursive(&mut body, arguments);
290
291 if simplify_expression(&mut body, ga) { Some(body) } else { None }
292}
293
294fn try_inline_builtin_function(
295 b: &BuiltinFunction,
296 args: &[Expression],
297 ga: &GlobalAnalysis,
298) -> Option<Expression> {
299 let a = |idx: usize| -> Option<f64> {
300 match args.get(idx)? {
301 Expression::NumberLiteral(n, Unit::None) => Some(*n),
302 _ => None,
303 }
304 };
305 let num = |n: f64| Some(Expression::NumberLiteral(n, Unit::None));
306
307 match b {
308 BuiltinFunction::GetWindowScaleFactor => {
309 ga.const_scale_factor.map(|factor| Expression::NumberLiteral(factor as _, Unit::None))
310 }
311 BuiltinFunction::GetWindowDefaultFontSize => match ga.default_font_size {
312 crate::passes::binding_analysis::DefaultFontSize::LogicalValue(val) => {
313 Some(Expression::NumberLiteral(val as _, Unit::Px))
314 }
315 _ => None,
316 },
317 BuiltinFunction::Mod => num(a(0)?.rem_euclid(a(1)?)),
318 BuiltinFunction::Round => num(a(0)?.round()),
319 BuiltinFunction::Ceil => num(a(0)?.ceil()),
320 BuiltinFunction::Floor => num(a(0)?.floor()),
321 BuiltinFunction::Abs => num(a(0)?.abs()),
322 _ => None,
323 }
324}
325
326#[test]
327fn test() {
328 let mut compiler_config =
329 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
330 compiler_config.style = Some("fluent".into());
331 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
332 let doc_node = crate::parser::parse(
333 r#"
334/* ... */
335struct Hello { s: string, v: float }
336enum Enum { aa, bb, cc }
337global G {
338 pure function complicated(a: float ) -> bool { if a > 5 { return true; }; if a < 1 { return true; }; uncomplicated() }
339 pure function uncomplicated( ) -> bool { false }
340 out property <float> p : 3 * 2 + 15 ;
341 property <string> q: "foo " + 42;
342 out property <float> w : -p / 2;
343 out property <Hello> out: { s: q, v: complicated(w + 15) ? -123 : p };
344
345 in-out property <Enum> e: Enum.bb;
346}
347export component Foo {
348 in property <int> input;
349 out property<float> out1: G.w;
350 out property<float> out2: G.out.v;
351 out property<bool> out3: false ? input == 12 : input > 0 ? input == 11 : G.e == Enum.bb;
352}
353"#
354 .into(),
355 Some(std::path::Path::new("HELLO")),
356 &mut test_diags,
357 );
358 let (doc, diag, _) =
359 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
360 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
361
362 let expected_p = 3.0 * 2.0 + 15.0;
363 let expected_w = -expected_p / 2.0;
364 let bindings = &doc.inner_components.last().unwrap().root_element.borrow().bindings;
365 let out1_binding = bindings.get("out1").unwrap().borrow().expression.clone();
366 match &out1_binding {
367 Expression::NumberLiteral(n, _) => assert_eq!(*n, expected_w),
368 _ => panic!("not number {out1_binding:?}"),
369 }
370 let out2_binding = bindings.get("out2").unwrap().borrow().expression.clone();
371 match &out2_binding {
372 Expression::NumberLiteral(n, _) => assert_eq!(*n, expected_p),
373 _ => panic!("not number {out2_binding:?}"),
374 }
375 let out3_binding = bindings.get("out3").unwrap().borrow().expression.clone();
376 match &out3_binding {
377 Expression::CodeBlock(stmts) => match &stmts[1] {
379 Expression::Condition { condition: _, true_expr: _, false_expr } => match &**false_expr
380 {
381 Expression::BoolLiteral(b) => assert_eq!(*b, true),
382 _ => panic!("false_expr not optimized in : {out3_binding:?}"),
383 },
384 _ => panic!("not condition: {out3_binding:?}"),
385 },
386 _ => panic!("not code block: {out3_binding:?}"),
387 };
388}
389
390#[test]
391fn test_propagate_font_size() {
392 struct Case {
393 default_font_size: &'static str,
394 another_window: &'static str,
395 check_expression: fn(&Expression),
396 }
397
398 #[track_caller]
399 fn assert_expr_is_mul(e: &Expression, l: f64, r: f64) {
400 assert!(
401 matches!(e, Expression::Cast { from, .. }
402 if matches!(from.as_ref(), Expression::BinaryExpression { lhs, rhs, op: '*'}
403 if matches!((lhs.as_ref(), rhs.as_ref()), (Expression::NumberLiteral(lhs, _), Expression::NumberLiteral(rhs, _)) if *lhs == l && *rhs == r ))),
404 "Expression {e:?} is not a {l} * {r} expected"
405 );
406 }
407
408 for Case { default_font_size, another_window, check_expression } in [
409 Case {
410 default_font_size: "default-font-size: 12px;",
411 another_window: "",
412 check_expression: |e| assert_expr_is_mul(e, 5.0, 12.0),
413 },
414 Case {
415 default_font_size: "default-font-size: some-value;",
416 another_window: "",
417 check_expression: |e| {
418 assert!(
419 !e.is_constant(None),
420 "{e:?} should not be constant since some-value can vary at runtime"
421 );
422 },
423 },
424 Case {
425 default_font_size: "default-font-size: 25px;",
426 another_window: "export component AnotherWindow inherits Window { default-font-size: 8px; }",
427 check_expression: |e| {
428 assert!(
429 e.is_constant(None) && !matches!(e, Expression::NumberLiteral(_, _)),
430 "{e:?} should be constant but not known at compile time since there are two windows"
431 );
432 },
433 },
434 Case {
435 default_font_size: "default-font-size: 25px;",
436 another_window: "export component AnotherWindow inherits Window { }",
437 check_expression: |e| {
438 assert!(
439 !e.is_constant(None),
440 "should not be const since at least one window has it unset"
441 );
442 },
443 },
444 Case {
445 default_font_size: "default-font-size: 20px;",
446 another_window: "export component AnotherWindow inherits Window { default-font-size: 20px; }",
447 check_expression: |e| assert_expr_is_mul(e, 5.0, 20.0),
448 },
449 Case {
450 default_font_size: "default-font-size: 20px;",
451 another_window: "export component AnotherWindow inherits Window { in property <float> f: 1; default-font-size: 20px*f; }",
452 check_expression: |e| {
453 assert!(
454 !e.is_constant(None),
455 "{e:?} should not be constant since 'f' can vary at runtime"
456 );
457 },
458 },
459 ] {
460 let source = format!(
461 r#"
462component SomeComponent {{
463 in-out property <length> rem-prop: 5rem;
464}}
465
466{another_window}
467
468export component Foo inherits Window {{
469 in property <length> some-value: 45px;
470 {default_font_size}
471 sc1 := SomeComponent {{}}
472 sc2 := SomeComponent {{}}
473
474 out property <length> test: sc1.rem-prop;
475}}
476"#
477 );
478
479 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
480
481 let doc_node = crate::parser::parse(
482 source.clone(),
483 Some(std::path::Path::new("HELLO")),
484 &mut test_diags,
485 );
486 let mut compiler_config =
487 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
488 compiler_config.style = Some("fluent".into());
489 let (doc, diag, _) =
490 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
491 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
492
493 let bindings = &doc.inner_components.last().unwrap().root_element.borrow().bindings;
494 let out1_binding = bindings.get("test").unwrap().borrow().expression.clone();
495 check_expression(&out1_binding);
496 }
497}
498
499#[test]
500fn test_const_scale_factor() {
501 let source = r#"
502export component Foo inherits Window {
503 out property <length> test: 10phx;
504}"#;
505
506 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
507 let doc_node = crate::parser::parse(
508 source.to_string(),
509 Some(std::path::Path::new("HELLO")),
510 &mut test_diags,
511 );
512 let mut compiler_config =
513 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
514 compiler_config.style = Some("fluent".into());
515 compiler_config.const_scale_factor = Some(2.);
516 let (doc, diag, _) =
517 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
518 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
519
520 let bindings = &doc.inner_components.last().unwrap().root_element.borrow().bindings;
521 let mut test_binding = bindings.get("test").unwrap().borrow().expression.clone();
522 if let Expression::Cast { from, to: _ } = test_binding {
523 test_binding = *from;
524 }
525 assert!(
526 matches!(test_binding, Expression::NumberLiteral(val, _) if val == 5.0),
527 "Expression should be 5.0: {test_binding:?}"
528 );
529}