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