1use crate::llr::*;
5use typed_index_collections::TiVec;
6
7struct Mapping {
8 prop_mapping: TiVec<PropertyIdx, Option<PropertyIdx>>,
9 callback_mapping: TiVec<CallbackIdx, Option<CallbackIdx>>,
10 function_mapping: TiVec<FunctionIdx, Option<FunctionIdx>>,
11}
12
13impl Mapping {
14 fn keep(&self, member: &LocalMemberIndex) -> bool {
15 match member {
16 LocalMemberIndex::Property(p) => self.prop_mapping[*p].is_some(),
17 LocalMemberIndex::Callback(c) => self.callback_mapping[*c].is_some(),
18 LocalMemberIndex::Function(f) => self.function_mapping[*f].is_some(),
19 LocalMemberIndex::Native { .. } | LocalMemberIndex::Timer(_) => true,
20 }
21 }
22}
23
24type ScMappings = TiVec<SubComponentIdx, Mapping>;
25type GlobMappings = TiVec<GlobalIdx, Mapping>;
26
27pub fn remove_unused(root: &mut CompilationUnit) {
28 struct RemoveUnusedMappings {
29 sc_mappings: ScMappings,
30 glob_mappings: GlobMappings,
31 }
32 let mappings = RemoveUnusedMappings {
33 sc_mappings: root
34 .sub_components
35 .iter_mut()
36 .map(|sc| create_mapping(&mut sc.properties, &mut sc.callbacks, &mut sc.functions))
37 .collect(),
38 glob_mappings: root
39 .globals
40 .iter_mut()
41 .map(|g| {
42 clean_vec(&mut g.const_properties, &g.properties);
43 clean_vec(&mut g.prop_analysis, &g.properties);
44 create_mapping(&mut g.properties, &mut g.callbacks, &mut g.functions)
45 })
46 .collect(),
47 };
48
49 let state = visitor::VisitorState::new(root);
50
51 for (idx, sc) in root.sub_components.iter_mut_enumerated() {
52 let keep = |refer: &MemberReference| match refer {
53 MemberReference::Relative { parent_level, local_reference } => {
54 assert_eq!(*parent_level, 0);
55 let idx = state.follow_sub_components(idx, &local_reference.sub_component_path);
56 mappings.sc_mappings[idx].keep(&local_reference.reference)
57 }
58 MemberReference::Global { global_index, member } => {
59 mappings.glob_mappings[*global_index].keep(member)
60 }
61 };
62
63 let mut property_init_mapping = Vec::new();
64 let mut i = 0;
65 sc.property_init.retain(|(x, v)| {
66 if keep(x) && v.use_count.get() > 0 {
67 property_init_mapping.push(Some(i));
68 i += 1;
69 true
70 } else {
71 property_init_mapping.push(None);
72 false
73 }
74 });
75 sc.change_callbacks.retain(|(x, _)| keep(x));
76 sc.const_properties.retain(|x| {
77 let idx = state.follow_sub_components(idx, &x.sub_component_path);
78 mappings.sc_mappings[idx].keep(&x.reference)
79 });
80 sc.prop_analysis.retain(|x, v| {
81 v.property_init = v.property_init.and_then(|x| property_init_mapping[x]);
82 keep(x)
83 });
84 sc.animations.retain(|x, _| keep(&x.clone().into()));
85 }
86 for (idx, g) in root.globals.iter_mut_enumerated() {
87 g.init_values.retain(|x, _| mappings.glob_mappings[idx].keep(x));
88 }
89
90 macro_rules! remap_index {
91 ($method:ident, $idx:ty, $field:ident) => {
92 fn $method(
93 &mut self,
94 p: &mut $idx,
95 scope: &EvaluationScope,
96 _state: &visitor::VisitorState,
97 ) {
98 *p = match scope {
99 EvaluationScope::SubComponent(sub_component_idx, _) => {
100 self.sc_mappings[*sub_component_idx].$field[*p]
101 }
102 EvaluationScope::Global(global_idx) => {
103 self.glob_mappings[*global_idx].$field[*p]
104 }
105 EvaluationScope::Const => {
106 panic!("member reference in a constant expression")
107 }
108 }
109 .unwrap();
110 }
111 };
112 }
113 impl visitor::Visitor for &RemoveUnusedMappings {
114 remap_index!(visit_property_idx, PropertyIdx, prop_mapping);
118 remap_index!(visit_callback_idx, CallbackIdx, callback_mapping);
119 remap_index!(visit_function_idx, FunctionIdx, function_mapping);
120 }
121 let mut visitor = &mappings;
122 visitor::visit_compilation_unit(root, &state, &mut visitor);
123}
124
125fn create_mapping(
126 properties: &mut TiVec<PropertyIdx, Property>,
127 callbacks: &mut TiVec<CallbackIdx, Callback>,
128 functions: &mut TiVec<FunctionIdx, Function>,
129) -> Mapping {
130 Mapping {
131 prop_mapping: create_vec_mapping(properties, |p| p.use_count.get() > 0),
132 callback_mapping: create_vec_mapping(callbacks, |c| c.use_count.get() > 0),
133 function_mapping: create_vec_mapping(functions, |f| f.use_count.get() > 0),
134 }
135}
136
137fn create_vec_mapping<Idx: From<usize>, T>(
138 vec: &mut TiVec<Idx, T>,
139 mut retain: impl FnMut(&T) -> bool,
140) -> TiVec<Idx, Option<Idx>> {
141 let mut map = TiVec::with_capacity(vec.len());
142 let mut i = 0;
143 vec.retain(|t| {
144 if retain(t) {
145 map.push(Some(Idx::from(i)));
146 i += 1;
147 true
148 } else {
149 map.push(None);
150 false
151 }
152 });
153 map
154}
155
156fn clean_vec<T>(vec: &mut TiVec<PropertyIdx, T>, properties: &TiVec<PropertyIdx, Property>) {
157 let mut idx = 0;
158 vec.retain(|_| {
159 idx += 1;
160 properties[PropertyIdx::from(idx - 1)].use_count.get() >= 1
161 });
162}
163
164mod visitor {
165
166 use super::*;
167
168 pub trait Visitor {
169 fn visit_property_idx(
170 &mut self,
171 _p: &mut PropertyIdx,
172 _scope: &EvaluationScope,
173 _state: &VisitorState,
174 ) {
175 }
176 fn visit_function_idx(
177 &mut self,
178 _p: &mut FunctionIdx,
179 _scope: &EvaluationScope,
180 _state: &VisitorState,
181 ) {
182 }
183
184 fn visit_callback_idx(
185 &mut self,
186 _p: &mut CallbackIdx,
187 _scope: &EvaluationScope,
188 _state: &VisitorState,
189 ) {
190 }
191 }
192
193 pub struct VisitorState {
194 sub_component_maps: TiVec<SubComponentIdx, TiVec<SubComponentInstanceIdx, SubComponentIdx>>,
196 parent_mapping: TiVec<SubComponentIdx, Option<SubComponentIdx>>,
198 }
199
200 impl VisitorState {
201 pub fn new(cu: &CompilationUnit) -> Self {
202 let mut parent_mapping = TiVec::new();
203 parent_mapping.resize(cu.sub_components.len(), None);
204 for (idx, sc) in cu.sub_components.iter_enumerated() {
205 for r in &sc.repeated {
206 parent_mapping[r.sub_tree.root] = Some(idx);
207 }
208 for p in &sc.popup_windows {
209 parent_mapping[p.item_tree.root] = Some(idx);
210 }
211 for m in &sc.menu_item_trees {
212 parent_mapping[m.root] = Some(idx);
213 }
214 }
215 Self {
216 sub_component_maps: cu
217 .sub_components
218 .iter()
219 .map(|sc| sc.sub_components.iter().map(|x| x.ty).collect())
220 .collect(),
221 parent_mapping,
222 }
223 }
224
225 pub fn follow_sub_components(
226 &self,
227 mut sc: SubComponentIdx,
228 sub_component_path: &[SubComponentInstanceIdx],
229 ) -> SubComponentIdx {
230 for i in sub_component_path {
231 sc = self.sub_component_maps[sc][*i];
232 }
233 sc
234 }
235 }
236
237 pub fn visit_compilation_unit(
238 CompilationUnit {
239 public_components,
240 sub_components,
241 used_sub_components: _,
242 globals,
243 popup_menu,
244 has_debug_info: _,
245 type_exports: _,
246 #[cfg(feature = "bundle-translations")]
247 translations: _,
248 }: &mut crate::llr::CompilationUnit,
249 state: &VisitorState,
250 visitor: &mut (impl Visitor + ?Sized),
251 ) {
252 for c in public_components {
253 visit_public_component(c, state, visitor);
254 }
255 for (idx, sc) in sub_components.iter_mut_enumerated() {
256 visit_sub_component(idx, sc, state, visitor);
257 }
258 for (idx, g) in globals.iter_mut_enumerated() {
259 visit_global(idx, g, state, visitor);
260 }
261 if let Some(p) = popup_menu {
262 visit_popup_menu(p, state, visitor);
263 }
264 }
265
266 pub fn visit_public_component(
267 PublicComponent {
268 public_properties,
269 private_properties: _,
270 item_tree,
271 name: _,
272 top_level_type: _,
273 }: &mut PublicComponent,
274 state: &VisitorState,
275 visitor: &mut (impl Visitor + ?Sized),
276 ) {
277 let scope = EvaluationScope::SubComponent(item_tree.root, None);
278 for p in public_properties.values_mut() {
279 visit_public_property(p, &scope, state, visitor);
280 }
281 visit_tree_node_z_properties(&mut item_tree.tree, &scope, state, visitor);
282 }
283
284 fn visit_tree_node_z_properties(
287 node: &mut crate::llr::TreeNode,
288 scope: &EvaluationScope,
289 state: &VisitorState,
290 visitor: &mut (impl Visitor + ?Sized),
291 ) {
292 if let Some(z_props) = &mut node.z_sort_order_property {
293 for z_source in z_props {
294 if let crate::llr::ZSource::Expression(e) = z_source {
295 visit_expression(e.get_mut(), scope, state, visitor);
296 }
297 }
298 }
299 for child in &mut node.children {
300 visit_tree_node_z_properties(child, scope, state, visitor);
301 }
302 }
303
304 pub fn visit_sub_component(
305 idx: SubComponentIdx,
306 SubComponent {
307 name: _,
308 properties: _,
309 callbacks: _,
310 functions,
311 items: _,
312 repeated,
313 component_containers: _,
314 popup_windows,
315 menu_item_trees: _,
316 timers,
317 sub_components: _,
318 property_init,
319 change_callbacks,
320 animations,
321 two_way_bindings,
322 const_properties,
323 pre_init_code,
324 init_code,
325 geometries,
326 layout_info_h,
327 layout_info_v,
328 child_of_layout: _,
329 grid_layout_input_for_repeated,
330 flexbox_layout_item_info_for_repeated,
331 cross_axis_self_alignment_for_repeated,
332 layout_order_for_repeated,
333 layout_info_v_constrained_for_repeated,
334 layout_info_v_at_cross_width_for_repeated,
335 grid_row_child_cross_width,
336 is_repeated_row: _,
337 grid_layout_children,
338 accessible_prop,
339 element_infos: _,
340 row_child_templates: _,
341 prop_analysis,
342 debug_info: _,
343 }: &mut SubComponent,
344 state: &VisitorState,
345 visitor: &mut (impl Visitor + ?Sized),
346 ) {
347 let scope = EvaluationScope::SubComponent(idx, None);
348 for f in functions {
349 visit_function(f, &scope, state, visitor);
350 }
351 for RepeatedElement {
352 model,
353 index_prop,
354 data_prop,
355 dynamic_z,
356 sub_tree,
357 index_in_tree: _,
358 listview,
359 container_item_index: _,
360 } in repeated
361 {
362 visit_expression(model.get_mut(), &scope, state, visitor);
363 let inner_scope = EvaluationScope::SubComponent(sub_tree.root, None);
364 if let Some(index_prop) = index_prop {
365 visitor.visit_property_idx(index_prop, &inner_scope, state);
366 }
367 if let Some(data_prop) = data_prop {
368 visitor.visit_property_idx(data_prop, &inner_scope, state);
369 }
370 if let Some(dynamic_z) = dynamic_z {
371 visit_member_reference(dynamic_z, &inner_scope, state, visitor);
372 }
373
374 visit_tree_node_z_properties(&mut sub_tree.tree, &inner_scope, state, visitor);
375
376 if let Some(listview) = listview {
377 visit_member_reference(&mut listview.content_y, &scope, state, visitor);
378 if let Some(content_height) = &mut listview.content_height {
379 visit_member_reference(content_height, &scope, state, visitor);
380 }
381 if let Some(content_width) = &mut listview.content_width {
382 visit_member_reference(content_width, &scope, state, visitor);
383 }
384 visit_member_reference(&mut listview.listview_width, &scope, state, visitor);
385 visit_member_reference(&mut listview.listview_height, &scope, state, visitor);
386
387 visit_member_reference(&mut listview.prop_y, &inner_scope, state, visitor);
388 visit_member_reference(&mut listview.prop_height, &inner_scope, state, visitor);
389 }
390 }
391
392 for p in popup_windows {
393 let popup_scope = EvaluationScope::SubComponent(p.item_tree.root, None);
394 visit_expression(p.position.get_mut(), &popup_scope, state, visitor);
395 visit_tree_node_z_properties(&mut p.item_tree.tree, &popup_scope, state, visitor);
396 }
397 for t in timers {
398 visit_expression(t.interval.get_mut(), &scope, state, visitor);
399 visit_expression(t.triggered.get_mut(), &scope, state, visitor);
400 visit_expression(t.running.get_mut(), &scope, state, visitor);
401 }
402 for (idx, init) in property_init {
403 visit_member_reference(idx, &scope, state, visitor);
404 visit_binding_expression(init, &scope, state, visitor);
405 }
406 for (idx, e) in change_callbacks {
407 visit_member_reference(idx, &scope, state, visitor);
408 visit_expression(e.get_mut(), &scope, state, visitor);
409 }
410 *animations = std::mem::take(animations)
411 .into_iter()
412 .map(|(mut k, mut v)| {
413 visit_local_member_reference(&mut k, &scope, state, visitor);
414 visit_expression(&mut v, &scope, state, visitor);
415 (k, v)
416 })
417 .collect();
418
419 for twb in two_way_bindings {
420 visit_local_member_reference(&mut twb.prop1, &scope, state, visitor);
421 visit_member_reference(&mut twb.prop2, &scope, state, visitor);
422 }
423 for c in const_properties {
424 visit_local_member_reference(c, &scope, state, visitor);
425 }
426 for i in pre_init_code.iter_mut().chain(init_code) {
427 visit_expression(i.get_mut(), &scope, state, visitor);
428 }
429 for g in geometries.iter_mut().flatten() {
430 visit_expression(g.get_mut(), &scope, state, visitor);
431 }
432 visit_expression(layout_info_h.get_mut(), &scope, state, visitor);
433 visit_expression(layout_info_v.get_mut(), &scope, state, visitor);
434 if let Some(e) = grid_layout_input_for_repeated {
435 visit_expression(e.get_mut(), &scope, state, visitor);
436 }
437 if let Some(e) = flexbox_layout_item_info_for_repeated {
438 visit_expression(e.get_mut(), &scope, state, visitor);
439 }
440 if let Some((_, e)) = cross_axis_self_alignment_for_repeated {
441 visit_expression(e.get_mut(), &scope, state, visitor);
442 }
443 if let Some((_, e)) = layout_order_for_repeated {
444 visit_expression(e.get_mut(), &scope, state, visitor);
445 }
446 if let Some(e) = layout_info_v_constrained_for_repeated {
447 visit_expression(e.get_mut(), &scope, state, visitor);
448 }
449 if let Some(e) = layout_info_v_at_cross_width_for_repeated {
450 visit_expression(e.get_mut(), &scope, state, visitor);
451 }
452 if let Some(e) = grid_row_child_cross_width {
453 visit_expression(e.get_mut(), &scope, state, visitor);
454 }
455 for child in grid_layout_children {
456 visit_expression(child.layout_info_h.get_mut(), &scope, state, visitor);
457 visit_expression(child.layout_info_v.get_mut(), &scope, state, visitor);
458 }
459
460 for a in accessible_prop.values_mut() {
461 visit_expression(a.get_mut(), &scope, state, visitor);
462 }
463
464 *prop_analysis = std::mem::take(prop_analysis)
465 .into_iter()
466 .map(|(mut k, v)| {
467 visit_member_reference(&mut k, &scope, state, visitor);
468 (k, v)
469 })
470 .collect();
471 }
472
473 fn visit_global(
474 global_idx: GlobalIdx,
475 GlobalComponent {
476 name: _,
477 properties: _,
478 callbacks: _,
479 functions,
480 init_values,
481 change_callbacks,
482 const_properties: _,
483 public_properties,
484 private_properties: _,
485 exported: _,
486 aliases: _,
487 is_builtin: _,
488 from_library: _,
489 prop_analysis: _,
490 }: &mut GlobalComponent,
491 state: &VisitorState,
492 visitor: &mut (impl Visitor + ?Sized),
493 ) {
494 let scope = EvaluationScope::Global(global_idx);
495 for f in functions {
496 visit_function(f, &scope, state, visitor);
497 }
498
499 *init_values = std::mem::take(init_values)
500 .into_iter()
501 .map(|(mut k, mut v)| {
502 visit_member_index(&mut k, &scope, state, visitor);
503 visit_binding_expression(&mut v, &scope, state, visitor);
504 (k, v)
505 })
506 .collect();
507
508 *change_callbacks = std::mem::take(change_callbacks)
509 .into_iter()
510 .map(|(mut k, mut v)| {
511 visitor.visit_property_idx(&mut k, &scope, state);
512 visit_expression(v.get_mut(), &scope, state, visitor);
513 (k, v)
514 })
515 .collect();
516
517 for p in public_properties.values_mut() {
518 visit_public_property(p, &scope, state, visitor);
519 }
520 }
521
522 pub fn visit_popup_menu(
523 PopupMenu { item_tree, sub_menu, activated, close, entries }: &mut PopupMenu,
524 state: &VisitorState,
525 visitor: &mut (impl Visitor + ?Sized),
526 ) {
527 let scope = EvaluationScope::SubComponent(item_tree.root, None);
528 visit_member_reference(sub_menu, &scope, state, visitor);
529 visit_member_reference(activated, &scope, state, visitor);
530 visit_member_reference(close, &scope, state, visitor);
531 visit_member_reference(entries, &scope, state, visitor);
532 visit_tree_node_z_properties(&mut item_tree.tree, &scope, state, visitor);
533 }
534
535 pub fn visit_public_property(
536 PublicProperty { prop, .. }: &mut PublicProperty,
537 scope: &EvaluationScope,
538 state: &VisitorState,
539 visitor: &mut (impl Visitor + ?Sized),
540 ) {
541 visit_member_reference(prop, scope, state, visitor);
542 }
543
544 pub fn visit_function(
545 Function { name: _, ret_ty: _, args: _, code, use_count: _ }: &mut Function,
546 scope: &EvaluationScope,
547 state: &VisitorState,
548 visitor: &mut (impl Visitor + ?Sized),
549 ) {
550 visit_expression(code.get_mut(), scope, state, visitor);
551 }
552
553 pub fn visit_expression(
554 expr: &mut Expression,
555 scope: &EvaluationScope,
556 state: &VisitorState,
557 visitor: &mut (impl Visitor + ?Sized),
558 ) {
559 expr.visit_recursive_mut(&mut |expr| {
560 let p = match expr {
561 Expression::PropertyReference(p) => p,
562 Expression::CallBackCall { callback, .. } => callback,
563 Expression::FunctionCall { function, .. } => function,
564 Expression::PropertyAssignment { property, .. } => property,
565 Expression::LayoutCacheAccess { layout_cache_prop, .. } => layout_cache_prop,
566 Expression::GridRepeaterCacheAccess { layout_cache_prop, .. } => layout_cache_prop,
567 _ => return,
568 };
569 visit_member_reference(p, scope, state, visitor);
570 });
571 }
572
573 pub fn visit_binding_expression(
574 BindingExpression { expression, animation, kind: _, use_count: _ }: &mut BindingExpression,
575 scope: &EvaluationScope,
576 state: &VisitorState,
577 visitor: &mut (impl Visitor + ?Sized),
578 ) {
579 visit_expression(expression.get_mut(), scope, state, visitor);
580 match animation {
581 Some(Animation::Static(anim) | Animation::Transition(anim)) => {
582 visit_expression(anim, scope, state, visitor)
583 }
584 None => (),
585 }
586 }
587
588 pub fn visit_member_reference(
589 member: &mut MemberReference,
590 scope: &EvaluationScope,
591 state: &VisitorState,
592 visitor: &mut (impl Visitor + ?Sized),
593 ) {
594 match member {
595 MemberReference::Relative { parent_level, local_reference } => {
596 let &EvaluationScope::SubComponent(mut sc, _) = scope else { unreachable!() };
597 for _ in 0..*parent_level {
598 sc = state.parent_mapping[sc].unwrap();
599 }
600 let scope = EvaluationScope::SubComponent(sc, None);
601 visit_local_member_reference(local_reference, &scope, state, visitor);
602 }
603 MemberReference::Global { global_index, member } => {
604 let scope = EvaluationScope::Global(*global_index);
605 visit_member_index(member, &scope, state, visitor);
606 }
607 }
608 }
609
610 pub fn visit_local_member_reference(
611 local_reference: &mut LocalMemberReference,
612 scope: &EvaluationScope,
613 state: &VisitorState,
614 visitor: &mut (impl Visitor + ?Sized),
615 ) {
616 let scope = match scope {
617 EvaluationScope::SubComponent(sub_component_idx, _) => EvaluationScope::SubComponent(
618 state
619 .follow_sub_components(*sub_component_idx, &local_reference.sub_component_path),
620 None,
621 ),
622 scope => *scope,
623 };
624 visit_member_index(&mut local_reference.reference, &scope, state, visitor);
625 }
626
627 pub fn visit_member_index(
628 member: &mut LocalMemberIndex,
629 scope: &EvaluationScope,
630 state: &VisitorState,
631 visitor: &mut (impl Visitor + ?Sized),
632 ) {
633 match member {
634 LocalMemberIndex::Property(p) => {
635 visitor.visit_property_idx(p, scope, state);
636 }
637 LocalMemberIndex::Function(f) => {
638 visitor.visit_function_idx(f, scope, state);
639 }
640 LocalMemberIndex::Callback(c) => {
641 visitor.visit_callback_idx(c, scope, state);
642 }
643 LocalMemberIndex::Native { .. } | LocalMemberIndex::Timer(_) => {}
644 }
645 }
646}
647
648#[cfg(test)]
649mod tests {
650 fn lowered_property_names(source: &str) -> std::collections::HashSet<String> {
655 let mut config =
656 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
657 config.style = Some("fluent".into());
658 let mut diags = crate::diagnostics::BuildDiagnostics::default();
659 let doc_node =
660 crate::parser::parse(source.into(), Some(std::path::Path::new("t.slint")), &mut diags);
661 let (doc, diag, _) =
662 spin_on::spin_on(crate::compile_syntax_node(doc_node, diags, config.clone()));
663 assert!(!diag.has_errors(), "compile error: {:#?}", diag.to_string_vec());
664 let unit = crate::llr::lower_to_item_tree::lower_to_item_tree(&doc, &config);
665 unit.sub_components
666 .iter()
667 .flat_map(|sc| sc.properties.iter().map(|p| p.name.to_string()))
668 .collect()
669 }
670
671 const SOURCE: &str = r#"
675export component Foo inherits Window {
676 in property <int> ext: 1;
677 in property <int> ext2: 2;
678
679 // KEPT: exposed in the public API.
680 out property <int> kept_public: ext + 1;
681
682 // REMOVED: read only from one other binding, so it is inlined into it and
683 // the reader is itself unused.
684 property <int> gone_inlined: ext * 2;
685 property <int> gone_reader: gone_inlined + 3;
686
687 // REMOVED: cheap enough to inline into each of its two (unused) readers.
688 property <int> gone_shared: ext * 3;
689 property <int> gone_shared_a: gone_shared + 1;
690 property <int> gone_shared_b: gone_shared + 2;
691
692 // KEPT: binding too expensive to inline, and read from two places.
693 property <int> kept_expensive: ext * ext2 + ext2 * ext + ext * ext2;
694 out property <int> exp_a: kept_expensive + 1;
695 out property <int> exp_b: kept_expensive + 2;
696
697 // REMOVED: only read from a timer's interval, which is inlined.
698 property <duration> gone_timer: ext * 1ms;
699
700 // REMOVED: read from a function that is called exactly once. The function is
701 // inlined into its caller, and the property is then inlined and removed.
702 property <int> gone_single_call: ext * 5;
703 pure function called_once() -> int { gone_single_call }
704 out property <int> single_reader: called_once();
705
706 // REMOVED: read only from a function's body. The function itself is not
707 // inlined (it is called from two places), but the property is inlined into
708 // the body, so it becomes unused.
709 property <int> gone_multi_call: ext * 6;
710 pure function called_twice() -> int { gone_multi_call }
711 out property <int> reader_a: called_twice();
712 out property <int> reader_b: called_twice();
713
714 // KEPT: has a change callback.
715 property <int> kept_with_change_callback: ext * 7;
716 changed kept_with_change_callback => {}
717
718 // REMOVED: read only from a callback handler, which is inlined into like a
719 // binding.
720 property <int> gone_callback: ext * 8;
721 callback do_it(int);
722 do_it(v) => { debug(gone_callback + v); }
723
724 Timer { interval: gone_timer; running: true; triggered => { do_it(1); } }
725}
726"#;
727
728 #[test]
729 fn unused_properties_are_removed_and_used_ones_kept() {
730 let names = lowered_property_names(SOURCE);
731 for gone in [
732 "gone-inlined",
733 "gone-reader",
734 "gone-shared",
735 "gone-timer",
736 "gone-single-call",
737 "gone-multi-call",
738 "gone-callback",
739 ] {
740 assert!(
741 !names.iter().any(|n| n.contains(gone)),
742 "property {gone} should have been removed, got {names:?}"
743 );
744 }
745 for kept in ["kept-public", "kept-expensive", "kept-with-change-callback"] {
746 assert!(
747 names.iter().any(|n| n.contains(kept)),
748 "property {kept} should have been kept, got {names:?}"
749 );
750 }
751 }
752}