1use smol_str::SmolStr;
9use std::collections::HashSet;
10use std::sync::Arc;
11
12use crate::expression_tree::{BindingExpression, Expression};
13use crate::langtype::{BuiltinElement, BuiltinPropertyDefault, ElementType, NativeClass};
14use crate::object_tree::{Component, recurse_elem_including_sub_components};
15
16pub fn resolve_native_classes(component: &Component) {
17 recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
18 let (new_native_class, unused_defaults) = {
19 let elem = elem.borrow();
20
21 let base_type = match &elem.base_type {
22 ElementType::Component(_) => {
23 return;
25 }
26 ElementType::Builtin(b) => b,
27 ElementType::Native(_) => {
28 return;
30 }
31 ElementType::Interface | ElementType::Global | ElementType::Error => {
32 panic!("This should not happen")
33 }
34 };
35
36 let defaults: Vec<&SmolStr> = elem
37 .bindings_including_synthetic()
38 .filter(|(name, binding)| is_default_value(base_type, name, &binding.borrow()))
39 .map(|(name, _)| name)
40 .collect();
41
42 let analysis = elem.property_analysis.borrow();
43 let native_properties_used: HashSet<_> = elem
44 .bindings_including_synthetic()
45 .map(|(k, _)| k)
46 .filter(|k| !defaults.contains(k))
47 .chain(analysis.iter().filter(|(_, v)| v.is_used()).map(|(k, _)| k))
48 .filter(|k| {
49 !elem.property_declarations.contains_key(*k)
50 && base_type.as_ref().properties.contains_key(*k)
51 })
52 .collect();
53
54 let new_native_class = select_minimal_class_based_on_property_usage(
55 &base_type.native_class,
56 native_properties_used.into_iter(),
57 );
58
59 let unused_defaults: Vec<SmolStr> = defaults
62 .into_iter()
63 .filter(|name| {
64 new_native_class.lookup_property(name).is_none()
65 && !elem.named_references.is_referenced(name)
66 })
67 .cloned()
68 .collect();
69
70 (new_native_class, unused_defaults)
71 };
72
73 let mut elem = elem.borrow_mut();
74 for name in unused_defaults {
75 elem.take_binding_including_synthetic(&name);
76 }
77 elem.base_type = ElementType::Native(new_native_class);
78 })
79}
80
81fn is_default_value(base_type: &BuiltinElement, name: &str, binding: &BindingExpression) -> bool {
84 let Some(BuiltinPropertyDefault::Expr(default)) =
85 base_type.properties.get(name).map(|p| &p.default_value)
86 else {
87 return false;
88 };
89 binding.animation.is_none()
90 && binding.two_way_bindings.is_empty()
91 && same_literal(binding.value_expression(), &default.to_expression())
92}
93
94fn same_literal(a: &Expression, b: &Expression) -> bool {
96 match (a, b) {
97 (Expression::NumberLiteral(a, a_unit), Expression::NumberLiteral(b, b_unit)) => {
98 a == b && a_unit == b_unit
99 }
100 (Expression::BoolLiteral(a), Expression::BoolLiteral(b)) => a == b,
101 (Expression::StringLiteral(a), Expression::StringLiteral(b)) => a == b,
102 (Expression::EnumerationValue(a), Expression::EnumerationValue(b)) => a == b,
103 (Expression::Cast { from: a, to: a_type }, Expression::Cast { from: b, to: b_type }) => {
105 a_type == b_type && same_literal(a, b)
106 }
107 _ => false,
108 }
109}
110
111fn lookup_property_distance(mut class: Arc<NativeClass>, name: &str) -> (usize, Arc<NativeClass>) {
112 let mut distance = 0;
113 loop {
114 if class.properties.contains_key(name)
115 || (class.parent.is_none() && ["x", "y", "width", "height"].contains(&name))
116 {
117 return (distance, class);
118 }
119 distance += 1;
120 class = class.parent.as_ref().unwrap().clone();
121 }
122}
123
124fn select_minimal_class_based_on_property_usage<'a>(
125 class: &Arc<NativeClass>,
126 properties_used: impl Iterator<Item = &'a SmolStr>,
127) -> Arc<NativeClass> {
128 let mut minimal_class = class.clone();
129 while let Some(class) = minimal_class.parent.clone() {
130 minimal_class = class;
131 }
132 let (_min_distance, minimal_class) = properties_used.fold(
133 (usize::MAX, minimal_class),
134 |(current_distance, current_class), prop_name| {
135 let (prop_distance, prop_class) = lookup_property_distance(class.clone(), prop_name);
136
137 if prop_distance < current_distance {
138 (prop_distance, prop_class)
139 } else {
140 (current_distance, current_class)
141 }
142 },
143 );
144 minimal_class
145}
146
147#[test]
148fn test_select_minimal_class_based_on_property_usage() {
149 use crate::langtype::{BuiltinPropertyInfo, Type};
150 use smol_str::ToSmolStr;
151 let first = Arc::new(NativeClass::new_with_properties(
152 "first_class",
153 [("first_prop".to_smolstr(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(),
154 ));
155
156 let mut second = NativeClass::new_with_properties(
157 "second_class",
158 [("second_prop".to_smolstr(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(),
159 );
160 second.parent = Some(first.clone());
161 let second = Arc::new(second);
162
163 let reduce_to_first =
164 select_minimal_class_based_on_property_usage(&second, ["first_prop".to_smolstr()].iter());
165
166 assert_eq!(reduce_to_first.class_name, first.class_name);
167
168 let reduce_to_second =
169 select_minimal_class_based_on_property_usage(&second, ["second_prop".to_smolstr()].iter());
170
171 assert_eq!(reduce_to_second.class_name, second.class_name);
172
173 let reduce_to_second = select_minimal_class_based_on_property_usage(
174 &second,
175 ["first_prop".to_smolstr(), "second_prop".to_smolstr()].iter(),
176 );
177
178 assert_eq!(reduce_to_second.class_name, second.class_name);
179}
180
181#[test]
182fn builtin_defaults_are_comparable() {
183 let tr = crate::typeregister::TypeRegister::builtin();
184 let tr = tr.borrow();
185 for (name, element) in tr.all_elements() {
186 let ElementType::Builtin(element) = element else { continue };
187 for (property, info) in &element.properties {
188 if let BuiltinPropertyDefault::Expr(default) = &info.default_value {
189 let default = default.to_expression();
190 assert!(
191 same_literal(&default, &default),
192 "the default of {name}::{property} is a shape same_literal doesn't compare, \
193 so the property always counts as used: {default:?}"
194 );
195 }
196 }
197 }
198}
199
200#[test]
201fn select_minimal_class() {
202 use smol_str::ToSmolStr;
203 let tr = crate::typeregister::TypeRegister::builtin();
204 let tr = tr.borrow();
205 let rect = tr.lookup_element("Rectangle").unwrap();
206 let rect = rect.as_builtin();
207 assert_eq!(
208 select_minimal_class_based_on_property_usage(
209 &rect.native_class,
210 ["x".to_smolstr(), "width".to_smolstr()].iter()
211 )
212 .class_name,
213 "Empty",
214 );
215 assert_eq!(
216 select_minimal_class_based_on_property_usage(&rect.native_class, [].iter()).class_name,
217 "Empty",
218 );
219 assert_eq!(
220 select_minimal_class_based_on_property_usage(
221 &rect.native_class,
222 ["border-width".to_smolstr()].iter()
223 )
224 .class_name,
225 "BasicBorderRectangle",
226 );
227 assert_eq!(
228 select_minimal_class_based_on_property_usage(
229 &rect.native_class,
230 ["border-width".to_smolstr(), "x".to_smolstr()].iter()
231 )
232 .class_name,
233 "BasicBorderRectangle",
234 );
235 assert_eq!(
236 select_minimal_class_based_on_property_usage(
237 &rect.native_class,
238 ["border-top-left-radius".to_smolstr(), "x".to_smolstr()].iter()
239 )
240 .class_name,
241 "BorderRectangle",
242 );
243}