i_slint_compiler/passes/
resolve_native_classes.rs1use 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 return;
23 }
24 ElementType::Builtin(b) => b,
25 ElementType::Native(_) => {
26 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}