1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CloneStats {
11 pub total_clones: u64,
13 pub optimizable_clones: u64,
15 pub optimized_clones: u64,
17 pub memory_saved_bytes: u64,
19 pub performance_improvement: f64,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CloneInfo {
26 pub location: String,
28 pub type_name: String,
30 pub estimated_size: usize,
32 pub can_optimize: bool,
34 pub optimization_reason: String,
36}
37
38pub struct CloneOptimizer {
40 stats: CloneStats,
42 clone_info: Vec<CloneInfo>,
44}
45
46impl CloneOptimizer {
47 pub fn new() -> Self {
49 Self {
50 stats: CloneStats {
51 total_clones: 0,
52 optimizable_clones: 0,
53 optimized_clones: 0,
54 memory_saved_bytes: 0,
55 performance_improvement: 0.0,
56 },
57 clone_info: Vec::new(),
58 }
59 }
60
61 pub fn record_clone(&mut self, location: &str, type_name: &str, size: usize) {
63 self.stats.total_clones += 1;
64
65 let can_optimize = self.can_optimize_type(type_name);
66 if can_optimize {
67 self.stats.optimizable_clones += 1;
68 }
69
70 let optimization_reason = if can_optimize {
71 "Can be replaced with Arc sharing".to_string()
72 } else {
73 self.get_optimization_reason(type_name)
74 };
75
76 self.clone_info.push(CloneInfo {
77 location: location.to_string(),
78 type_name: type_name.to_string(),
79 estimated_size: size,
80 can_optimize,
81 optimization_reason,
82 });
83 }
84
85 fn can_optimize_type(&self, type_name: &str) -> bool {
87 matches!(type_name,
89 "AllocationInfo" |
90 "String" |
91 "Vec<_>" |
92 "HashMap<_,_>" |
93 "BTreeMap<_,_>" |
94 "ExportConfig" |
95 "AnalysisResult" |
96 _ if type_name.contains("Config") ||
97 type_name.contains("Result") ||
98 type_name.contains("Info") ||
99 type_name.contains("Data")
100 )
101 }
102
103 fn get_optimization_reason(&self, type_name: &str) -> String {
105 if type_name.contains("Mutex") || type_name.contains("RwLock") {
106 "Already uses interior mutability".to_string()
107 } else if type_name.contains("Arc") {
108 "Already uses Arc sharing".to_string()
109 } else if type_name.contains("Rc") {
110 "Uses Rc, consider Arc for thread safety".to_string()
111 } else if self.is_primitive_type(type_name) {
112 "Primitive type, clone is cheap".to_string()
113 } else {
114 "Type analysis needed".to_string()
115 }
116 }
117
118 fn is_primitive_type(&self, type_name: &str) -> bool {
120 matches!(
121 type_name,
122 "i8" | "i16"
123 | "i32"
124 | "i64"
125 | "i128"
126 | "isize"
127 | "u8"
128 | "u16"
129 | "u32"
130 | "u64"
131 | "u128"
132 | "usize"
133 | "f32"
134 | "f64"
135 | "bool"
136 | "char"
137 )
138 }
139
140 pub fn get_stats(&self) -> &CloneStats {
142 &self.stats
143 }
144
145 pub fn get_clone_info(&self) -> &[CloneInfo] {
147 &self.clone_info
148 }
149
150 pub fn mark_optimized(&mut self, location: &str) {
152 if let Some(info) = self.clone_info.iter_mut().find(|i| i.location == location) {
153 if info.can_optimize {
154 self.stats.optimized_clones += 1;
155 self.stats.memory_saved_bytes += info.estimated_size as u64;
156 info.optimization_reason = "Optimized with Arc sharing".to_string();
157 }
158 }
159
160 if self.stats.optimizable_clones > 0 {
162 self.stats.performance_improvement =
163 self.stats.optimized_clones as f64 / self.stats.optimizable_clones as f64;
164 }
165 }
166}
167
168impl Default for CloneOptimizer {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174pub fn should_use_arc(type_name: &str) -> bool {
176 type_name.contains("AllocationInfo")
177 || type_name.contains("Config")
178 || type_name.contains("Result")
179 || type_name.contains("Collection")
180 || type_name.len() > 50 }
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_clone_optimizer_new() {
189 let optimizer = CloneOptimizer::new();
190 let stats = optimizer.get_stats();
191
192 assert_eq!(stats.total_clones, 0);
193 assert_eq!(stats.optimizable_clones, 0);
194 assert_eq!(stats.optimized_clones, 0);
195 assert_eq!(stats.memory_saved_bytes, 0);
196 assert_eq!(stats.performance_improvement, 0.0);
197 assert!(optimizer.get_clone_info().is_empty());
198 }
199
200 #[test]
201 fn test_clone_optimizer_default() {
202 let optimizer = CloneOptimizer::default();
203 let stats = optimizer.get_stats();
204
205 assert_eq!(stats.total_clones, 0);
206 assert_eq!(stats.optimizable_clones, 0);
207 assert_eq!(stats.optimized_clones, 0);
208 assert_eq!(stats.memory_saved_bytes, 0);
209 assert_eq!(stats.performance_improvement, 0.0);
210 }
211
212 #[test]
213 fn test_record_optimizable_clone() {
214 let mut optimizer = CloneOptimizer::new();
215
216 optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
217
218 let stats = optimizer.get_stats();
219 assert_eq!(stats.total_clones, 1);
220 assert_eq!(stats.optimizable_clones, 1);
221 assert_eq!(stats.optimized_clones, 0);
222
223 let clone_info = optimizer.get_clone_info();
224 assert_eq!(clone_info.len(), 1);
225 assert_eq!(clone_info[0].location, "main.rs:10");
226 assert_eq!(clone_info[0].type_name, "AllocationInfo");
227 assert_eq!(clone_info[0].estimated_size, 256);
228 assert!(clone_info[0].can_optimize);
229 assert_eq!(
230 clone_info[0].optimization_reason,
231 "Can be replaced with Arc sharing"
232 );
233 }
234
235 #[test]
236 fn test_record_non_optimizable_clone() {
237 let mut optimizer = CloneOptimizer::new();
238
239 optimizer.record_clone("main.rs:20", "i32", 4);
240
241 let stats = optimizer.get_stats();
242 assert_eq!(stats.total_clones, 1);
243 assert_eq!(stats.optimizable_clones, 0);
244 assert_eq!(stats.optimized_clones, 0);
245
246 let clone_info = optimizer.get_clone_info();
247 assert_eq!(clone_info.len(), 1);
248 assert_eq!(clone_info[0].location, "main.rs:20");
249 assert_eq!(clone_info[0].type_name, "i32");
250 assert_eq!(clone_info[0].estimated_size, 4);
251 assert!(!clone_info[0].can_optimize);
252 assert_eq!(
253 clone_info[0].optimization_reason,
254 "Primitive type, clone is cheap"
255 );
256 }
257
258 #[test]
259 fn test_can_optimize_type_optimizable_types() {
260 let optimizer = CloneOptimizer::new();
261
262 assert!(optimizer.can_optimize_type("AllocationInfo"));
264 assert!(optimizer.can_optimize_type("ExportConfig"));
265 assert!(optimizer.can_optimize_type("AnalysisResult"));
266
267 assert!(optimizer.can_optimize_type("MyConfig")); assert!(optimizer.can_optimize_type("TestResult")); assert!(optimizer.can_optimize_type("UserInfo")); assert!(optimizer.can_optimize_type("ProcessData")); }
273
274 #[test]
275 fn test_can_optimize_type_non_optimizable_types() {
276 let optimizer = CloneOptimizer::new();
277
278 assert!(!optimizer.can_optimize_type("i32"));
279 assert!(!optimizer.can_optimize_type("u64"));
280 assert!(!optimizer.can_optimize_type("f64"));
281 assert!(!optimizer.can_optimize_type("bool"));
282 assert!(!optimizer.can_optimize_type("char"));
283 assert!(!optimizer.can_optimize_type("SomeOtherType"));
284 }
285
286 #[test]
287 fn test_is_primitive_type() {
288 let optimizer = CloneOptimizer::new();
289
290 assert!(optimizer.is_primitive_type("i8"));
292 assert!(optimizer.is_primitive_type("i16"));
293 assert!(optimizer.is_primitive_type("i32"));
294 assert!(optimizer.is_primitive_type("i64"));
295 assert!(optimizer.is_primitive_type("i128"));
296 assert!(optimizer.is_primitive_type("isize"));
297 assert!(optimizer.is_primitive_type("u8"));
298 assert!(optimizer.is_primitive_type("u16"));
299 assert!(optimizer.is_primitive_type("u32"));
300 assert!(optimizer.is_primitive_type("u64"));
301 assert!(optimizer.is_primitive_type("u128"));
302 assert!(optimizer.is_primitive_type("usize"));
303
304 assert!(optimizer.is_primitive_type("f32"));
306 assert!(optimizer.is_primitive_type("f64"));
307
308 assert!(optimizer.is_primitive_type("bool"));
310 assert!(optimizer.is_primitive_type("char"));
311
312 assert!(!optimizer.is_primitive_type("String"));
314 assert!(!optimizer.is_primitive_type("Vec<i32>"));
315 assert!(!optimizer.is_primitive_type("AllocationInfo"));
316 }
317
318 #[test]
319 fn test_get_optimization_reason_mutex_types() {
320 let optimizer = CloneOptimizer::new();
321
322 let reason = optimizer.get_optimization_reason("Mutex<i32>");
323 assert_eq!(reason, "Already uses interior mutability");
324
325 let reason = optimizer.get_optimization_reason("RwLock<String>");
326 assert_eq!(reason, "Already uses interior mutability");
327 }
328
329 #[test]
330 fn test_get_optimization_reason_arc_types() {
331 let optimizer = CloneOptimizer::new();
332
333 let reason = optimizer.get_optimization_reason("Arc<String>");
334 assert_eq!(reason, "Already uses Arc sharing");
335 }
336
337 #[test]
338 fn test_get_optimization_reason_rc_types() {
339 let optimizer = CloneOptimizer::new();
340
341 let reason = optimizer.get_optimization_reason("Rc<String>");
342 assert_eq!(reason, "Uses Rc, consider Arc for thread safety");
343 }
344
345 #[test]
346 fn test_get_optimization_reason_primitive_types() {
347 let optimizer = CloneOptimizer::new();
348
349 let reason = optimizer.get_optimization_reason("i32");
350 assert_eq!(reason, "Primitive type, clone is cheap");
351
352 let reason = optimizer.get_optimization_reason("bool");
353 assert_eq!(reason, "Primitive type, clone is cheap");
354 }
355
356 #[test]
357 fn test_get_optimization_reason_unknown_types() {
358 let optimizer = CloneOptimizer::new();
359
360 let reason = optimizer.get_optimization_reason("UnknownType");
361 assert_eq!(reason, "Type analysis needed");
362 }
363
364 #[test]
365 fn test_mark_optimized_valid_location() {
366 let mut optimizer = CloneOptimizer::new();
367
368 optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
370
371 optimizer.mark_optimized("main.rs:10");
373
374 let stats = optimizer.get_stats();
375 assert_eq!(stats.optimized_clones, 1);
376 assert_eq!(stats.memory_saved_bytes, 256);
377 assert_eq!(stats.performance_improvement, 1.0); let clone_info = optimizer.get_clone_info();
380 assert_eq!(
381 clone_info[0].optimization_reason,
382 "Optimized with Arc sharing"
383 );
384 }
385
386 #[test]
387 fn test_mark_optimized_invalid_location() {
388 let mut optimizer = CloneOptimizer::new();
389
390 optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
392
393 optimizer.mark_optimized("main.rs:20");
395
396 let stats = optimizer.get_stats();
397 assert_eq!(stats.optimized_clones, 0);
398 assert_eq!(stats.memory_saved_bytes, 0);
399 assert_eq!(stats.performance_improvement, 0.0);
400 }
401
402 #[test]
403 fn test_mark_optimized_non_optimizable_clone() {
404 let mut optimizer = CloneOptimizer::new();
405
406 optimizer.record_clone("main.rs:10", "i32", 4);
408
409 optimizer.mark_optimized("main.rs:10");
411
412 let stats = optimizer.get_stats();
413 assert_eq!(stats.optimized_clones, 0);
414 assert_eq!(stats.memory_saved_bytes, 0);
415 assert_eq!(stats.performance_improvement, 0.0);
416 }
417
418 #[test]
419 fn test_performance_improvement_calculation() {
420 let mut optimizer = CloneOptimizer::new();
421
422 optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
424 optimizer.record_clone("main.rs:20", "ExportConfig", 128);
425 optimizer.record_clone("main.rs:30", "Vec<_>", 512);
426 optimizer.record_clone("main.rs:40", "i32", 4); let stats = optimizer.get_stats();
429 assert_eq!(stats.total_clones, 4);
430 assert_eq!(stats.optimizable_clones, 2); assert_eq!(stats.optimized_clones, 0);
432
433 optimizer.mark_optimized("main.rs:10");
435 optimizer.mark_optimized("main.rs:20");
436
437 let stats = optimizer.get_stats();
438 assert_eq!(stats.optimized_clones, 2);
439 assert_eq!(stats.memory_saved_bytes, 384); assert!((stats.performance_improvement - 1.0).abs() < f64::EPSILON); }
442
443 #[test]
444 fn test_multiple_clone_records() {
445 let mut optimizer = CloneOptimizer::new();
446
447 optimizer.record_clone("file1.rs:10", "AllocationInfo", 256);
449 optimizer.record_clone("file2.rs:20", "i32", 4);
450 optimizer.record_clone("file3.rs:30", "ExportConfig", 64);
451 optimizer.record_clone("file4.rs:40", "Mutex<i32>", 32);
452 optimizer.record_clone("file5.rs:50", "Arc<String>", 48);
453
454 let stats = optimizer.get_stats();
455 assert_eq!(stats.total_clones, 5);
456 assert_eq!(stats.optimizable_clones, 2); let clone_info = optimizer.get_clone_info();
459 assert_eq!(clone_info.len(), 5);
460
461 assert!(clone_info[0].can_optimize); assert!(!clone_info[1].can_optimize); assert!(clone_info[2].can_optimize); assert!(!clone_info[3].can_optimize); assert!(!clone_info[4].can_optimize); }
468
469 #[test]
470 fn test_should_use_arc_function() {
471 assert!(should_use_arc("AllocationInfo"));
472 assert!(should_use_arc("MyConfig"));
473 assert!(should_use_arc("TestResult"));
474 assert!(should_use_arc("DataCollection"));
475 assert!(should_use_arc(
476 "VeryLongTypeNameThatExceedsFiftyCharactersInLengthAndMoreToMakeSureItIsLongEnough"
477 ));
478
479 assert!(!should_use_arc("i32"));
480 assert!(!should_use_arc("String"));
481 assert!(!should_use_arc("ShortType"));
482 }
483
484 #[test]
485 fn test_clone_stats_serialization() {
486 let stats = CloneStats {
487 total_clones: 10,
488 optimizable_clones: 7,
489 optimized_clones: 5,
490 memory_saved_bytes: 1024,
491 performance_improvement: 0.714,
492 };
493
494 let serialized = serde_json::to_string(&stats).expect("Failed to serialize");
496 let deserialized: CloneStats =
497 serde_json::from_str(&serialized).expect("Failed to deserialize");
498
499 assert_eq!(deserialized.total_clones, 10);
500 assert_eq!(deserialized.optimizable_clones, 7);
501 assert_eq!(deserialized.optimized_clones, 5);
502 assert_eq!(deserialized.memory_saved_bytes, 1024);
503 assert!((deserialized.performance_improvement - 0.714).abs() < f64::EPSILON);
504 }
505
506 #[test]
507 fn test_clone_info_serialization() {
508 let info = CloneInfo {
509 location: "main.rs:42".to_string(),
510 type_name: "AllocationInfo".to_string(),
511 estimated_size: 256,
512 can_optimize: true,
513 optimization_reason: "Can be replaced with Arc sharing".to_string(),
514 };
515
516 let serialized = serde_json::to_string(&info).expect("Failed to serialize");
518 let deserialized: CloneInfo =
519 serde_json::from_str(&serialized).expect("Failed to deserialize");
520
521 assert_eq!(deserialized.location, "main.rs:42");
522 assert_eq!(deserialized.type_name, "AllocationInfo");
523 assert_eq!(deserialized.estimated_size, 256);
524 assert!(deserialized.can_optimize);
525 assert_eq!(
526 deserialized.optimization_reason,
527 "Can be replaced with Arc sharing"
528 );
529 }
530
531 #[test]
532 fn test_clone_stats_debug() {
533 let stats = CloneStats {
534 total_clones: 5,
535 optimizable_clones: 3,
536 optimized_clones: 2,
537 memory_saved_bytes: 512,
538 performance_improvement: 0.667,
539 };
540
541 let debug_str = format!("{stats:?}");
542 assert!(debug_str.contains("total_clones: 5"));
543 assert!(debug_str.contains("optimizable_clones: 3"));
544 assert!(debug_str.contains("optimized_clones: 2"));
545 }
546
547 #[test]
548 fn test_clone_info_debug() {
549 let info = CloneInfo {
550 location: "test.rs:1".to_string(),
551 type_name: "TestType".to_string(),
552 estimated_size: 128,
553 can_optimize: false,
554 optimization_reason: "Test reason".to_string(),
555 };
556
557 let debug_str = format!("{info:?}");
558 assert!(debug_str.contains("location: \"test.rs:1\""));
559 assert!(debug_str.contains("type_name: \"TestType\""));
560 assert!(debug_str.contains("estimated_size: 128"));
561 }
562
563 #[test]
564 fn test_edge_case_empty_type_name() {
565 let mut optimizer = CloneOptimizer::new();
566
567 optimizer.record_clone("main.rs:1", "", 0);
568
569 let clone_info = optimizer.get_clone_info();
570 assert_eq!(clone_info.len(), 1);
571 assert_eq!(clone_info[0].type_name, "");
572 assert!(!clone_info[0].can_optimize);
573 assert_eq!(clone_info[0].optimization_reason, "Type analysis needed");
574 }
575
576 #[test]
577 fn test_edge_case_zero_size_clone() {
578 let mut optimizer = CloneOptimizer::new();
579
580 optimizer.record_clone("main.rs:1", "AllocationInfo", 0);
581 optimizer.mark_optimized("main.rs:1");
582
583 let stats = optimizer.get_stats();
584 assert_eq!(stats.memory_saved_bytes, 0);
585 assert_eq!(stats.optimized_clones, 1);
586 }
587}