1use depyler_hir::hir::{BinOp, HirExpr, HirFunction, HirProgram, HirStmt, Type};
3use colored::Colorize;
4
5pub struct PerformanceAnalyzer {
7 warnings: Vec<PerformanceWarning>,
9 config: PerformanceConfig,
11 current_loop_depth: usize,
13}
14
15#[derive(Debug, Clone)]
16pub struct PerformanceConfig {
17 pub warn_string_concat: bool,
19 pub warn_allocations: bool,
21 pub warn_algorithms: bool,
23 pub warn_repeated_computation: bool,
25 pub max_loop_depth: usize,
27 pub quadratic_threshold: usize,
29}
30
31impl Default for PerformanceConfig {
32 fn default() -> Self {
33 Self {
34 warn_string_concat: true,
35 warn_allocations: true,
36 warn_algorithms: true,
37 warn_repeated_computation: true,
38 max_loop_depth: 3,
39 quadratic_threshold: 100,
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
45pub struct PerformanceWarning {
46 pub category: WarningCategory,
48 pub severity: WarningSeverity,
50 pub message: String,
52 pub explanation: String,
54 pub suggestion: String,
56 pub impact: PerformanceImpact,
58 pub location: Option<Location>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum WarningCategory {
64 StringPerformance,
66 MemoryAllocation,
68 AlgorithmComplexity,
70 RedundantComputation,
72 IoPerformance,
74 CollectionUsage,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
79pub enum WarningSeverity {
80 Low,
82 Medium,
84 High,
86 Critical,
88}
89
90#[derive(Debug, Clone)]
91pub struct PerformanceImpact {
92 pub complexity: String,
94 pub scales_with_input: bool,
96 pub in_hot_path: bool,
98}
99
100#[derive(Debug, Clone)]
101pub struct Location {
102 pub function: String,
103 pub line: usize,
104 pub in_loop: bool,
105 pub loop_depth: usize,
106}
107
108impl PerformanceAnalyzer {
109 pub fn new(config: PerformanceConfig) -> Self {
110 Self {
111 warnings: Vec::new(),
112 config,
113 current_loop_depth: 0,
114 }
115 }
116
117 pub fn analyze_program(&mut self, program: &HirProgram) -> Vec<PerformanceWarning> {
119 self.warnings.clear();
120
121 for func in &program.functions {
122 self.analyze_function(func);
123 }
124
125 self.warnings.sort_by(|a, b| b.severity.cmp(&a.severity));
127
128 self.warnings.clone()
129 }
130
131 fn analyze_function(&mut self, func: &HirFunction) {
132 self.current_loop_depth = 0;
133
134 self.check_function_level_issues(func);
136
137 for (idx, stmt) in func.body.iter().enumerate() {
139 self.analyze_stmt(stmt, func, idx);
140 }
141 }
142
143 fn check_function_level_issues(&mut self, func: &HirFunction) {
144 for param in &func.params {
146 if self.is_large_type(¶m.ty) && !self.is_reference_type(¶m.ty) {
147 self.add_warning(PerformanceWarning {
148 category: WarningCategory::MemoryAllocation,
149 severity: WarningSeverity::Medium,
150 message: format!("Large value '{}' passed by copy", param.name),
151 explanation: "Passing large values by copy is inefficient".to_string(),
152 suggestion:
153 "Consider passing by reference (&) or using Box/Arc for large types"
154 .to_string(),
155 impact: PerformanceImpact {
156 complexity: "O(n)".to_string(),
157 scales_with_input: true,
158 in_hot_path: false,
159 },
160 location: Some(Location {
161 function: func.name.clone(),
162 line: 0,
163 in_loop: false,
164 loop_depth: 0,
165 }),
166 });
167 }
168 }
169 }
170
171 fn analyze_stmt(&mut self, stmt: &HirStmt, func: &HirFunction, line: usize) {
172 match stmt {
173 HirStmt::For {
174 target: _,
175 iter,
176 body,
177 } => {
178 self.analyze_for_loop(iter, body, func, line);
179 }
180 HirStmt::While { condition: _, body } => {
181 self.analyze_while_loop(body, func, line);
182 }
183 HirStmt::Assign {
184 target: _, value, ..
185 } => {
186 self.analyze_assignment(value, func, line);
187 }
188 HirStmt::Expr(expr) => {
189 self.analyze_expr(expr, func, line);
190 }
191 _ => {}
192 }
193 }
194
195 fn analyze_for_loop(
196 &mut self,
197 iter: &HirExpr,
198 body: &[HirStmt],
199 func: &HirFunction,
200 line: usize,
201 ) {
202 self.current_loop_depth += 1;
203
204 self.check_loop_depth_violation(func, line);
205 self.check_iteration_pattern(iter, func, line);
206
207 for inner_stmt in body {
208 self.analyze_stmt(inner_stmt, func, line);
209 }
210
211 self.current_loop_depth -= 1;
212 }
213
214 fn analyze_while_loop(&mut self, body: &[HirStmt], func: &HirFunction, line: usize) {
215 self.current_loop_depth += 1;
216
217 for inner_stmt in body {
218 self.analyze_stmt(inner_stmt, func, line);
219 }
220
221 self.current_loop_depth -= 1;
222 }
223
224 fn analyze_assignment(&mut self, value: &HirExpr, func: &HirFunction, line: usize) {
225 self.analyze_expr(value, func, line);
226
227 if self.current_loop_depth > 0 && self.is_string_concatenation(value) {
228 self.warn_string_concat_in_loop(func, line);
229 }
230 }
231
232 fn check_loop_depth_violation(&mut self, func: &HirFunction, line: usize) {
233 if self.current_loop_depth > self.config.max_loop_depth {
234 self.add_warning(PerformanceWarning {
235 category: WarningCategory::AlgorithmComplexity,
236 severity: WarningSeverity::High,
237 message: format!("Deeply nested loops (depth: {})", self.current_loop_depth),
238 explanation: "Deeply nested loops can lead to exponential time complexity"
239 .to_string(),
240 suggestion:
241 "Consider refactoring to reduce nesting or use more efficient algorithms"
242 .to_string(),
243 impact: PerformanceImpact {
244 complexity: format!("O(n^{})", self.current_loop_depth),
245 scales_with_input: true,
246 in_hot_path: true,
247 },
248 location: Some(Location {
249 function: func.name.clone(),
250 line,
251 in_loop: true,
252 loop_depth: self.current_loop_depth,
253 }),
254 });
255 }
256 }
257
258 fn warn_string_concat_in_loop(&mut self, func: &HirFunction, line: usize) {
259 self.add_warning(PerformanceWarning {
260 category: WarningCategory::StringPerformance,
261 severity: WarningSeverity::High,
262 message: "String concatenation in loop".to_string(),
263 explanation: "String concatenation in loops creates many intermediate strings"
264 .to_string(),
265 suggestion: "Use String::with_capacity() and push_str(), or collect into a String"
266 .to_string(),
267 impact: PerformanceImpact {
268 complexity: "O(n²)".to_string(),
269 scales_with_input: true,
270 in_hot_path: true,
271 },
272 location: Some(Location {
273 function: func.name.clone(),
274 line,
275 in_loop: true,
276 loop_depth: self.current_loop_depth,
277 }),
278 });
279 }
280
281 fn analyze_expr(&mut self, expr: &HirExpr, func: &HirFunction, line: usize) {
282 match expr {
283 HirExpr::Binary { left, right, op } => {
284 self.analyze_binary_expr(left, right, op, func, line);
285 }
286 HirExpr::Call {
287 func: fname, args, ..
288 } => {
289 self.analyze_function_call(fname, args, func, line);
290 }
291 HirExpr::MethodCall {
292 object,
293 method,
294 args,
295 ..
296 } => {
297 self.analyze_method_call(object, method, args, func, line);
298 }
299 HirExpr::List(items) => {
300 self.analyze_list_expr(items, func, line);
301 }
302 _ => {}
303 }
304 }
305
306 fn analyze_binary_expr(
307 &mut self,
308 left: &HirExpr,
309 right: &HirExpr,
310 op: &BinOp,
311 func: &HirFunction,
312 line: usize,
313 ) {
314 self.analyze_expr(left, func, line);
315 self.analyze_expr(right, func, line);
316
317 if matches!(op, BinOp::Pow) && self.current_loop_depth > 0 {
318 self.warn_power_in_loop(func, line);
319 }
320 }
321
322 fn analyze_function_call(
323 &mut self,
324 fname: &str,
325 args: &[HirExpr],
326 func: &HirFunction,
327 line: usize,
328 ) {
329 if self.current_loop_depth > 0 && self.is_expensive_function(fname) {
330 self.warn_expensive_function_in_loop(fname, func, line);
331 }
332
333 self.check_function_call_patterns(fname, args, func, line);
334
335 for arg in args {
336 self.analyze_expr(arg, func, line);
337 }
338 }
339
340 fn analyze_method_call(
341 &mut self,
342 object: &HirExpr,
343 method: &str,
344 args: &[HirExpr],
345 func: &HirFunction,
346 line: usize,
347 ) {
348 self.analyze_expr(object, func, line);
349 self.check_method_patterns(object, method, args, func, line);
350
351 for arg in args {
352 self.analyze_expr(arg, func, line);
353 }
354 }
355
356 fn analyze_list_expr(&mut self, items: &[HirExpr], func: &HirFunction, line: usize) {
357 if self.current_loop_depth > 0 && items.len() > 10 {
358 self.warn_large_list_in_loop(func, line);
359 }
360
361 for item in items {
362 self.analyze_expr(item, func, line);
363 }
364 }
365
366 fn warn_power_in_loop(&mut self, func: &HirFunction, line: usize) {
367 self.add_warning(PerformanceWarning {
368 category: WarningCategory::RedundantComputation,
369 severity: WarningSeverity::Medium,
370 message: "Power operation in loop".to_string(),
371 explanation: "Power operations are computationally expensive".to_string(),
372 suggestion: "Consider caching the result if the value doesn't change".to_string(),
373 impact: PerformanceImpact {
374 complexity: "O(log n) per operation".to_string(),
375 scales_with_input: false,
376 in_hot_path: true,
377 },
378 location: Some(Location {
379 function: func.name.clone(),
380 line,
381 in_loop: true,
382 loop_depth: self.current_loop_depth,
383 }),
384 });
385 }
386
387 fn warn_expensive_function_in_loop(&mut self, fname: &str, func: &HirFunction, line: usize) {
388 self.add_warning(PerformanceWarning {
389 category: WarningCategory::RedundantComputation,
390 severity: WarningSeverity::Medium,
391 message: format!("Expensive function '{}' called in loop", fname),
392 explanation: "Calling expensive functions repeatedly can impact performance"
393 .to_string(),
394 suggestion: "Cache the result if the inputs don't change".to_string(),
395 impact: PerformanceImpact {
396 complexity: "Depends on function".to_string(),
397 scales_with_input: true,
398 in_hot_path: true,
399 },
400 location: Some(Location {
401 function: func.name.clone(),
402 line,
403 in_loop: true,
404 loop_depth: self.current_loop_depth,
405 }),
406 });
407 }
408
409 fn warn_large_list_in_loop(&mut self, func: &HirFunction, line: usize) {
410 self.add_warning(PerformanceWarning {
411 category: WarningCategory::MemoryAllocation,
412 severity: WarningSeverity::Medium,
413 message: "Large list created in loop".to_string(),
414 explanation: "Creating large collections in loops causes repeated allocations"
415 .to_string(),
416 suggestion: "Move the list creation outside the loop or use a pre-allocated buffer"
417 .to_string(),
418 impact: PerformanceImpact {
419 complexity: "O(n) allocations".to_string(),
420 scales_with_input: true,
421 in_hot_path: true,
422 },
423 location: Some(Location {
424 function: func.name.clone(),
425 line,
426 in_loop: true,
427 loop_depth: self.current_loop_depth,
428 }),
429 });
430 }
431
432 fn check_iteration_pattern(&mut self, iter: &HirExpr, func: &HirFunction, line: usize) {
433 if let HirExpr::Call {
435 func: fname, args, ..
436 } = iter
437 {
438 if fname == "range" && !args.is_empty() {
439 if let HirExpr::Call {
440 func: inner_func, ..
441 } = &args[0]
442 {
443 if inner_func == "len" {
444 self.add_warning(PerformanceWarning {
445 category: WarningCategory::CollectionUsage,
446 severity: WarningSeverity::Low,
447 message: "Using range(len(x)) instead of enumerate".to_string(),
448 explanation: "This pattern is less efficient and less idiomatic"
449 .to_string(),
450 suggestion: "Use enumerate() to get both index and value".to_string(),
451 impact: PerformanceImpact {
452 complexity: "O(1) overhead".to_string(),
453 scales_with_input: false,
454 in_hot_path: true,
455 },
456 location: Some(Location {
457 function: func.name.clone(),
458 line,
459 in_loop: false,
460 loop_depth: self.current_loop_depth,
461 }),
462 });
463 }
464 }
465 }
466 }
467 }
468
469 fn check_function_call_patterns(
470 &mut self,
471 fname: &str,
472 args: &[HirExpr],
473 func: &HirFunction,
474 line: usize,
475 ) {
476 if fname == "sorted" && self.current_loop_depth > 0 {
478 self.add_warning(PerformanceWarning {
479 category: WarningCategory::AlgorithmComplexity,
480 severity: WarningSeverity::High,
481 message: "Sorting inside a loop".to_string(),
482 explanation: "Sorting has O(n log n) complexity and shouldn't be repeated"
483 .to_string(),
484 suggestion: "Sort once before the loop or maintain sorted order".to_string(),
485 impact: PerformanceImpact {
486 complexity: "O(n² log n)".to_string(),
487 scales_with_input: true,
488 in_hot_path: true,
489 },
490 location: Some(Location {
491 function: func.name.clone(),
492 line,
493 in_loop: true,
494 loop_depth: self.current_loop_depth,
495 }),
496 });
497 }
498
499 if ["sum", "min", "max"].contains(&fname) && !args.is_empty() {
501 if self.current_loop_depth > 1 {
504 self.add_warning(PerformanceWarning {
505 category: WarningCategory::RedundantComputation,
506 severity: WarningSeverity::Medium,
507 message: format!("Aggregate function '{}' in nested loop", fname),
508 explanation: "Computing aggregates repeatedly is inefficient".to_string(),
509 suggestion: "Compute once and cache the result".to_string(),
510 impact: PerformanceImpact {
511 complexity: "O(n) per call".to_string(),
512 scales_with_input: true,
513 in_hot_path: true,
514 },
515 location: Some(Location {
516 function: func.name.clone(),
517 line,
518 in_loop: true,
519 loop_depth: self.current_loop_depth,
520 }),
521 });
522 }
523 }
524 }
525
526 fn check_method_patterns(
527 &mut self,
528 _object: &HirExpr,
529 method: &str,
530 _args: &[HirExpr],
531 func: &HirFunction,
532 line: usize,
533 ) {
534 if self.current_loop_depth == 0 {
535 return;
536 }
537
538 match method {
539 "append" => self.warn_append_in_loop(func, line),
540 "remove" if self.current_loop_depth > 1 => self.warn_remove_in_nested_loop(func, line),
541 "index" | "count" => self.warn_linear_search_in_loop(method, func, line),
542 _ => {}
543 }
544 }
545
546 fn warn_append_in_loop(&mut self, func: &HirFunction, line: usize) {
547 self.add_warning(PerformanceWarning {
548 category: WarningCategory::CollectionUsage,
549 severity: WarningSeverity::Low,
550 message: "Multiple append calls in loop".to_string(),
551 explanation: "Multiple append operations can be less efficient than extend".to_string(),
552 suggestion: "Consider collecting items and using extend() once".to_string(),
553 impact: PerformanceImpact {
554 complexity: "O(1) amortized, but more calls".to_string(),
555 scales_with_input: true,
556 in_hot_path: true,
557 },
558 location: Some(Location {
559 function: func.name.clone(),
560 line,
561 in_loop: true,
562 loop_depth: self.current_loop_depth,
563 }),
564 });
565 }
566
567 fn warn_remove_in_nested_loop(&mut self, func: &HirFunction, line: usize) {
568 self.add_warning(PerformanceWarning {
569 category: WarningCategory::AlgorithmComplexity,
570 severity: WarningSeverity::Critical,
571 message: "List remove() in nested loop".to_string(),
572 explanation: "remove() is O(n) and in nested loops becomes O(n²) or worse".to_string(),
573 suggestion: "Use a set for O(1) removal or filter to create a new list".to_string(),
574 impact: PerformanceImpact {
575 complexity: format!("O(n^{})", self.current_loop_depth + 1),
576 scales_with_input: true,
577 in_hot_path: true,
578 },
579 location: Some(Location {
580 function: func.name.clone(),
581 line,
582 in_loop: true,
583 loop_depth: self.current_loop_depth,
584 }),
585 });
586 }
587
588 fn warn_linear_search_in_loop(&mut self, method: &str, func: &HirFunction, line: usize) {
589 self.add_warning(PerformanceWarning {
590 category: WarningCategory::AlgorithmComplexity,
591 severity: WarningSeverity::Medium,
592 message: format!("Linear search method '{}' in loop", method),
593 explanation: "Linear search in loops can lead to quadratic complexity".to_string(),
594 suggestion: "Consider using a HashMap/HashSet for O(1) lookups".to_string(),
595 impact: PerformanceImpact {
596 complexity: "O(n²)".to_string(),
597 scales_with_input: true,
598 in_hot_path: true,
599 },
600 location: Some(Location {
601 function: func.name.clone(),
602 line,
603 in_loop: true,
604 loop_depth: self.current_loop_depth,
605 }),
606 });
607 }
608
609 fn is_large_type(&self, ty: &Type) -> bool {
612 match ty {
613 Type::List(_) | Type::Dict(_, _) | Type::String => true,
614 Type::Custom(name) => {
615 !["i32", "i64", "f32", "f64", "bool", "char"].contains(&name.as_str())
617 }
618 _ => false,
619 }
620 }
621
622 fn is_reference_type(&self, _ty: &Type) -> bool {
623 false
626 }
627
628 fn is_string_concatenation(&self, expr: &HirExpr) -> bool {
629 if let HirExpr::Binary {
630 op: BinOp::Add,
631 left,
632 right,
633 } = expr
634 {
635 matches!(left.as_ref(), HirExpr::Var(_)) || matches!(right.as_ref(), HirExpr::Var(_))
637 } else {
638 false
639 }
640 }
641
642 fn is_expensive_function(&self, fname: &str) -> bool {
643 let expensive = [
645 "sorted", "sort", "reverse", "compile", "eval", "exec", "deepcopy", "copy", "hash",
646 "checksum",
647 ];
648 expensive.contains(&fname)
649 }
650
651 fn add_warning(&mut self, warning: PerformanceWarning) {
652 self.warnings.push(warning);
653 }
654
655 pub fn format_warnings(&self, warnings: &[PerformanceWarning]) -> String {
657 if warnings.is_empty() {
658 return "✅ No performance warnings found!\n".green().to_string();
659 }
660
661 let mut output = String::new();
662 self.append_header(&mut output);
663 self.append_warning_details(&mut output, warnings);
664 self.append_summary(&mut output, warnings);
665 output
666 }
667
668 fn append_header(&self, output: &mut String) {
669 output.push_str(&format!("\n{}\n", "Performance Warnings".bold().yellow()));
670 output.push_str(&format!("{}\n\n", "═".repeat(50)));
671 }
672
673 fn append_warning_details(&self, output: &mut String, warnings: &[PerformanceWarning]) {
674 for (idx, warning) in warnings.iter().enumerate() {
675 self.append_single_warning(output, idx, warning);
676 }
677 }
678
679 fn append_single_warning(&self, output: &mut String, idx: usize, warning: &PerformanceWarning) {
680 self.append_warning_header(output, idx, warning);
681 self.append_warning_location(output, warning);
682 self.append_warning_impact(output, warning);
683 self.append_warning_explanation(output, warning);
684 self.append_warning_suggestion(output, warning);
685 output.push('\n');
686 }
687
688 fn append_warning_header(&self, output: &mut String, idx: usize, warning: &PerformanceWarning) {
689 let severity_color = self.get_severity_color(warning.severity);
690 output.push_str(&format!(
691 "{} {} {}\n",
692 format!("[{}]", idx + 1).dimmed(),
693 format!("[{:?}]", warning.severity)
694 .color(severity_color)
695 .bold(),
696 warning.message.bold()
697 ));
698 }
699
700 fn append_warning_location(&self, output: &mut String, warning: &PerformanceWarning) {
701 if let Some(loc) = &warning.location {
702 let loop_info = self.format_loop_info(loc);
703 output.push_str(&format!(
704 " {} {}, line {}{}\n",
705 "Location:".dimmed(),
706 loc.function,
707 loc.line,
708 loop_info
709 ));
710 }
711 }
712
713 fn append_warning_impact(&self, output: &mut String, warning: &PerformanceWarning) {
714 output.push_str(&format!(
715 " {} Complexity: {}, Scales: {}, Hot path: {}\n",
716 "Impact:".dimmed(),
717 warning.impact.complexity.yellow(),
718 self.format_yes_no(warning.impact.scales_with_input),
719 self.format_yes_no(warning.impact.in_hot_path)
720 ));
721 }
722
723 fn append_warning_explanation(&self, output: &mut String, warning: &PerformanceWarning) {
724 output.push_str(&format!(" {} {}\n", "Why:".dimmed(), warning.explanation));
725 }
726
727 fn append_warning_suggestion(&self, output: &mut String, warning: &PerformanceWarning) {
728 output.push_str(&format!(
729 " {} {}\n",
730 "Fix:".green(),
731 warning.suggestion.green()
732 ));
733 }
734
735 fn append_summary(&self, output: &mut String, warnings: &[PerformanceWarning]) {
736 let (critical, high) = self.count_severity_levels(warnings);
737
738 output.push_str(&format!(
739 "{} Found {} warnings ({} critical, {} high severity)\n",
740 "Summary:".bold(),
741 warnings.len(),
742 critical,
743 high
744 ));
745
746 if critical > 0 || high > 0 {
747 self.append_critical_warning_notice(output);
748 }
749 }
750
751 fn get_severity_color(&self, severity: WarningSeverity) -> &'static str {
752 match severity {
753 WarningSeverity::Critical => "red",
754 WarningSeverity::High => "bright red",
755 WarningSeverity::Medium => "yellow",
756 WarningSeverity::Low => "bright yellow",
757 }
758 }
759
760 fn format_loop_info(&self, loc: &Location) -> String {
761 if loc.in_loop {
762 format!(" (in loop, depth: {})", loc.loop_depth)
763 .red()
764 .to_string()
765 } else {
766 String::new()
767 }
768 }
769
770 fn format_yes_no(&self, value: bool) -> colored::ColoredString {
771 if value {
772 "Yes".red()
773 } else {
774 "No".green()
775 }
776 }
777
778 fn count_severity_levels(&self, warnings: &[PerformanceWarning]) -> (usize, usize) {
779 let critical = warnings
780 .iter()
781 .filter(|w| w.severity == WarningSeverity::Critical)
782 .count();
783 let high = warnings
784 .iter()
785 .filter(|w| w.severity == WarningSeverity::High)
786 .count();
787 (critical, high)
788 }
789
790 fn append_critical_warning_notice(&self, output: &mut String) {
791 output.push_str(
792 &"⚠️ Address critical and high severity warnings for better performance\n"
793 .red()
794 .to_string(),
795 );
796 }
797}
798
799#[cfg(test)]
800mod tests {
801 use super::*;
802 use depyler_hir::hir::*;
803 use smallvec::smallvec;
804
805 fn create_test_function(name: &str, body: Vec<HirStmt>) -> HirFunction {
806 HirFunction {
807 name: name.to_string(),
808 params: smallvec![],
809 ret_type: Type::Unknown,
810 body,
811 properties: FunctionProperties::default(),
812 annotations: Default::default(),
813 docstring: None,
814 }
815 }
816
817 #[test]
820 fn test_performance_config_default() {
821 let config = PerformanceConfig::default();
822 assert!(config.warn_string_concat);
823 assert!(config.warn_allocations);
824 assert!(config.warn_algorithms);
825 assert!(config.warn_repeated_computation);
826 assert_eq!(config.max_loop_depth, 3);
827 assert_eq!(config.quadratic_threshold, 100);
828 }
829
830 #[test]
831 fn test_performance_config_clone() {
832 let config = PerformanceConfig {
833 warn_string_concat: false,
834 warn_allocations: false,
835 max_loop_depth: 5,
836 ..Default::default()
837 };
838 let cloned = config.clone();
839 assert!(!cloned.warn_string_concat);
840 assert_eq!(cloned.max_loop_depth, 5);
841 }
842
843 #[test]
844 fn test_performance_config_debug() {
845 let config = PerformanceConfig::default();
846 let debug = format!("{:?}", config);
847 assert!(debug.contains("PerformanceConfig"));
848 assert!(debug.contains("warn_string_concat"));
849 }
850
851 #[test]
854 fn test_warning_category_variants() {
855 let categories = [
856 WarningCategory::StringPerformance,
857 WarningCategory::MemoryAllocation,
858 WarningCategory::AlgorithmComplexity,
859 WarningCategory::RedundantComputation,
860 WarningCategory::IoPerformance,
861 WarningCategory::CollectionUsage,
862 ];
863 assert_eq!(categories.len(), 6);
864 }
865
866 #[test]
867 fn test_warning_category_eq() {
868 assert_eq!(
869 WarningCategory::StringPerformance,
870 WarningCategory::StringPerformance
871 );
872 assert_ne!(
873 WarningCategory::StringPerformance,
874 WarningCategory::MemoryAllocation
875 );
876 }
877
878 #[test]
879 fn test_warning_category_clone() {
880 let cat = WarningCategory::AlgorithmComplexity;
881 let cloned = cat.clone();
882 assert_eq!(cat, cloned);
883 }
884
885 #[test]
886 fn test_warning_category_debug() {
887 let debug = format!("{:?}", WarningCategory::IoPerformance);
888 assert!(debug.contains("IoPerformance"));
889 }
890
891 #[test]
894 fn test_severity_ordering() {
895 assert!(WarningSeverity::Critical > WarningSeverity::High);
896 assert!(WarningSeverity::High > WarningSeverity::Medium);
897 assert!(WarningSeverity::Medium > WarningSeverity::Low);
898 }
899
900 #[test]
901 fn test_severity_eq() {
902 assert_eq!(WarningSeverity::Low, WarningSeverity::Low);
903 assert_ne!(WarningSeverity::Low, WarningSeverity::High);
904 }
905
906 #[test]
907 fn test_severity_copy() {
908 let s1 = WarningSeverity::Critical;
909 let s2 = s1;
910 assert_eq!(s1, s2);
911 }
912
913 #[test]
914 fn test_severity_clone() {
915 let s = WarningSeverity::Medium;
916 let cloned = s;
917 assert_eq!(s, cloned);
918 }
919
920 #[test]
921 fn test_severity_debug() {
922 let debug = format!("{:?}", WarningSeverity::High);
923 assert!(debug.contains("High"));
924 }
925
926 #[test]
927 fn test_severity_ord_all() {
928 let mut severities = [
929 WarningSeverity::Medium,
930 WarningSeverity::Critical,
931 WarningSeverity::Low,
932 WarningSeverity::High,
933 ];
934 severities.sort();
935 assert_eq!(severities[0], WarningSeverity::Low);
936 assert_eq!(severities[3], WarningSeverity::Critical);
937 }
938
939 #[test]
942 fn test_performance_impact_new() {
943 let impact = PerformanceImpact {
944 complexity: "O(n²)".to_string(),
945 scales_with_input: true,
946 in_hot_path: false,
947 };
948 assert_eq!(impact.complexity, "O(n²)");
949 assert!(impact.scales_with_input);
950 assert!(!impact.in_hot_path);
951 }
952
953 #[test]
954 fn test_performance_impact_clone() {
955 let impact = PerformanceImpact {
956 complexity: "O(1)".to_string(),
957 scales_with_input: false,
958 in_hot_path: true,
959 };
960 let cloned = impact.clone();
961 assert_eq!(cloned.complexity, "O(1)");
962 }
963
964 #[test]
965 fn test_performance_impact_debug() {
966 let impact = PerformanceImpact {
967 complexity: "O(n)".to_string(),
968 scales_with_input: true,
969 in_hot_path: true,
970 };
971 let debug = format!("{:?}", impact);
972 assert!(debug.contains("PerformanceImpact"));
973 }
974
975 #[test]
978 fn test_location_new() {
979 let loc = Location {
980 function: "my_func".to_string(),
981 line: 42,
982 in_loop: true,
983 loop_depth: 2,
984 };
985 assert_eq!(loc.function, "my_func");
986 assert_eq!(loc.line, 42);
987 assert!(loc.in_loop);
988 assert_eq!(loc.loop_depth, 2);
989 }
990
991 #[test]
992 fn test_location_clone() {
993 let loc = Location {
994 function: "test".to_string(),
995 line: 1,
996 in_loop: false,
997 loop_depth: 0,
998 };
999 let cloned = loc.clone();
1000 assert_eq!(loc.function, cloned.function);
1001 }
1002
1003 #[test]
1004 fn test_location_debug() {
1005 let loc = Location {
1006 function: "f".to_string(),
1007 line: 10,
1008 in_loop: true,
1009 loop_depth: 1,
1010 };
1011 let debug = format!("{:?}", loc);
1012 assert!(debug.contains("Location"));
1013 }
1014
1015 #[test]
1018 fn test_performance_warning_new() {
1019 let warning = PerformanceWarning {
1020 category: WarningCategory::StringPerformance,
1021 severity: WarningSeverity::High,
1022 message: "test message".to_string(),
1023 explanation: "test explanation".to_string(),
1024 suggestion: "test suggestion".to_string(),
1025 impact: PerformanceImpact {
1026 complexity: "O(n)".to_string(),
1027 scales_with_input: true,
1028 in_hot_path: true,
1029 },
1030 location: Some(Location {
1031 function: "test".to_string(),
1032 line: 1,
1033 in_loop: true,
1034 loop_depth: 1,
1035 }),
1036 };
1037 assert_eq!(warning.category, WarningCategory::StringPerformance);
1038 assert_eq!(warning.severity, WarningSeverity::High);
1039 }
1040
1041 #[test]
1042 fn test_performance_warning_no_location() {
1043 let warning = PerformanceWarning {
1044 category: WarningCategory::MemoryAllocation,
1045 severity: WarningSeverity::Low,
1046 message: "msg".to_string(),
1047 explanation: "exp".to_string(),
1048 suggestion: "sug".to_string(),
1049 impact: PerformanceImpact {
1050 complexity: "O(1)".to_string(),
1051 scales_with_input: false,
1052 in_hot_path: false,
1053 },
1054 location: None,
1055 };
1056 assert!(warning.location.is_none());
1057 }
1058
1059 #[test]
1060 fn test_performance_warning_clone() {
1061 let warning = PerformanceWarning {
1062 category: WarningCategory::CollectionUsage,
1063 severity: WarningSeverity::Medium,
1064 message: "test".to_string(),
1065 explanation: "exp".to_string(),
1066 suggestion: "sug".to_string(),
1067 impact: PerformanceImpact {
1068 complexity: "O(n)".to_string(),
1069 scales_with_input: true,
1070 in_hot_path: false,
1071 },
1072 location: None,
1073 };
1074 let cloned = warning.clone();
1075 assert_eq!(cloned.category, WarningCategory::CollectionUsage);
1076 }
1077
1078 #[test]
1079 fn test_performance_warning_debug() {
1080 let warning = PerformanceWarning {
1081 category: WarningCategory::RedundantComputation,
1082 severity: WarningSeverity::Critical,
1083 message: "debug test".to_string(),
1084 explanation: "".to_string(),
1085 suggestion: "".to_string(),
1086 impact: PerformanceImpact {
1087 complexity: "O(1)".to_string(),
1088 scales_with_input: false,
1089 in_hot_path: false,
1090 },
1091 location: None,
1092 };
1093 let debug = format!("{:?}", warning);
1094 assert!(debug.contains("PerformanceWarning"));
1095 }
1096
1097 #[test]
1100 fn test_analyzer_new() {
1101 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1102 assert_eq!(analyzer.current_loop_depth, 0);
1103 }
1104
1105 #[test]
1106 fn test_analyzer_format_warnings_empty() {
1107 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1108 let output = analyzer.format_warnings(&[]);
1109 assert!(output.contains("No performance warnings"));
1110 }
1111
1112 #[test]
1113 fn test_analyzer_format_warnings_with_warnings() {
1114 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1115 let warnings = vec![PerformanceWarning {
1116 category: WarningCategory::StringPerformance,
1117 severity: WarningSeverity::High,
1118 message: "Test warning".to_string(),
1119 explanation: "Test explanation".to_string(),
1120 suggestion: "Test suggestion".to_string(),
1121 impact: PerformanceImpact {
1122 complexity: "O(n²)".to_string(),
1123 scales_with_input: true,
1124 in_hot_path: true,
1125 },
1126 location: Some(Location {
1127 function: "test_func".to_string(),
1128 line: 10,
1129 in_loop: true,
1130 loop_depth: 1,
1131 }),
1132 }];
1133 let output = analyzer.format_warnings(&warnings);
1134 assert!(output.contains("Performance Warnings"));
1135 assert!(output.contains("Test warning"));
1136 assert!(output.contains("test_func"));
1137 }
1138
1139 #[test]
1140 fn test_analyzer_empty_program() {
1141 let program = HirProgram {
1142 functions: vec![],
1143 classes: vec![],
1144 imports: vec![],
1145 };
1146 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1147 let warnings = analyzer.analyze_program(&program);
1148 assert!(warnings.is_empty());
1149 }
1150
1151 #[test]
1152 fn test_analyzer_simple_function() {
1153 let func = create_test_function("simple", vec![]);
1154 let program = HirProgram {
1155 functions: vec![func],
1156 classes: vec![],
1157 imports: vec![],
1158 };
1159 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1160 let warnings = analyzer.analyze_program(&program);
1161 assert!(warnings.is_empty());
1162 }
1163
1164 #[test]
1167 fn test_string_concat_in_loop_detection() {
1168 let body = vec![HirStmt::For {
1169 target: AssignTarget::Symbol("i".to_string()),
1170 iter: HirExpr::Call {
1171 func: "range".to_string(),
1172 args: vec![HirExpr::Literal(Literal::Int(10))],
1173 kwargs: vec![],
1174 },
1175 body: vec![HirStmt::Assign {
1176 target: AssignTarget::Symbol("s".to_string()),
1177 value: HirExpr::Binary {
1178 op: BinOp::Add,
1179 left: Box::new(HirExpr::Var("s".to_string())),
1180 right: Box::new(HirExpr::Var("i".to_string())),
1181 },
1182 type_annotation: None,
1183 }],
1184 }];
1185
1186 let func = create_test_function("test", body);
1187 let program = HirProgram {
1188 functions: vec![func],
1189 classes: vec![],
1190 imports: vec![],
1191 };
1192
1193 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1194 let warnings = analyzer.analyze_program(&program);
1195
1196 assert!(!warnings.is_empty());
1197 assert!(warnings
1198 .iter()
1199 .any(|w| w.category == WarningCategory::StringPerformance));
1200 }
1201
1202 #[test]
1203 fn test_nested_loop_detection() {
1204 let inner_loop = HirStmt::For {
1205 target: AssignTarget::Symbol("j".to_string()),
1206 iter: HirExpr::Var("items".to_string()),
1207 body: vec![HirStmt::Expr(HirExpr::Var("j".to_string()))],
1208 };
1209
1210 let body = vec![HirStmt::For {
1211 target: AssignTarget::Symbol("i".to_string()),
1212 iter: HirExpr::Var("items".to_string()),
1213 body: vec![inner_loop],
1214 }];
1215
1216 let func = create_test_function("test", body);
1217 let program = HirProgram {
1218 functions: vec![func],
1219 classes: vec![],
1220 imports: vec![],
1221 };
1222
1223 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig {
1224 max_loop_depth: 2,
1225 ..Default::default()
1226 });
1227 let warnings = analyzer.analyze_program(&program);
1228
1229 assert!(warnings.is_empty()); }
1231
1232 #[test]
1233 fn test_deeply_nested_loop_warning() {
1234 let innermost = HirStmt::Expr(HirExpr::Var("x".to_string()));
1236 let level3 = HirStmt::For {
1237 target: AssignTarget::Symbol("d".to_string()),
1238 iter: HirExpr::Var("items".to_string()),
1239 body: vec![innermost],
1240 };
1241 let level2 = HirStmt::For {
1242 target: AssignTarget::Symbol("c".to_string()),
1243 iter: HirExpr::Var("items".to_string()),
1244 body: vec![level3],
1245 };
1246 let level1 = HirStmt::For {
1247 target: AssignTarget::Symbol("b".to_string()),
1248 iter: HirExpr::Var("items".to_string()),
1249 body: vec![level2],
1250 };
1251 let body = vec![HirStmt::For {
1252 target: AssignTarget::Symbol("a".to_string()),
1253 iter: HirExpr::Var("items".to_string()),
1254 body: vec![level1],
1255 }];
1256
1257 let func = create_test_function("test", body);
1258 let program = HirProgram {
1259 functions: vec![func],
1260 classes: vec![],
1261 imports: vec![],
1262 };
1263
1264 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1265 let warnings = analyzer.analyze_program(&program);
1266
1267 assert!(warnings
1268 .iter()
1269 .any(|w| w.category == WarningCategory::AlgorithmComplexity
1270 && w.message.contains("Deeply nested")));
1271 }
1272
1273 #[test]
1274 fn test_expensive_function_in_loop() {
1275 let body = vec![HirStmt::For {
1276 target: AssignTarget::Symbol("item".to_string()),
1277 iter: HirExpr::Var("items".to_string()),
1278 body: vec![HirStmt::Assign {
1279 target: AssignTarget::Symbol("s".to_string()),
1280 value: HirExpr::Call {
1281 func: "sorted".to_string(),
1282 args: vec![HirExpr::Var("data".to_string())],
1283 kwargs: vec![],
1284 },
1285 type_annotation: None,
1286 }],
1287 }];
1288
1289 let func = create_test_function("test", body);
1290 let program = HirProgram {
1291 functions: vec![func],
1292 classes: vec![],
1293 imports: vec![],
1294 };
1295
1296 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1297 let warnings = analyzer.analyze_program(&program);
1298
1299 assert!(!warnings.is_empty());
1300 assert!(warnings.iter().any(|w| {
1301 w.category == WarningCategory::AlgorithmComplexity
1302 || w.category == WarningCategory::RedundantComputation
1303 }));
1304 }
1305
1306 #[test]
1307 fn test_while_loop_analysis() {
1308 let body = vec![HirStmt::While {
1309 condition: HirExpr::Literal(Literal::Bool(true)),
1310 body: vec![HirStmt::Expr(HirExpr::Var("x".to_string()))],
1311 }];
1312
1313 let func = create_test_function("test", body);
1314 let program = HirProgram {
1315 functions: vec![func],
1316 classes: vec![],
1317 imports: vec![],
1318 };
1319
1320 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1321 let warnings = analyzer.analyze_program(&program);
1322 assert!(warnings.is_empty());
1324 }
1325
1326 #[test]
1327 fn test_range_len_antipattern() {
1328 let body = vec![HirStmt::For {
1329 target: AssignTarget::Symbol("i".to_string()),
1330 iter: HirExpr::Call {
1331 func: "range".to_string(),
1332 args: vec![HirExpr::Call {
1333 func: "len".to_string(),
1334 args: vec![HirExpr::Var("items".to_string())],
1335 kwargs: vec![],
1336 }],
1337 kwargs: vec![],
1338 },
1339 body: vec![HirStmt::Expr(HirExpr::Var("i".to_string()))],
1340 }];
1341
1342 let func = create_test_function("test", body);
1343 let program = HirProgram {
1344 functions: vec![func],
1345 classes: vec![],
1346 imports: vec![],
1347 };
1348
1349 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1350 let warnings = analyzer.analyze_program(&program);
1351
1352 assert!(warnings.iter().any(|w| w.message.contains("range(len")));
1353 }
1354
1355 #[test]
1356 fn test_large_list_in_loop() {
1357 let large_list = HirExpr::List(vec![HirExpr::Literal(Literal::Int(1)); 15]);
1358 let body = vec![HirStmt::For {
1359 target: AssignTarget::Symbol("i".to_string()),
1360 iter: HirExpr::Var("items".to_string()),
1361 body: vec![HirStmt::Expr(large_list)],
1362 }];
1363
1364 let func = create_test_function("test", body);
1365 let program = HirProgram {
1366 functions: vec![func],
1367 classes: vec![],
1368 imports: vec![],
1369 };
1370
1371 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1372 let warnings = analyzer.analyze_program(&program);
1373
1374 assert!(warnings
1375 .iter()
1376 .any(|w| w.category == WarningCategory::MemoryAllocation));
1377 }
1378
1379 #[test]
1380 fn test_append_in_loop() {
1381 let body = vec![HirStmt::For {
1382 target: AssignTarget::Symbol("i".to_string()),
1383 iter: HirExpr::Var("items".to_string()),
1384 body: vec![HirStmt::Expr(HirExpr::MethodCall {
1385 object: Box::new(HirExpr::Var("result".to_string())),
1386 method: "append".to_string(),
1387 args: vec![HirExpr::Var("i".to_string())],
1388 kwargs: vec![],
1389 })],
1390 }];
1391
1392 let func = create_test_function("test", body);
1393 let program = HirProgram {
1394 functions: vec![func],
1395 classes: vec![],
1396 imports: vec![],
1397 };
1398
1399 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1400 let warnings = analyzer.analyze_program(&program);
1401
1402 assert!(warnings
1403 .iter()
1404 .any(|w| w.category == WarningCategory::CollectionUsage));
1405 }
1406
1407 #[test]
1408 fn test_linear_search_in_loop() {
1409 let body = vec![HirStmt::For {
1410 target: AssignTarget::Symbol("i".to_string()),
1411 iter: HirExpr::Var("items".to_string()),
1412 body: vec![HirStmt::Expr(HirExpr::MethodCall {
1413 object: Box::new(HirExpr::Var("data".to_string())),
1414 method: "index".to_string(),
1415 args: vec![HirExpr::Var("i".to_string())],
1416 kwargs: vec![],
1417 })],
1418 }];
1419
1420 let func = create_test_function("test", body);
1421 let program = HirProgram {
1422 functions: vec![func],
1423 classes: vec![],
1424 imports: vec![],
1425 };
1426
1427 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1428 let warnings = analyzer.analyze_program(&program);
1429
1430 assert!(warnings.iter().any(|w| w.message.contains("Linear search")));
1431 }
1432
1433 #[test]
1434 fn test_power_in_loop() {
1435 let body = vec![HirStmt::For {
1436 target: AssignTarget::Symbol("i".to_string()),
1437 iter: HirExpr::Var("items".to_string()),
1438 body: vec![HirStmt::Assign {
1439 target: AssignTarget::Symbol("result".to_string()),
1440 value: HirExpr::Binary {
1441 op: BinOp::Pow,
1442 left: Box::new(HirExpr::Var("x".to_string())),
1443 right: Box::new(HirExpr::Var("i".to_string())),
1444 },
1445 type_annotation: None,
1446 }],
1447 }];
1448
1449 let func = create_test_function("test", body);
1450 let program = HirProgram {
1451 functions: vec![func],
1452 classes: vec![],
1453 imports: vec![],
1454 };
1455
1456 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1457 let warnings = analyzer.analyze_program(&program);
1458
1459 assert!(warnings
1460 .iter()
1461 .any(|w| w.message.contains("Power operation")));
1462 }
1463
1464 #[test]
1467 fn test_is_large_type_list() {
1468 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1469 assert!(analyzer.is_large_type(&Type::List(Box::new(Type::Int))));
1470 }
1471
1472 #[test]
1473 fn test_is_large_type_dict() {
1474 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1475 assert!(analyzer.is_large_type(&Type::Dict(Box::new(Type::String), Box::new(Type::Int))));
1476 }
1477
1478 #[test]
1479 fn test_is_large_type_string() {
1480 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1481 assert!(analyzer.is_large_type(&Type::String));
1482 }
1483
1484 #[test]
1485 fn test_is_large_type_int() {
1486 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1487 assert!(!analyzer.is_large_type(&Type::Int));
1488 }
1489
1490 #[test]
1491 fn test_is_large_type_custom() {
1492 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1493 assert!(analyzer.is_large_type(&Type::Custom("MyStruct".to_string())));
1494 assert!(!analyzer.is_large_type(&Type::Custom("i32".to_string())));
1495 }
1496
1497 #[test]
1498 fn test_is_expensive_function() {
1499 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1500 assert!(analyzer.is_expensive_function("sorted"));
1501 assert!(analyzer.is_expensive_function("deepcopy"));
1502 assert!(analyzer.is_expensive_function("eval"));
1503 assert!(!analyzer.is_expensive_function("len"));
1504 assert!(!analyzer.is_expensive_function("print"));
1505 }
1506
1507 #[test]
1508 fn test_is_string_concatenation() {
1509 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1510
1511 let concat = HirExpr::Binary {
1512 op: BinOp::Add,
1513 left: Box::new(HirExpr::Var("a".to_string())),
1514 right: Box::new(HirExpr::Var("b".to_string())),
1515 };
1516 assert!(analyzer.is_string_concatenation(&concat));
1517
1518 let mult = HirExpr::Binary {
1519 op: BinOp::Mul,
1520 left: Box::new(HirExpr::Var("a".to_string())),
1521 right: Box::new(HirExpr::Var("b".to_string())),
1522 };
1523 assert!(!analyzer.is_string_concatenation(&mult));
1524 }
1525
1526 #[test]
1529 fn test_severity_color() {
1530 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1531 assert_eq!(
1532 analyzer.get_severity_color(WarningSeverity::Critical),
1533 "red"
1534 );
1535 assert_eq!(
1536 analyzer.get_severity_color(WarningSeverity::High),
1537 "bright red"
1538 );
1539 assert_eq!(
1540 analyzer.get_severity_color(WarningSeverity::Medium),
1541 "yellow"
1542 );
1543 assert_eq!(
1544 analyzer.get_severity_color(WarningSeverity::Low),
1545 "bright yellow"
1546 );
1547 }
1548
1549 #[test]
1550 fn test_format_loop_info() {
1551 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1552
1553 let loc_in_loop = Location {
1554 function: "f".to_string(),
1555 line: 1,
1556 in_loop: true,
1557 loop_depth: 2,
1558 };
1559 let info = analyzer.format_loop_info(&loc_in_loop);
1560 assert!(info.contains("loop"));
1561 assert!(info.contains("2"));
1562
1563 let loc_not_in_loop = Location {
1564 function: "f".to_string(),
1565 line: 1,
1566 in_loop: false,
1567 loop_depth: 0,
1568 };
1569 let info2 = analyzer.format_loop_info(&loc_not_in_loop);
1570 assert!(info2.is_empty());
1571 }
1572
1573 #[test]
1574 fn test_count_severity_levels() {
1575 let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1576 let warnings = vec![
1577 PerformanceWarning {
1578 category: WarningCategory::StringPerformance,
1579 severity: WarningSeverity::Critical,
1580 message: "".to_string(),
1581 explanation: "".to_string(),
1582 suggestion: "".to_string(),
1583 impact: PerformanceImpact {
1584 complexity: "".to_string(),
1585 scales_with_input: false,
1586 in_hot_path: false,
1587 },
1588 location: None,
1589 },
1590 PerformanceWarning {
1591 category: WarningCategory::StringPerformance,
1592 severity: WarningSeverity::High,
1593 message: "".to_string(),
1594 explanation: "".to_string(),
1595 suggestion: "".to_string(),
1596 impact: PerformanceImpact {
1597 complexity: "".to_string(),
1598 scales_with_input: false,
1599 in_hot_path: false,
1600 },
1601 location: None,
1602 },
1603 PerformanceWarning {
1604 category: WarningCategory::StringPerformance,
1605 severity: WarningSeverity::High,
1606 message: "".to_string(),
1607 explanation: "".to_string(),
1608 suggestion: "".to_string(),
1609 impact: PerformanceImpact {
1610 complexity: "".to_string(),
1611 scales_with_input: false,
1612 in_hot_path: false,
1613 },
1614 location: None,
1615 },
1616 ];
1617
1618 let (critical, high) = analyzer.count_severity_levels(&warnings);
1619 assert_eq!(critical, 1);
1620 assert_eq!(high, 2);
1621 }
1622
1623 #[test]
1624 fn test_warnings_sorted_by_severity() {
1625 let func = create_test_function("test", vec![]);
1626 let program = HirProgram {
1627 functions: vec![func],
1628 classes: vec![],
1629 imports: vec![],
1630 };
1631
1632 let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
1633 let warnings = analyzer.analyze_program(&program);
1634
1635 for window in warnings.windows(2) {
1637 assert!(window[0].severity >= window[1].severity);
1638 }
1639 }
1640
1641 #[test]
1644 fn test_warning_severity_ordering_comprehensive() {
1645 assert!(WarningSeverity::Critical > WarningSeverity::High);
1646 assert!(WarningSeverity::High > WarningSeverity::Medium);
1647 assert!(WarningSeverity::Medium > WarningSeverity::Low);
1648 assert_eq!(WarningSeverity::Critical, WarningSeverity::Critical);
1650 }
1651
1652 #[test]
1653 fn test_warning_severity_copy_trait() {
1654 let s1 = WarningSeverity::High;
1655 let s2 = s1; assert_eq!(s1, s2);
1657 let _s3 = s1;
1659 }
1660
1661 #[test]
1662 fn test_config_all_fields_custom() {
1663 let config = PerformanceConfig {
1664 warn_string_concat: false,
1665 warn_allocations: false,
1666 warn_algorithms: true,
1667 warn_repeated_computation: true,
1668 max_loop_depth: 10,
1669 quadratic_threshold: 50,
1670 };
1671 assert!(!config.warn_string_concat);
1672 assert!(!config.warn_allocations);
1673 assert!(config.warn_algorithms);
1674 assert!(config.warn_repeated_computation);
1675 assert_eq!(config.max_loop_depth, 10);
1676 assert_eq!(config.quadratic_threshold, 50);
1677 }
1678
1679 #[test]
1680 fn test_all_warning_categories_debug() {
1681 let categories = [
1683 WarningCategory::StringPerformance,
1684 WarningCategory::MemoryAllocation,
1685 WarningCategory::AlgorithmComplexity,
1686 WarningCategory::RedundantComputation,
1687 WarningCategory::IoPerformance,
1688 WarningCategory::CollectionUsage,
1689 ];
1690 for cat in categories {
1691 let debug = format!("{:?}", cat);
1692 assert!(!debug.is_empty());
1693 }
1694 }
1695
1696 #[test]
1697 fn test_all_warning_severities_debug() {
1698 let severities = [
1699 WarningSeverity::Low,
1700 WarningSeverity::Medium,
1701 WarningSeverity::High,
1702 WarningSeverity::Critical,
1703 ];
1704 for sev in severities {
1705 let debug = format!("{:?}", sev);
1706 assert!(!debug.is_empty());
1707 }
1708 }
1709
1710 #[test]
1711 fn test_analyzer_with_disabled_warnings() {
1712 let config = PerformanceConfig {
1713 warn_string_concat: false,
1714 warn_allocations: false,
1715 warn_algorithms: false,
1716 warn_repeated_computation: false,
1717 max_loop_depth: 100,
1718 quadratic_threshold: 1000,
1719 };
1720 let analyzer = PerformanceAnalyzer::new(config);
1721 assert!(analyzer.warnings.is_empty());
1723 }
1724
1725 #[test]
1726 fn test_warning_category_ne() {
1727 assert_ne!(
1729 WarningCategory::StringPerformance,
1730 WarningCategory::IoPerformance
1731 );
1732 assert_ne!(
1733 WarningCategory::MemoryAllocation,
1734 WarningCategory::CollectionUsage
1735 );
1736 assert_ne!(
1737 WarningCategory::AlgorithmComplexity,
1738 WarningCategory::RedundantComputation
1739 );
1740 }
1741
1742 #[test]
1743 fn test_performance_impact_fields() {
1744 let impact = PerformanceImpact {
1745 complexity: "O(n log n)".to_string(),
1746 scales_with_input: true,
1747 in_hot_path: false,
1748 };
1749 assert_eq!(impact.complexity, "O(n log n)");
1750 assert!(impact.scales_with_input);
1751 assert!(!impact.in_hot_path);
1752 }
1753
1754 #[test]
1755 fn test_location_all_fields() {
1756 let loc = Location {
1757 function: "process".to_string(),
1758 line: 42,
1759 in_loop: true,
1760 loop_depth: 2,
1761 };
1762 assert_eq!(loc.function, "process");
1763 assert_eq!(loc.line, 42);
1764 assert!(loc.in_loop);
1765 assert_eq!(loc.loop_depth, 2);
1766 }
1767}