Skip to main content

i_slint_compiler/passes/
resolve_native_classes.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//! After inlining and moving declarations, all Element::base_type should be Type::BuiltinElement. This pass resolves them
5//! to NativeClass and picking a variant that only contains the used properties.
6
7use smol_str::SmolStr;
8use std::collections::HashSet;
9use std::rc::Rc;
10
11use crate::langtype::{ElementType, NativeClass};
12use crate::object_tree::{Component, recurse_elem_including_sub_components};
13
14pub fn resolve_native_classes(component: &Component) {
15    recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
16        let new_native_class = {
17            let elem = elem.borrow();
18
19            let base_type = match &elem.base_type {
20                ElementType::Component(_) => {
21                    // recurse_elem_including_sub_components will recurse into it
22                    return;
23                }
24                ElementType::Builtin(b) => b,
25                ElementType::Native(_) => {
26                    // already native
27                    return;
28                }
29                ElementType::Interface | ElementType::Global | ElementType::Error => {
30                    panic!("This should not happen")
31                }
32            };
33
34            let analysis = elem.property_analysis.borrow();
35            let native_properties_used: HashSet<_> = elem
36                .bindings
37                .keys()
38                .chain(analysis.iter().filter(|(_, v)| v.is_used()).map(|(k, _)| k))
39                .filter(|k| {
40                    !elem.property_declarations.contains_key(*k)
41                        && base_type.as_ref().properties.contains_key(*k)
42                })
43                .collect();
44
45            select_minimal_class_based_on_property_usage(
46                &elem.base_type.as_builtin().native_class,
47                native_properties_used.into_iter(),
48            )
49        };
50
51        elem.borrow_mut().base_type = ElementType::Native(new_native_class);
52    })
53}
54
55fn lookup_property_distance(mut class: Rc<NativeClass>, name: &str) -> (usize, Rc<NativeClass>) {
56    let mut distance = 0;
57    loop {
58        if class.properties.contains_key(name)
59            || (class.parent.is_none() && ["x", "y", "width", "height"].contains(&name))
60        {
61            return (distance, class);
62        }
63        distance += 1;
64        class = class.parent.as_ref().unwrap().clone();
65    }
66}
67
68fn select_minimal_class_based_on_property_usage<'a>(
69    class: &Rc<NativeClass>,
70    properties_used: impl Iterator<Item = &'a SmolStr>,
71) -> Rc<NativeClass> {
72    let mut minimal_class = class.clone();
73    while let Some(class) = minimal_class.parent.clone() {
74        minimal_class = class;
75    }
76    let (_min_distance, minimal_class) = properties_used.fold(
77        (usize::MAX, minimal_class),
78        |(current_distance, current_class), prop_name| {
79            let (prop_distance, prop_class) = lookup_property_distance(class.clone(), prop_name);
80
81            if prop_distance < current_distance {
82                (prop_distance, prop_class)
83            } else {
84                (current_distance, current_class)
85            }
86        },
87    );
88    minimal_class
89}
90
91#[test]
92fn test_select_minimal_class_based_on_property_usage() {
93    use crate::langtype::{BuiltinPropertyInfo, Type};
94    use smol_str::ToSmolStr;
95    let first = Rc::new(NativeClass::new_with_properties(
96        "first_class",
97        [("first_prop".to_smolstr(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(),
98    ));
99
100    let mut second = NativeClass::new_with_properties(
101        "second_class",
102        [("second_prop".to_smolstr(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(),
103    );
104    second.parent = Some(first.clone());
105    let second = Rc::new(second);
106
107    let reduce_to_first =
108        select_minimal_class_based_on_property_usage(&second, ["first_prop".to_smolstr()].iter());
109
110    assert_eq!(reduce_to_first.class_name, first.class_name);
111
112    let reduce_to_second =
113        select_minimal_class_based_on_property_usage(&second, ["second_prop".to_smolstr()].iter());
114
115    assert_eq!(reduce_to_second.class_name, second.class_name);
116
117    let reduce_to_second = select_minimal_class_based_on_property_usage(
118        &second,
119        ["first_prop".to_smolstr(), "second_prop".to_smolstr()].iter(),
120    );
121
122    assert_eq!(reduce_to_second.class_name, second.class_name);
123}
124
125#[test]
126fn select_minimal_class() {
127    use smol_str::ToSmolStr;
128    let tr = crate::typeregister::TypeRegister::builtin();
129    let tr = tr.borrow();
130    let rect = tr.lookup_element("Rectangle").unwrap();
131    let rect = rect.as_builtin();
132    assert_eq!(
133        select_minimal_class_based_on_property_usage(
134            &rect.native_class,
135            ["x".to_smolstr(), "width".to_smolstr()].iter()
136        )
137        .class_name,
138        "Empty",
139    );
140    assert_eq!(
141        select_minimal_class_based_on_property_usage(&rect.native_class, [].iter()).class_name,
142        "Empty",
143    );
144    assert_eq!(
145        select_minimal_class_based_on_property_usage(
146            &rect.native_class,
147            ["border-width".to_smolstr()].iter()
148        )
149        .class_name,
150        "BasicBorderRectangle",
151    );
152    assert_eq!(
153        select_minimal_class_based_on_property_usage(
154            &rect.native_class,
155            ["border-width".to_smolstr(), "x".to_smolstr()].iter()
156        )
157        .class_name,
158        "BasicBorderRectangle",
159    );
160    assert_eq!(
161        select_minimal_class_based_on_property_usage(
162            &rect.native_class,
163            ["border-top-left-radius".to_smolstr(), "x".to_smolstr()].iter()
164        )
165        .class_name,
166        "BorderRectangle",
167    );
168}