Skip to main content

depyler_analysis/
performance_warnings.rs

1/// Performance warning system for identifying inefficient patterns
2use depyler_hir::hir::{BinOp, HirExpr, HirFunction, HirProgram, HirStmt, Type};
3use colored::Colorize;
4
5/// Performance analyzer that identifies potentially inefficient patterns
6pub struct PerformanceAnalyzer {
7    /// Collected warnings
8    warnings: Vec<PerformanceWarning>,
9    /// Configuration
10    config: PerformanceConfig,
11    /// Loop depth tracking
12    current_loop_depth: usize,
13}
14
15#[derive(Debug, Clone)]
16pub struct PerformanceConfig {
17    /// Warn about string concatenation in loops
18    pub warn_string_concat: bool,
19    /// Warn about unnecessary allocations
20    pub warn_allocations: bool,
21    /// Warn about inefficient algorithms
22    pub warn_algorithms: bool,
23    /// Warn about repeated computations
24    pub warn_repeated_computation: bool,
25    /// Maximum loop depth before warning
26    pub max_loop_depth: usize,
27    /// Minimum list size to warn about O(n²) operations
28    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    /// Type of performance issue
47    pub category: WarningCategory,
48    /// Severity of the issue
49    pub severity: WarningSeverity,
50    /// Brief description
51    pub message: String,
52    /// Detailed explanation
53    pub explanation: String,
54    /// Suggested fix
55    pub suggestion: String,
56    /// Estimated impact
57    pub impact: PerformanceImpact,
58    /// Source location
59    pub location: Option<Location>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum WarningCategory {
64    /// String operations
65    StringPerformance,
66    /// Memory allocations
67    MemoryAllocation,
68    /// Algorithm complexity
69    AlgorithmComplexity,
70    /// Repeated computation
71    RedundantComputation,
72    /// I/O operations
73    IoPerformance,
74    /// Collection usage
75    CollectionUsage,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
79pub enum WarningSeverity {
80    /// Minor performance impact
81    Low,
82    /// Noticeable performance impact
83    Medium,
84    /// Significant performance impact
85    High,
86    /// Severe performance impact
87    Critical,
88}
89
90#[derive(Debug, Clone)]
91pub struct PerformanceImpact {
92    /// Complexity class (O(n), O(n²), etc.)
93    pub complexity: String,
94    /// Whether impact scales with input size
95    pub scales_with_input: bool,
96    /// Whether it's in a hot path (loop)
97    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    /// Analyze a program for performance issues
118    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        // Sort by severity
126        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        // Check for common performance antipatterns
135        self.check_function_level_issues(func);
136
137        // Analyze function body
138        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        // Check for large parameter passing
145        for param in &func.params {
146            if self.is_large_type(&param.ty) && !self.is_reference_type(&param.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        // Check for range(len(x)) antipattern
434        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        // Check for repeated sorting
477        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        // Check for sum/min/max on same collection multiple times
500        if ["sum", "min", "max"].contains(&fname) && !args.is_empty() {
501            // This would need more context to detect repeated calls on same data
502            // For now, just warn if in nested loops
503            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    // Helper methods
610
611    fn is_large_type(&self, ty: &Type) -> bool {
612        match ty {
613            Type::List(_) | Type::Dict(_, _) | Type::String => true,
614            Type::Custom(name) => {
615                // Assume custom types might be large
616                !["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        // In the current HIR, we don't track references explicitly
624        // This would need enhancement to properly detect &T types
625        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            // Check if either operand might be a string
636            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        // List of known expensive functions
644        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    /// Format warnings for display
656    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    // === PerformanceConfig tests ===
818
819    #[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    // === WarningCategory tests ===
852
853    #[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    // === WarningSeverity tests ===
892
893    #[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    // === PerformanceImpact tests ===
940
941    #[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    // === Location tests ===
976
977    #[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    // === PerformanceWarning tests ===
1016
1017    #[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    // === PerformanceAnalyzer tests ===
1098
1099    #[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    // === Detection tests ===
1165
1166    #[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()); // Depth 2 is within limit
1230    }
1231
1232    #[test]
1233    fn test_deeply_nested_loop_warning() {
1234        // Create 4 levels of nesting to exceed max_loop_depth=3
1235        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        // Simple while loop without issues
1323        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    // === Helper method tests ===
1465
1466    #[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    // === Formatting helper tests ===
1527
1528    #[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        // Check that warnings are sorted by severity (descending)
1636        for window in warnings.windows(2) {
1637            assert!(window[0].severity >= window[1].severity);
1638        }
1639    }
1640
1641    // === DEPYLER-COVERAGE-95: Additional tests for untested components ===
1642
1643    #[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        // Also test equality
1649        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; // Copy, not move
1656        assert_eq!(s1, s2);
1657        // Can still use s1 after copy
1658        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        // Verify all enum variants have valid debug format
1682        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        // Verify it starts empty
1722        assert!(analyzer.warnings.is_empty());
1723    }
1724
1725    #[test]
1726    fn test_warning_category_ne() {
1727        // Test inequality between different categories
1728        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}