i_slint_compiler/passes/
const_propagation.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Try to simplify property bindings by propagating constant expressions
5
6use 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
21/// Returns false if the expression still contains a reference to an element
22fn 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                // Inline the constant value
34                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                // TODO: take care of * and / when both numbers have units
71                ('=' | '!', 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                // TODO: more types and more comparison operators
81                ('&', 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
218/// Will extract the property binding from the given named reference
219/// and propagate constant expression within it. If that's possible,
220/// return the new expression
221fn extract_constant_property_reference(nr: &NamedReference) -> Option<Expression> {
222    debug_assert!(nr.is_constant());
223    // find the binding.
224    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                // TODO: In practice, we should still find out what the real binding is
230                // and solve that.
231                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        // There is no binding for this property, return the default value
247        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        // We have a code block because the first entry stores the value of `intput` in a local variable
336        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}