1use super::GlobalAnalysis;
7use crate::expression_tree::*;
8use crate::langtype::ElementType;
9use crate::langtype::Type;
10use crate::object_tree::*;
11use smol_str::{format_smolstr, ToSmolStr};
12
13pub fn const_propagation(component: &Component, global_analysis: &GlobalAnalysis) {
14 visit_all_expressions(component, |expr, ty| {
15 if matches!(ty(), Type::Callback { .. }) {
16 return;
17 }
18 simplify_expression(expr, global_analysis);
19 });
20}
21
22fn simplify_expression(expr: &mut Expression, ga: &GlobalAnalysis) -> bool {
24 match expr {
25 Expression::PropertyReference(nr) => {
26 if nr.is_constant()
27 && !match nr.ty() {
28 Type::Struct(s) => {
29 s.name.as_ref().is_some_and(|name| name.ends_with("::StateInfo"))
30 }
31 _ => false,
32 }
33 {
34 if let Some(result) = extract_constant_property_reference(nr, ga) {
36 *expr = result;
37 return true;
38 }
39 }
40 false
41 }
42 Expression::BinaryExpression { lhs, op, rhs } => {
43 let mut can_inline = simplify_expression(lhs, ga);
44 can_inline &= simplify_expression(rhs, ga);
45
46 let new = match (*op, &mut **lhs, &mut **rhs) {
47 ('+', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
48 Some(Expression::StringLiteral(format_smolstr!("{}{}", a, b)))
49 }
50 ('+', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
51 if un1 == un2 =>
52 {
53 Some(Expression::NumberLiteral(*a + *b, *un1))
54 }
55 ('-', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
56 if un1 == un2 =>
57 {
58 Some(Expression::NumberLiteral(*a - *b, *un1))
59 }
60 ('*', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
61 if *un1 == Unit::None || *un2 == Unit::None =>
62 {
63 let preserved_unit = if *un1 == Unit::None { *un2 } else { *un1 };
64 Some(Expression::NumberLiteral(*a * *b, preserved_unit))
65 }
66 (
67 '/',
68 Expression::NumberLiteral(a, un1),
69 Expression::NumberLiteral(b, Unit::None),
70 ) => Some(Expression::NumberLiteral(*a / *b, *un1)),
71 ('=' | '!', Expression::NumberLiteral(a, _), Expression::NumberLiteral(b, _)) => {
73 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
74 }
75 ('=' | '!', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
76 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
77 }
78 ('=' | '!', Expression::EnumerationValue(a), Expression::EnumerationValue(b)) => {
79 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
80 }
81 ('&', Expression::BoolLiteral(false), _) => {
83 can_inline = true;
84 Some(Expression::BoolLiteral(false))
85 }
86 ('&', _, Expression::BoolLiteral(false)) => {
87 can_inline = true;
88 Some(Expression::BoolLiteral(false))
89 }
90 ('&', Expression::BoolLiteral(true), e) => Some(std::mem::take(e)),
91 ('&', e, Expression::BoolLiteral(true)) => Some(std::mem::take(e)),
92 ('|', Expression::BoolLiteral(true), _) => {
93 can_inline = true;
94 Some(Expression::BoolLiteral(true))
95 }
96 ('|', _, Expression::BoolLiteral(true)) => {
97 can_inline = true;
98 Some(Expression::BoolLiteral(true))
99 }
100 ('|', Expression::BoolLiteral(false), e) => Some(std::mem::take(e)),
101 ('|', e, Expression::BoolLiteral(false)) => Some(std::mem::take(e)),
102 ('>', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
103 if un1 == un2 =>
104 {
105 Some(Expression::BoolLiteral(*a > *b))
106 }
107 ('<', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
108 if un1 == un2 =>
109 {
110 Some(Expression::BoolLiteral(*a < *b))
111 }
112 _ => None,
113 };
114 if let Some(new) = new {
115 *expr = new;
116 }
117 can_inline
118 }
119 Expression::UnaryOp { sub, op } => {
120 let can_inline = simplify_expression(sub, ga);
121 let new = match (*op, &mut **sub) {
122 ('!', Expression::BoolLiteral(b)) => Some(Expression::BoolLiteral(!*b)),
123 ('-', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(-*n, *u)),
124 ('+', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(*n, *u)),
125 _ => None,
126 };
127 if let Some(new) = new {
128 *expr = new;
129 }
130 can_inline
131 }
132 Expression::StructFieldAccess { base, name } => {
133 let r = simplify_expression(base, ga);
134 if let Expression::Struct { values, .. } = &mut **base {
135 if let Some(e) = values.remove(name) {
136 *expr = e;
137 return simplify_expression(expr, ga);
138 }
139 }
140 r
141 }
142 Expression::Cast { from, to } => {
143 let can_inline = simplify_expression(from, ga);
144 let new = if from.ty() == *to {
145 Some(std::mem::take(&mut **from))
146 } else {
147 match (&**from, to) {
148 (Expression::NumberLiteral(x, Unit::None), Type::String) => {
149 Some(Expression::StringLiteral(x.to_smolstr()))
150 }
151 (Expression::Struct { values, .. }, Type::Struct(ty)) => {
152 Some(Expression::Struct { ty: ty.clone(), values: values.clone() })
153 }
154 _ => None,
155 }
156 };
157 if let Some(new) = new {
158 *expr = new;
159 }
160 can_inline
161 }
162 Expression::MinMax { op, lhs, rhs, ty: _ } => {
163 let can_inline = simplify_expression(lhs, ga) & simplify_expression(rhs, ga);
164 if let (Expression::NumberLiteral(lhs, u), Expression::NumberLiteral(rhs, _)) =
165 (&**lhs, &**rhs)
166 {
167 let v = match op {
168 MinMaxOp::Min => lhs.min(*rhs),
169 MinMaxOp::Max => lhs.max(*rhs),
170 };
171 *expr = Expression::NumberLiteral(v, *u);
172 }
173 can_inline
174 }
175 Expression::Condition { condition, true_expr, false_expr } => {
176 let mut can_inline = simplify_expression(condition, ga);
177 can_inline &= match &**condition {
178 Expression::BoolLiteral(true) => {
179 *expr = *true_expr.clone();
180 simplify_expression(expr, ga)
181 }
182 Expression::BoolLiteral(false) => {
183 *expr = *false_expr.clone();
184 simplify_expression(expr, ga)
185 }
186 _ => simplify_expression(true_expr, ga) & simplify_expression(false_expr, ga),
187 };
188 can_inline
189 }
190 Expression::CodeBlock(stmts)
192 if stmts.len() == 1 && !matches!(stmts[0], Expression::StoreLocalVariable { .. }) =>
193 {
194 *expr = stmts[0].clone();
195 simplify_expression(expr, ga)
196 }
197 Expression::FunctionCall { function, arguments, .. } => {
198 let mut args_can_inline = true;
199 for arg in arguments.iter_mut() {
200 args_can_inline &= simplify_expression(arg, ga);
201 }
202 if args_can_inline {
203 if let Some(inlined) = try_inline_function(function, arguments, ga) {
204 *expr = inlined;
205 return true;
206 }
207 }
208 false
209 }
210 Expression::ElementReference { .. } => false,
211 Expression::LayoutCacheAccess { .. } => false,
212 Expression::SolveLayout { .. } => false,
213 Expression::ComputeLayoutInfo { .. } => false,
214 _ => {
215 let mut result = true;
216 expr.visit_mut(|expr| result &= simplify_expression(expr, ga));
217 result
218 }
219 }
220}
221
222fn extract_constant_property_reference(
226 nr: &NamedReference,
227 ga: &GlobalAnalysis,
228) -> Option<Expression> {
229 debug_assert!(nr.is_constant());
230 let mut element = nr.element();
232 let mut expression = loop {
233 if let Some(binding) = element.borrow().bindings.get(nr.name()) {
234 let binding = binding.borrow();
235 if !binding.two_way_bindings.is_empty() {
236 return None;
239 }
240 if !matches!(binding.expression, Expression::Invalid) {
241 break binding.expression.clone();
242 }
243 };
244 if let Some(decl) = element.clone().borrow().property_declarations.get(nr.name()) {
245 if let Some(alias) = &decl.is_alias {
246 return extract_constant_property_reference(alias, ga);
247 }
248 } else if let ElementType::Component(c) = &element.clone().borrow().base_type {
249 element = c.root_element.clone();
250 continue;
251 }
252
253 let ty = nr.ty();
255 debug_assert!(!matches!(ty, Type::Invalid));
256 return Some(Expression::default_value_for_type(&ty));
257 };
258 if !(simplify_expression(&mut expression, ga)) {
259 return None;
260 }
261 Some(expression)
262}
263
264fn try_inline_function(
265 function: &Callable,
266 arguments: &[Expression],
267 ga: &GlobalAnalysis,
268) -> Option<Expression> {
269 let function = match function {
270 Callable::Function(function) => function,
271 Callable::Builtin(b) => return try_inline_builtin_function(b, arguments, ga),
272 _ => return None,
273 };
274 if !function.is_constant() {
275 return None;
276 }
277 let mut body = extract_constant_property_reference(function, ga)?;
278
279 fn substitute_arguments_recursive(e: &mut Expression, arguments: &[Expression]) {
280 if let Expression::FunctionParameterReference { index, ty } = e {
281 let e_new = arguments.get(*index).expect("reference to invalid arg").clone();
282 debug_assert_eq!(e_new.ty(), *ty);
283 *e = e_new;
284 } else {
285 e.visit_mut(|e| substitute_arguments_recursive(e, arguments));
286 }
287 }
288 substitute_arguments_recursive(&mut body, arguments);
289
290 if simplify_expression(&mut body, ga) {
291 Some(body)
292 } else {
293 None
294 }
295}
296
297fn try_inline_builtin_function(
298 b: &BuiltinFunction,
299 args: &[Expression],
300 ga: &GlobalAnalysis,
301) -> Option<Expression> {
302 let a = |idx: usize| -> Option<f64> {
303 match args.get(idx)? {
304 Expression::NumberLiteral(n, Unit::None) => Some(*n),
305 _ => None,
306 }
307 };
308 let num = |n: f64| Some(Expression::NumberLiteral(n, Unit::None));
309
310 match b {
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!(!e.is_constant(None), "{e:?} should not be constant since some-value can vary at runtime");
419 },
420 },
421 Case {
422 default_font_size: "default-font-size: 25px;",
423 another_window: "export component AnotherWindow inherits Window { default-font-size: 8px; }",
424 check_expression: |e| {
425 assert!(e.is_constant(None) && !matches!(e, Expression::NumberLiteral(_,_ )), "{e:?} should be constant but not known at compile time since there are two windows");
426 },
427 },
428 Case {
429 default_font_size: "default-font-size: 25px;",
430 another_window: "export component AnotherWindow inherits Window { }",
431 check_expression: |e| {
432 assert!(!e.is_constant(None), "should not be const since at least one window has it unset");
433 },
434 },
435 Case {
436 default_font_size: "default-font-size: 20px;",
437 another_window: "export component AnotherWindow inherits Window { default-font-size: 20px; }",
438 check_expression: |e| assert_expr_is_mul(e, 5.0, 20.0)
439 },
440 Case {
441 default_font_size: "default-font-size: 20px;",
442 another_window: "export component AnotherWindow inherits Window { in property <float> f: 1; default-font-size: 20px*f; }",
443 check_expression: |e| {
444 assert!(!e.is_constant(None), "{e:?} should not be constant since 'f' can vary at runtime");
445 },
446 },
447
448 ] {
449 let source = format!(
450 r#"
451component SomeComponent {{
452 in-out property <length> rem-prop: 5rem;
453}}
454
455{another_window}
456
457export component Foo inherits Window {{
458 in property <length> some-value: 45px;
459 {default_font_size}
460 sc1 := SomeComponent {{}}
461 sc2 := SomeComponent {{}}
462
463 out property <length> test: sc1.rem-prop;
464}}
465"#
466 );
467
468 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
469
470 let doc_node = crate::parser::parse(
471 source.clone(),
472 Some(std::path::Path::new("HELLO")),
473 &mut test_diags,
474 );
475 let mut compiler_config =
476 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
477 compiler_config.style = Some("fluent".into());
478 let (doc, diag, _) =
479 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
480 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
481
482 let bindings = &doc.inner_components.last().unwrap().root_element.borrow().bindings;
483 let out1_binding = bindings.get("test").unwrap().borrow().expression.clone();
484 check_expression(&out1_binding);
485 }
486}