i_slint_compiler/passes/resolving/
remove_noop.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
4use crate::{diagnostics::BuildDiagnostics, expression_tree::Expression, parser::SyntaxNode};
5
6/// Remove all expressions that are proven to have no effect from the given Expressions.
7///
8/// This function assumes that the given Expressions will form the an [Expression::CodeBlock], and
9/// will therefore not modify the last Expression in the Vec, as that forms the result value of the
10/// CodeBlock itself.
11pub fn remove_from_codeblock(
12    code_block: &mut Vec<(SyntaxNode, Expression)>,
13    diagnostics: &mut BuildDiagnostics,
14) {
15    if code_block.len() > 1 {
16        // In a code block, only the last expression returns a value.
17        // Therefore all other expressions inside the block are only useful if they have side
18        // effects.
19        //
20        // Remove all expressions without side effects (except for the last one) and emit a
21        // warning.
22        //
23        // Note: Iterate over the indices in reverse, so that all to-be-iterated indices remain
24        // valid when removing items from the vector.
25        for index in (0..(code_block.len() - 1)).rev() {
26            let (node, expression) = &code_block[index];
27            if without_side_effects(expression) {
28                diagnostics.push_warning("Expression has no effect!".to_owned(), node);
29                code_block.remove(index);
30            }
31        }
32    }
33}
34
35/// Returns whether the expression is certain to be without side effects.
36/// This function is conservative and may still return `false`, even if a given expression
37/// is without side effects.
38/// It is only guaranteed that if this function returns `true`, the expression definitely does not
39/// contain side effects.
40fn without_side_effects(expression: &Expression) -> bool {
41    match expression {
42        Expression::Condition { condition, true_expr, false_expr } => {
43            without_side_effects(condition)
44                && without_side_effects(true_expr)
45                && without_side_effects(false_expr)
46        }
47        Expression::NumberLiteral(_, _) => true,
48        Expression::StringLiteral(_) => true,
49        Expression::BoolLiteral(_) => true,
50        Expression::CodeBlock(expressions) => expressions.iter().all(without_side_effects),
51        Expression::FunctionParameterReference { .. } => true,
52        // Invalid and uncompiled expressions are unknown at this point, so default to
53        // `false`, because they may have side-efffects.
54        Expression::Invalid => false,
55        Expression::Uncompiled(_) => false,
56        // A property reference may cause re-evaluation of a property, which may result in
57        // side effects
58        Expression::PropertyReference(_) => false,
59        Expression::ElementReference(_) => false,
60        Expression::RepeaterIndexReference { .. } => true,
61        Expression::RepeaterModelReference { .. } => true,
62        Expression::StoreLocalVariable { .. } => false,
63        Expression::ReadLocalVariable { .. } => true,
64        Expression::StructFieldAccess { base, name: _ } => without_side_effects(&*base),
65        Expression::ArrayIndex { array, index } => {
66            without_side_effects(&*array) && without_side_effects(&*index)
67        }
68        // Note: This assumes that the cast itself does not have any side effects, which may not be
69        // the case if custom casting rules are implemented.
70        Expression::Cast { from, to: _ } => without_side_effects(from),
71        // Note: Calling a *pure* function is without side effects, however
72        // just from the expression, the purity of the function is not known.
73        // We would need to resolve the function to determine its purity.
74        Expression::FunctionCall { .. } => false,
75        Expression::SelfAssignment { .. } => false,
76        Expression::BinaryExpression { lhs, rhs, .. } => {
77            without_side_effects(&*lhs) && without_side_effects(&*rhs)
78        }
79        Expression::UnaryOp { sub, op: _ } => without_side_effects(&*sub),
80        Expression::ImageReference { .. } => true,
81        Expression::Array { element_ty: _, values } => values.iter().all(without_side_effects),
82        Expression::Struct { ty: _, values } => values.values().all(without_side_effects),
83        Expression::PathData(_) => true,
84        Expression::EasingCurve(_) => true,
85        Expression::LinearGradient { angle, stops } => {
86            without_side_effects(&angle)
87                && stops
88                    .iter()
89                    .all(|(start, end)| without_side_effects(start) && without_side_effects(end))
90        }
91        Expression::RadialGradient { stops } => stops
92            .iter()
93            .all(|(start, end)| without_side_effects(start) && without_side_effects(end)),
94        Expression::ConicGradient { stops } => stops
95            .iter()
96            .all(|(start, end)| without_side_effects(start) && without_side_effects(end)),
97        Expression::EnumerationValue(_) => true,
98        // A return statement is never without side effects, as an important "side effect" is that
99        // the current function stops at this point.
100        Expression::ReturnStatement(_) => false,
101        Expression::LayoutCacheAccess { .. } => false,
102        Expression::ComputeLayoutInfo(_, _) => false,
103        Expression::SolveLayout(_, _) => false,
104        Expression::MinMax { ty: _, op: _, lhs, rhs } => {
105            without_side_effects(lhs) && without_side_effects(rhs)
106        }
107        Expression::DebugHook { .. } => false,
108        Expression::EmptyComponentFactory => false,
109    }
110}