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