Skip to main content

i_slint_compiler/passes/
remove_constant_conditions.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//! Remove `if false` conditional elements
5//!
6//! Conditions often fold to a literal only during const propagation, long
7//! after the repeater machinery for the dead subtree was built; this pass
8//! deletes it again.
9
10use crate::expression_tree::Expression;
11use crate::layout::{BOX_LAYOUT_CACHE_ENTRIES_PER_CELL, Layout};
12use crate::namedreference::NamedReference;
13use crate::object_tree::*;
14use std::collections::HashMap;
15use std::rc::Rc;
16
17pub fn remove_constant_conditions(component: &Rc<Component>) {
18    let mut dead = Vec::new();
19    recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
20        let e = elem.borrow();
21        // Grid cells keep their per-child repeater; the ComponentContainer placeholder is a
22        // permanent false repeater that must survive as the embed slot.
23        // For a repeated element, the grid_layout_cell sits on the repeater component's root.
24        let is_grid_cell = e.grid_layout_cell.is_some()
25            || matches!(&e.base_type, crate::langtype::ElementType::Component(c)
26                if c.root_element.borrow().grid_layout_cell.is_some());
27        if e.repeated.as_ref().is_some_and(|r| {
28            r.is_conditional_element && matches!(r.model, Expression::BoolLiteral(false))
29        }) && !is_grid_cell
30            && !e.is_component_placeholder
31        {
32            dead.push(elem.clone());
33        }
34    });
35    if dead.is_empty() {
36        return;
37    }
38
39    // Flexbox cells keep their length-zero repeater; the measure path queries
40    // cells individually.
41    visit_all_expressions(component, |expr, _| {
42        expr.visit_recursive_mut(&mut |e| {
43            if let Expression::SolveFlexboxLayout(l)
44            | Expression::ComputeFlexboxLayoutInfo { layout: l, .. } = e
45            {
46                dead.retain(|c| !l.elems.iter().any(|it| Rc::ptr_eq(&it.element, c)));
47            }
48        })
49    });
50    if dead.is_empty() {
51        return;
52    }
53    let is_dead = |e: &ElementRc| dead.iter().any(|d| Rc::ptr_eq(d, e));
54
55    // The component root of each removed conditional. Its box cell's constraints reference this
56    // root, so a stale debug snapshot below is left still naming it.
57    let mut dead_roots = std::collections::HashSet::new();
58    for c in &dead {
59        if let crate::langtype::ElementType::Component(base) = &c.borrow().base_type {
60            dead_roots.insert(Rc::as_ptr(&base.root_element));
61        }
62    }
63
64    let mut fixes: HashMap<NamedReference, Vec<usize>> = HashMap::new();
65    recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
66        visit_element_expressions(elem, |expr, name, _| {
67            let Some(name) = name else { return };
68            expr.visit_recursive_mut(&mut |e| match e {
69                Expression::SolveBoxLayout(l, _) => {
70                    let bases: Vec<usize> = l
71                        .elems
72                        .iter()
73                        .enumerate()
74                        .filter(|(_, it)| is_dead(&it.element))
75                        .map(|(k, _)| BOX_LAYOUT_CACHE_ENTRIES_PER_CELL * k)
76                        .collect();
77                    if !bases.is_empty() {
78                        fixes.insert(NamedReference::new(elem, name.into()), bases);
79                        l.elems.retain(|it| !is_dead(&it.element));
80                    }
81                }
82                Expression::ComputeBoxLayoutInfo { layout: l, .. } => {
83                    l.elems.retain(|it| !is_dead(&it.element))
84                }
85                _ => {}
86            });
87        });
88        // Drop debug cells naming a removed root, or a NamedReference to a dropped element crashes
89        // a later pass; the layout-expression removal above does not reach these snapshots.
90        for d in elem.borrow_mut().debug.iter_mut() {
91            if let Some(Layout::BoxLayout(l)) = d.layout.as_mut() {
92                l.elems.retain(|it| {
93                    let mut hit = false;
94                    it.constraints.clone().visit_named_references(&mut |nr| {
95                        hit |= dead_roots.contains(&Rc::as_ptr(&nr.element()))
96                    });
97                    !hit
98                });
99            }
100        }
101        elem.borrow_mut().children.retain(|c| !is_dead(c));
102    });
103
104    // Each removed cell freed BOX_LAYOUT_CACHE_ENTRIES_PER_CELL cache slots whose indices were
105    // baked into geometry bindings, so shift every surviving access behind it down.
106    visit_all_expressions(component, |expr, _| {
107        expr.visit_recursive_mut(&mut |e| {
108            if let Expression::LayoutCacheAccess { layout_cache_prop, index, .. } = e
109                && let Some(bases) = fixes.get(layout_cache_prop)
110            {
111                *index -= BOX_LAYOUT_CACHE_ENTRIES_PER_CELL
112                    * bases.iter().filter(|b| **b < *index).count();
113            }
114        })
115    });
116}
117
118#[test]
119fn removes_constant_false_conditionals() {
120    let mut compiler_config =
121        crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
122    compiler_config.style = Some("fluent".into());
123    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
124    let doc_node = crate::parser::parse(
125        r#"
126export component Foo {
127    in property <bool> dynamic;
128    property <bool> never: false;
129    if false: Rectangle {}
130    if never: Rectangle {}
131    if dynamic: Rectangle {}
132}
133"#
134        .into(),
135        Some(std::path::Path::new("test.slint")),
136        &mut test_diags,
137    );
138    let (doc, diag, _) =
139        spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
140    assert!(!diag.has_errors(), "slint compile error {:#?}", diag.to_string_vec());
141
142    let foo = doc.inner_components.iter().find(|c| c.id == "Foo").unwrap();
143    let mut models = Vec::new();
144    recurse_elem_including_sub_components(foo, &(), &mut |elem, _| {
145        if let Some(r) = &elem.borrow().repeated
146            && r.is_conditional_element
147        {
148            models.push(r.model.clone());
149        }
150    });
151
152    // `never` folds to false during const propagation and is removed like the literal
153    // `if false`; only `dynamic`, an `in` property, stays non-constant and survives, its
154    // model still a runtime expression rather than a folded literal.
155    assert_eq!(models.len(), 1, "{models:?}");
156    assert!(!matches!(models[0], Expression::BoolLiteral(_)), "{:?}", models[0]);
157}