1use crate::expression_tree::*;
7use crate::langtype::ElementType;
8use crate::langtype::Type;
9use crate::object_tree::*;
10use smol_str::{format_smolstr, ToSmolStr};
11
12pub fn const_propagation(component: &Component) {
13 visit_all_expressions(component, |expr, ty| {
14 if matches!(ty(), Type::Callback { .. }) {
15 return;
16 }
17 simplify_expression(expr);
18 });
19}
20
21fn simplify_expression(expr: &mut Expression) -> bool {
23 match expr {
24 Expression::PropertyReference(nr) => {
25 if nr.is_constant()
26 && !match nr.ty() {
27 Type::Struct(s) => {
28 s.name.as_ref().is_some_and(|name| name.ends_with("::StateInfo"))
29 }
30 _ => false,
31 }
32 {
33 if let Some(result) = extract_constant_property_reference(nr) {
35 *expr = result;
36 return true;
37 }
38 }
39 false
40 }
41 Expression::BinaryExpression { lhs, op, rhs } => {
42 let mut can_inline = simplify_expression(lhs);
43 can_inline &= simplify_expression(rhs);
44
45 let new = match (*op, &mut **lhs, &mut **rhs) {
46 ('+', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
47 Some(Expression::StringLiteral(format_smolstr!("{}{}", a, b)))
48 }
49 ('+', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
50 if un1 == un2 =>
51 {
52 Some(Expression::NumberLiteral(*a + *b, *un1))
53 }
54 ('-', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
55 if un1 == un2 =>
56 {
57 Some(Expression::NumberLiteral(*a - *b, *un1))
58 }
59 ('*', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
60 if *un1 == Unit::None || *un2 == Unit::None =>
61 {
62 let preserved_unit = if *un1 == Unit::None { *un2 } else { *un1 };
63 Some(Expression::NumberLiteral(*a * *b, preserved_unit))
64 }
65 (
66 '/',
67 Expression::NumberLiteral(a, un1),
68 Expression::NumberLiteral(b, Unit::None),
69 ) => Some(Expression::NumberLiteral(*a / *b, *un1)),
70 ('=' | '!', Expression::NumberLiteral(a, _), Expression::NumberLiteral(b, _)) => {
72 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
73 }
74 ('=' | '!', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
75 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
76 }
77 ('=' | '!', Expression::EnumerationValue(a), Expression::EnumerationValue(b)) => {
78 Some(Expression::BoolLiteral((a == b) == (*op == '=')))
79 }
80 ('&', Expression::BoolLiteral(false), _) => {
82 can_inline = true;
83 Some(Expression::BoolLiteral(false))
84 }
85 ('&', _, Expression::BoolLiteral(false)) => {
86 can_inline = true;
87 Some(Expression::BoolLiteral(false))
88 }
89 ('&', Expression::BoolLiteral(true), e) => Some(std::mem::take(e)),
90 ('&', e, Expression::BoolLiteral(true)) => Some(std::mem::take(e)),
91 ('|', Expression::BoolLiteral(true), _) => {
92 can_inline = true;
93 Some(Expression::BoolLiteral(true))
94 }
95 ('|', _, Expression::BoolLiteral(true)) => {
96 can_inline = true;
97 Some(Expression::BoolLiteral(true))
98 }
99 ('|', Expression::BoolLiteral(false), e) => Some(std::mem::take(e)),
100 ('|', e, Expression::BoolLiteral(false)) => Some(std::mem::take(e)),
101 ('>', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
102 if un1 == un2 =>
103 {
104 Some(Expression::BoolLiteral(*a > *b))
105 }
106 ('<', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
107 if un1 == un2 =>
108 {
109 Some(Expression::BoolLiteral(*a < *b))
110 }
111 _ => None,
112 };
113 if let Some(new) = new {
114 *expr = new;
115 }
116 can_inline
117 }
118 Expression::UnaryOp { sub, op } => {
119 let can_inline = simplify_expression(sub);
120 let new = match (*op, &mut **sub) {
121 ('!', Expression::BoolLiteral(b)) => Some(Expression::BoolLiteral(!*b)),
122 ('-', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(-*n, *u)),
123 ('+', Expression::NumberLiteral(n, u)) => Some(Expression::NumberLiteral(*n, *u)),
124 _ => None,
125 };
126 if let Some(new) = new {
127 *expr = new;
128 }
129 can_inline
130 }
131 Expression::StructFieldAccess { base, name } => {
132 let r = simplify_expression(base);
133 if let Expression::Struct { values, .. } = &mut **base {
134 if let Some(e) = values.remove(name) {
135 *expr = e;
136 return simplify_expression(expr);
137 }
138 }
139 r
140 }
141 Expression::Cast { from, to } => {
142 let can_inline = simplify_expression(from);
143 let new = if from.ty() == *to {
144 Some(std::mem::take(&mut **from))
145 } else {
146 match (&**from, to) {
147 (Expression::NumberLiteral(x, Unit::None), Type::String) => {
148 Some(Expression::StringLiteral((*x).to_smolstr()))
149 }
150 (Expression::Struct { values, .. }, Type::Struct(ty)) => {
151 Some(Expression::Struct { ty: ty.clone(), values: values.clone() })
152 }
153 _ => None,
154 }
155 };
156 if let Some(new) = new {
157 *expr = new;
158 }
159 can_inline
160 }
161 Expression::MinMax { op, lhs, rhs, ty: _ } => {
162 let can_inline = simplify_expression(lhs) & simplify_expression(rhs);
163 if let (Expression::NumberLiteral(lhs, u), Expression::NumberLiteral(rhs, _)) =
164 (&**lhs, &**rhs)
165 {
166 let v = match op {
167 MinMaxOp::Min => lhs.min(*rhs),
168 MinMaxOp::Max => lhs.max(*rhs),
169 };
170 *expr = Expression::NumberLiteral(v, *u);
171 }
172 can_inline
173 }
174 Expression::Condition { condition, true_expr, false_expr } => {
175 let mut can_inline = simplify_expression(condition);
176 can_inline &= match &**condition {
177 Expression::BoolLiteral(true) => {
178 *expr = *true_expr.clone();
179 simplify_expression(expr)
180 }
181 Expression::BoolLiteral(false) => {
182 *expr = *false_expr.clone();
183 simplify_expression(expr)
184 }
185 _ => simplify_expression(true_expr) & simplify_expression(false_expr),
186 };
187 can_inline
188 }
189 Expression::CodeBlock(stmts) if stmts.len() == 1 => {
190 *expr = stmts[0].clone();
191 simplify_expression(expr)
192 }
193 Expression::FunctionCall { function, arguments, .. } => {
194 let mut args_can_inline = true;
195 for arg in arguments.iter_mut() {
196 args_can_inline &= simplify_expression(arg);
197 }
198 if args_can_inline {
199 if let Some(inlined) = try_inline_function(function, arguments) {
200 *expr = inlined;
201 return true;
202 }
203 }
204 false
205 }
206 Expression::ElementReference { .. } => false,
207 Expression::LayoutCacheAccess { .. } => false,
208 Expression::SolveLayout { .. } => false,
209 Expression::ComputeLayoutInfo { .. } => false,
210 _ => {
211 let mut result = true;
212 expr.visit_mut(|expr| result &= simplify_expression(expr));
213 result
214 }
215 }
216}
217
218fn extract_constant_property_reference(nr: &NamedReference) -> Option<Expression> {
222 debug_assert!(nr.is_constant());
223 let mut element = nr.element();
225 let mut expression = loop {
226 if let Some(binding) = element.borrow().bindings.get(nr.name()) {
227 let binding = binding.borrow();
228 if !binding.two_way_bindings.is_empty() {
229 return None;
232 }
233 if !matches!(binding.expression, Expression::Invalid) {
234 break binding.expression.clone();
235 }
236 };
237 if let Some(decl) = element.clone().borrow().property_declarations.get(nr.name()) {
238 if let Some(alias) = &decl.is_alias {
239 return extract_constant_property_reference(alias);
240 }
241 } else if let ElementType::Component(c) = &element.clone().borrow().base_type {
242 element = c.root_element.clone();
243 continue;
244 }
245
246 let ty = nr.ty();
248 debug_assert!(!matches!(ty, Type::Invalid));
249 return Some(Expression::default_value_for_type(&ty));
250 };
251 if !(simplify_expression(&mut expression)) {
252 return None;
253 }
254 Some(expression)
255}
256
257fn try_inline_function(function: &Callable, arguments: &[Expression]) -> Option<Expression> {
258 let Callable::Function(function) = function else {
259 return None;
260 };
261 if !function.is_constant() {
262 return None;
263 }
264 let mut body = extract_constant_property_reference(function)?;
265
266 fn substitute_arguments_recursive(e: &mut Expression, arguments: &[Expression]) {
267 if let Expression::FunctionParameterReference { index, ty } = e {
268 let e_new = arguments.get(*index).expect("reference to invalid arg").clone();
269 debug_assert_eq!(e_new.ty(), *ty);
270 *e = e_new;
271 } else {
272 e.visit_mut(|e| substitute_arguments_recursive(e, arguments));
273 }
274 }
275 substitute_arguments_recursive(&mut body, arguments);
276
277 if simplify_expression(&mut body) {
278 Some(body)
279 } else {
280 None
281 }
282}
283
284#[test]
285fn test() {
286 let mut compiler_config =
287 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
288 compiler_config.style = Some("fluent".into());
289 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
290 let doc_node = crate::parser::parse(
291 r#"
292/* ... */
293struct Hello { s: string, v: float }
294enum Enum { aa, bb, cc }
295global G {
296 pure function complicated(a: float ) -> bool { if a > 5 { return true; }; if a < 1 { return true; }; uncomplicated() }
297 pure function uncomplicated( ) -> bool { false }
298 out property <float> p : 3 * 2 + 15 ;
299 property <string> q: "foo " + 42;
300 out property <float> w : -p / 2;
301 out property <Hello> out: { s: q, v: complicated(w + 15) ? -123 : p };
302
303 in-out property <Enum> e: Enum.bb;
304}
305export component Foo {
306 in property <int> input;
307 out property<float> out1: G.w;
308 out property<float> out2: G.out.v;
309 out property<bool> out3: false ? input == 12 : input > 0 ? input == 11 : G.e == Enum.bb;
310}
311"#
312 .into(),
313 Some(std::path::Path::new("HELLO")),
314 &mut test_diags,
315 );
316 let (doc, diag, _) =
317 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
318 assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
319
320 let expected_p = 3.0 * 2.0 + 15.0;
321 let expected_w = -expected_p / 2.0;
322 let bindings = &doc.inner_components.last().unwrap().root_element.borrow().bindings;
323 let out1_binding = bindings.get("out1").unwrap().borrow().expression.clone();
324 match &out1_binding {
325 Expression::NumberLiteral(n, _) => assert_eq!(*n, expected_w),
326 _ => panic!("not number {out1_binding:?}"),
327 }
328 let out2_binding = bindings.get("out2").unwrap().borrow().expression.clone();
329 match &out2_binding {
330 Expression::NumberLiteral(n, _) => assert_eq!(*n, expected_p),
331 _ => panic!("not number {out2_binding:?}"),
332 }
333 let out3_binding = bindings.get("out3").unwrap().borrow().expression.clone();
334 match &out3_binding {
335 Expression::CodeBlock(stmts) => match &stmts[1] {
337 Expression::Condition { condition: _, true_expr: _, false_expr } => match &**false_expr
338 {
339 Expression::BoolLiteral(b) => assert_eq!(*b, true),
340 _ => panic!("false_expr not optimized in : {out3_binding:?}"),
341 },
342 _ => panic!("not condition: {out3_binding:?}"),
343 },
344 _ => panic!("not code block: {out3_binding:?}"),
345 };
346}