use depyler_hir::hir::{BinOp, HirExpr, HirFunction, HirProgram, HirStmt, Type};
use colored::Colorize;
pub struct PerformanceAnalyzer {
warnings: Vec<PerformanceWarning>,
config: PerformanceConfig,
current_loop_depth: usize,
}
#[derive(Debug, Clone)]
pub struct PerformanceConfig {
pub warn_string_concat: bool,
pub warn_allocations: bool,
pub warn_algorithms: bool,
pub warn_repeated_computation: bool,
pub max_loop_depth: usize,
pub quadratic_threshold: usize,
}
impl Default for PerformanceConfig {
fn default() -> Self {
Self {
warn_string_concat: true,
warn_allocations: true,
warn_algorithms: true,
warn_repeated_computation: true,
max_loop_depth: 3,
quadratic_threshold: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct PerformanceWarning {
pub category: WarningCategory,
pub severity: WarningSeverity,
pub message: String,
pub explanation: String,
pub suggestion: String,
pub impact: PerformanceImpact,
pub location: Option<Location>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WarningCategory {
StringPerformance,
MemoryAllocation,
AlgorithmComplexity,
RedundantComputation,
IoPerformance,
CollectionUsage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum WarningSeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone)]
pub struct PerformanceImpact {
pub complexity: String,
pub scales_with_input: bool,
pub in_hot_path: bool,
}
#[derive(Debug, Clone)]
pub struct Location {
pub function: String,
pub line: usize,
pub in_loop: bool,
pub loop_depth: usize,
}
impl PerformanceAnalyzer {
pub fn new(config: PerformanceConfig) -> Self {
Self {
warnings: Vec::new(),
config,
current_loop_depth: 0,
}
}
pub fn analyze_program(&mut self, program: &HirProgram) -> Vec<PerformanceWarning> {
self.warnings.clear();
for func in &program.functions {
self.analyze_function(func);
}
self.warnings.sort_by(|a, b| b.severity.cmp(&a.severity));
self.warnings.clone()
}
fn analyze_function(&mut self, func: &HirFunction) {
self.current_loop_depth = 0;
self.check_function_level_issues(func);
for (idx, stmt) in func.body.iter().enumerate() {
self.analyze_stmt(stmt, func, idx);
}
}
fn check_function_level_issues(&mut self, func: &HirFunction) {
for param in &func.params {
if self.is_large_type(¶m.ty) && !self.is_reference_type(¶m.ty) {
self.add_warning(PerformanceWarning {
category: WarningCategory::MemoryAllocation,
severity: WarningSeverity::Medium,
message: format!("Large value '{}' passed by copy", param.name),
explanation: "Passing large values by copy is inefficient".to_string(),
suggestion:
"Consider passing by reference (&) or using Box/Arc for large types"
.to_string(),
impact: PerformanceImpact {
complexity: "O(n)".to_string(),
scales_with_input: true,
in_hot_path: false,
},
location: Some(Location {
function: func.name.clone(),
line: 0,
in_loop: false,
loop_depth: 0,
}),
});
}
}
}
fn analyze_stmt(&mut self, stmt: &HirStmt, func: &HirFunction, line: usize) {
match stmt {
HirStmt::For {
target: _,
iter,
body,
} => {
self.analyze_for_loop(iter, body, func, line);
}
HirStmt::While { condition: _, body } => {
self.analyze_while_loop(body, func, line);
}
HirStmt::Assign {
target: _, value, ..
} => {
self.analyze_assignment(value, func, line);
}
HirStmt::Expr(expr) => {
self.analyze_expr(expr, func, line);
}
_ => {}
}
}
fn analyze_for_loop(
&mut self,
iter: &HirExpr,
body: &[HirStmt],
func: &HirFunction,
line: usize,
) {
self.current_loop_depth += 1;
self.check_loop_depth_violation(func, line);
self.check_iteration_pattern(iter, func, line);
for inner_stmt in body {
self.analyze_stmt(inner_stmt, func, line);
}
self.current_loop_depth -= 1;
}
fn analyze_while_loop(&mut self, body: &[HirStmt], func: &HirFunction, line: usize) {
self.current_loop_depth += 1;
for inner_stmt in body {
self.analyze_stmt(inner_stmt, func, line);
}
self.current_loop_depth -= 1;
}
fn analyze_assignment(&mut self, value: &HirExpr, func: &HirFunction, line: usize) {
self.analyze_expr(value, func, line);
if self.current_loop_depth > 0 && self.is_string_concatenation(value) {
self.warn_string_concat_in_loop(func, line);
}
}
fn check_loop_depth_violation(&mut self, func: &HirFunction, line: usize) {
if self.current_loop_depth > self.config.max_loop_depth {
self.add_warning(PerformanceWarning {
category: WarningCategory::AlgorithmComplexity,
severity: WarningSeverity::High,
message: format!("Deeply nested loops (depth: {})", self.current_loop_depth),
explanation: "Deeply nested loops can lead to exponential time complexity"
.to_string(),
suggestion:
"Consider refactoring to reduce nesting or use more efficient algorithms"
.to_string(),
impact: PerformanceImpact {
complexity: format!("O(n^{})", self.current_loop_depth),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
}
fn warn_string_concat_in_loop(&mut self, func: &HirFunction, line: usize) {
self.add_warning(PerformanceWarning {
category: WarningCategory::StringPerformance,
severity: WarningSeverity::High,
message: "String concatenation in loop".to_string(),
explanation: "String concatenation in loops creates many intermediate strings"
.to_string(),
suggestion: "Use String::with_capacity() and push_str(), or collect into a String"
.to_string(),
impact: PerformanceImpact {
complexity: "O(n²)".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
fn analyze_expr(&mut self, expr: &HirExpr, func: &HirFunction, line: usize) {
match expr {
HirExpr::Binary { left, right, op } => {
self.analyze_binary_expr(left, right, op, func, line);
}
HirExpr::Call {
func: fname, args, ..
} => {
self.analyze_function_call(fname, args, func, line);
}
HirExpr::MethodCall {
object,
method,
args,
..
} => {
self.analyze_method_call(object, method, args, func, line);
}
HirExpr::List(items) => {
self.analyze_list_expr(items, func, line);
}
_ => {}
}
}
fn analyze_binary_expr(
&mut self,
left: &HirExpr,
right: &HirExpr,
op: &BinOp,
func: &HirFunction,
line: usize,
) {
self.analyze_expr(left, func, line);
self.analyze_expr(right, func, line);
if matches!(op, BinOp::Pow) && self.current_loop_depth > 0 {
self.warn_power_in_loop(func, line);
}
}
fn analyze_function_call(
&mut self,
fname: &str,
args: &[HirExpr],
func: &HirFunction,
line: usize,
) {
if self.current_loop_depth > 0 && self.is_expensive_function(fname) {
self.warn_expensive_function_in_loop(fname, func, line);
}
self.check_function_call_patterns(fname, args, func, line);
for arg in args {
self.analyze_expr(arg, func, line);
}
}
fn analyze_method_call(
&mut self,
object: &HirExpr,
method: &str,
args: &[HirExpr],
func: &HirFunction,
line: usize,
) {
self.analyze_expr(object, func, line);
self.check_method_patterns(object, method, args, func, line);
for arg in args {
self.analyze_expr(arg, func, line);
}
}
fn analyze_list_expr(&mut self, items: &[HirExpr], func: &HirFunction, line: usize) {
if self.current_loop_depth > 0 && items.len() > 10 {
self.warn_large_list_in_loop(func, line);
}
for item in items {
self.analyze_expr(item, func, line);
}
}
fn warn_power_in_loop(&mut self, func: &HirFunction, line: usize) {
self.add_warning(PerformanceWarning {
category: WarningCategory::RedundantComputation,
severity: WarningSeverity::Medium,
message: "Power operation in loop".to_string(),
explanation: "Power operations are computationally expensive".to_string(),
suggestion: "Consider caching the result if the value doesn't change".to_string(),
impact: PerformanceImpact {
complexity: "O(log n) per operation".to_string(),
scales_with_input: false,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
fn warn_expensive_function_in_loop(&mut self, fname: &str, func: &HirFunction, line: usize) {
self.add_warning(PerformanceWarning {
category: WarningCategory::RedundantComputation,
severity: WarningSeverity::Medium,
message: format!("Expensive function '{}' called in loop", fname),
explanation: "Calling expensive functions repeatedly can impact performance"
.to_string(),
suggestion: "Cache the result if the inputs don't change".to_string(),
impact: PerformanceImpact {
complexity: "Depends on function".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
fn warn_large_list_in_loop(&mut self, func: &HirFunction, line: usize) {
self.add_warning(PerformanceWarning {
category: WarningCategory::MemoryAllocation,
severity: WarningSeverity::Medium,
message: "Large list created in loop".to_string(),
explanation: "Creating large collections in loops causes repeated allocations"
.to_string(),
suggestion: "Move the list creation outside the loop or use a pre-allocated buffer"
.to_string(),
impact: PerformanceImpact {
complexity: "O(n) allocations".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
fn check_iteration_pattern(&mut self, iter: &HirExpr, func: &HirFunction, line: usize) {
if let HirExpr::Call {
func: fname, args, ..
} = iter
{
if fname == "range" && !args.is_empty() {
if let HirExpr::Call {
func: inner_func, ..
} = &args[0]
{
if inner_func == "len" {
self.add_warning(PerformanceWarning {
category: WarningCategory::CollectionUsage,
severity: WarningSeverity::Low,
message: "Using range(len(x)) instead of enumerate".to_string(),
explanation: "This pattern is less efficient and less idiomatic"
.to_string(),
suggestion: "Use enumerate() to get both index and value".to_string(),
impact: PerformanceImpact {
complexity: "O(1) overhead".to_string(),
scales_with_input: false,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: false,
loop_depth: self.current_loop_depth,
}),
});
}
}
}
}
}
fn check_function_call_patterns(
&mut self,
fname: &str,
args: &[HirExpr],
func: &HirFunction,
line: usize,
) {
if fname == "sorted" && self.current_loop_depth > 0 {
self.add_warning(PerformanceWarning {
category: WarningCategory::AlgorithmComplexity,
severity: WarningSeverity::High,
message: "Sorting inside a loop".to_string(),
explanation: "Sorting has O(n log n) complexity and shouldn't be repeated"
.to_string(),
suggestion: "Sort once before the loop or maintain sorted order".to_string(),
impact: PerformanceImpact {
complexity: "O(n² log n)".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
if ["sum", "min", "max"].contains(&fname) && !args.is_empty() {
if self.current_loop_depth > 1 {
self.add_warning(PerformanceWarning {
category: WarningCategory::RedundantComputation,
severity: WarningSeverity::Medium,
message: format!("Aggregate function '{}' in nested loop", fname),
explanation: "Computing aggregates repeatedly is inefficient".to_string(),
suggestion: "Compute once and cache the result".to_string(),
impact: PerformanceImpact {
complexity: "O(n) per call".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
}
}
fn check_method_patterns(
&mut self,
_object: &HirExpr,
method: &str,
_args: &[HirExpr],
func: &HirFunction,
line: usize,
) {
if self.current_loop_depth == 0 {
return;
}
match method {
"append" => self.warn_append_in_loop(func, line),
"remove" if self.current_loop_depth > 1 => self.warn_remove_in_nested_loop(func, line),
"index" | "count" => self.warn_linear_search_in_loop(method, func, line),
_ => {}
}
}
fn warn_append_in_loop(&mut self, func: &HirFunction, line: usize) {
self.add_warning(PerformanceWarning {
category: WarningCategory::CollectionUsage,
severity: WarningSeverity::Low,
message: "Multiple append calls in loop".to_string(),
explanation: "Multiple append operations can be less efficient than extend".to_string(),
suggestion: "Consider collecting items and using extend() once".to_string(),
impact: PerformanceImpact {
complexity: "O(1) amortized, but more calls".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
fn warn_remove_in_nested_loop(&mut self, func: &HirFunction, line: usize) {
self.add_warning(PerformanceWarning {
category: WarningCategory::AlgorithmComplexity,
severity: WarningSeverity::Critical,
message: "List remove() in nested loop".to_string(),
explanation: "remove() is O(n) and in nested loops becomes O(n²) or worse".to_string(),
suggestion: "Use a set for O(1) removal or filter to create a new list".to_string(),
impact: PerformanceImpact {
complexity: format!("O(n^{})", self.current_loop_depth + 1),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
fn warn_linear_search_in_loop(&mut self, method: &str, func: &HirFunction, line: usize) {
self.add_warning(PerformanceWarning {
category: WarningCategory::AlgorithmComplexity,
severity: WarningSeverity::Medium,
message: format!("Linear search method '{}' in loop", method),
explanation: "Linear search in loops can lead to quadratic complexity".to_string(),
suggestion: "Consider using a HashMap/HashSet for O(1) lookups".to_string(),
impact: PerformanceImpact {
complexity: "O(n²)".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: func.name.clone(),
line,
in_loop: true,
loop_depth: self.current_loop_depth,
}),
});
}
fn is_large_type(&self, ty: &Type) -> bool {
match ty {
Type::List(_) | Type::Dict(_, _) | Type::String => true,
Type::Custom(name) => {
!["i32", "i64", "f32", "f64", "bool", "char"].contains(&name.as_str())
}
_ => false,
}
}
fn is_reference_type(&self, _ty: &Type) -> bool {
false
}
fn is_string_concatenation(&self, expr: &HirExpr) -> bool {
if let HirExpr::Binary {
op: BinOp::Add,
left,
right,
} = expr
{
matches!(left.as_ref(), HirExpr::Var(_)) || matches!(right.as_ref(), HirExpr::Var(_))
} else {
false
}
}
fn is_expensive_function(&self, fname: &str) -> bool {
let expensive = [
"sorted", "sort", "reverse", "compile", "eval", "exec", "deepcopy", "copy", "hash",
"checksum",
];
expensive.contains(&fname)
}
fn add_warning(&mut self, warning: PerformanceWarning) {
self.warnings.push(warning);
}
pub fn format_warnings(&self, warnings: &[PerformanceWarning]) -> String {
if warnings.is_empty() {
return "✅ No performance warnings found!\n".green().to_string();
}
let mut output = String::new();
self.append_header(&mut output);
self.append_warning_details(&mut output, warnings);
self.append_summary(&mut output, warnings);
output
}
fn append_header(&self, output: &mut String) {
output.push_str(&format!("\n{}\n", "Performance Warnings".bold().yellow()));
output.push_str(&format!("{}\n\n", "═".repeat(50)));
}
fn append_warning_details(&self, output: &mut String, warnings: &[PerformanceWarning]) {
for (idx, warning) in warnings.iter().enumerate() {
self.append_single_warning(output, idx, warning);
}
}
fn append_single_warning(&self, output: &mut String, idx: usize, warning: &PerformanceWarning) {
self.append_warning_header(output, idx, warning);
self.append_warning_location(output, warning);
self.append_warning_impact(output, warning);
self.append_warning_explanation(output, warning);
self.append_warning_suggestion(output, warning);
output.push('\n');
}
fn append_warning_header(&self, output: &mut String, idx: usize, warning: &PerformanceWarning) {
let severity_color = self.get_severity_color(warning.severity);
output.push_str(&format!(
"{} {} {}\n",
format!("[{}]", idx + 1).dimmed(),
format!("[{:?}]", warning.severity)
.color(severity_color)
.bold(),
warning.message.bold()
));
}
fn append_warning_location(&self, output: &mut String, warning: &PerformanceWarning) {
if let Some(loc) = &warning.location {
let loop_info = self.format_loop_info(loc);
output.push_str(&format!(
" {} {}, line {}{}\n",
"Location:".dimmed(),
loc.function,
loc.line,
loop_info
));
}
}
fn append_warning_impact(&self, output: &mut String, warning: &PerformanceWarning) {
output.push_str(&format!(
" {} Complexity: {}, Scales: {}, Hot path: {}\n",
"Impact:".dimmed(),
warning.impact.complexity.yellow(),
self.format_yes_no(warning.impact.scales_with_input),
self.format_yes_no(warning.impact.in_hot_path)
));
}
fn append_warning_explanation(&self, output: &mut String, warning: &PerformanceWarning) {
output.push_str(&format!(" {} {}\n", "Why:".dimmed(), warning.explanation));
}
fn append_warning_suggestion(&self, output: &mut String, warning: &PerformanceWarning) {
output.push_str(&format!(
" {} {}\n",
"Fix:".green(),
warning.suggestion.green()
));
}
fn append_summary(&self, output: &mut String, warnings: &[PerformanceWarning]) {
let (critical, high) = self.count_severity_levels(warnings);
output.push_str(&format!(
"{} Found {} warnings ({} critical, {} high severity)\n",
"Summary:".bold(),
warnings.len(),
critical,
high
));
if critical > 0 || high > 0 {
self.append_critical_warning_notice(output);
}
}
fn get_severity_color(&self, severity: WarningSeverity) -> &'static str {
match severity {
WarningSeverity::Critical => "red",
WarningSeverity::High => "bright red",
WarningSeverity::Medium => "yellow",
WarningSeverity::Low => "bright yellow",
}
}
fn format_loop_info(&self, loc: &Location) -> String {
if loc.in_loop {
format!(" (in loop, depth: {})", loc.loop_depth)
.red()
.to_string()
} else {
String::new()
}
}
fn format_yes_no(&self, value: bool) -> colored::ColoredString {
if value {
"Yes".red()
} else {
"No".green()
}
}
fn count_severity_levels(&self, warnings: &[PerformanceWarning]) -> (usize, usize) {
let critical = warnings
.iter()
.filter(|w| w.severity == WarningSeverity::Critical)
.count();
let high = warnings
.iter()
.filter(|w| w.severity == WarningSeverity::High)
.count();
(critical, high)
}
fn append_critical_warning_notice(&self, output: &mut String) {
output.push_str(
&"⚠️ Address critical and high severity warnings for better performance\n"
.red()
.to_string(),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use depyler_hir::hir::*;
use smallvec::smallvec;
fn create_test_function(name: &str, body: Vec<HirStmt>) -> HirFunction {
HirFunction {
name: name.to_string(),
params: smallvec![],
ret_type: Type::Unknown,
body,
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
}
}
#[test]
fn test_performance_config_default() {
let config = PerformanceConfig::default();
assert!(config.warn_string_concat);
assert!(config.warn_allocations);
assert!(config.warn_algorithms);
assert!(config.warn_repeated_computation);
assert_eq!(config.max_loop_depth, 3);
assert_eq!(config.quadratic_threshold, 100);
}
#[test]
fn test_performance_config_clone() {
let config = PerformanceConfig {
warn_string_concat: false,
warn_allocations: false,
max_loop_depth: 5,
..Default::default()
};
let cloned = config.clone();
assert!(!cloned.warn_string_concat);
assert_eq!(cloned.max_loop_depth, 5);
}
#[test]
fn test_performance_config_debug() {
let config = PerformanceConfig::default();
let debug = format!("{:?}", config);
assert!(debug.contains("PerformanceConfig"));
assert!(debug.contains("warn_string_concat"));
}
#[test]
fn test_warning_category_variants() {
let categories = [
WarningCategory::StringPerformance,
WarningCategory::MemoryAllocation,
WarningCategory::AlgorithmComplexity,
WarningCategory::RedundantComputation,
WarningCategory::IoPerformance,
WarningCategory::CollectionUsage,
];
assert_eq!(categories.len(), 6);
}
#[test]
fn test_warning_category_eq() {
assert_eq!(
WarningCategory::StringPerformance,
WarningCategory::StringPerformance
);
assert_ne!(
WarningCategory::StringPerformance,
WarningCategory::MemoryAllocation
);
}
#[test]
fn test_warning_category_clone() {
let cat = WarningCategory::AlgorithmComplexity;
let cloned = cat.clone();
assert_eq!(cat, cloned);
}
#[test]
fn test_warning_category_debug() {
let debug = format!("{:?}", WarningCategory::IoPerformance);
assert!(debug.contains("IoPerformance"));
}
#[test]
fn test_severity_ordering() {
assert!(WarningSeverity::Critical > WarningSeverity::High);
assert!(WarningSeverity::High > WarningSeverity::Medium);
assert!(WarningSeverity::Medium > WarningSeverity::Low);
}
#[test]
fn test_severity_eq() {
assert_eq!(WarningSeverity::Low, WarningSeverity::Low);
assert_ne!(WarningSeverity::Low, WarningSeverity::High);
}
#[test]
fn test_severity_copy() {
let s1 = WarningSeverity::Critical;
let s2 = s1;
assert_eq!(s1, s2);
}
#[test]
fn test_severity_clone() {
let s = WarningSeverity::Medium;
let cloned = s;
assert_eq!(s, cloned);
}
#[test]
fn test_severity_debug() {
let debug = format!("{:?}", WarningSeverity::High);
assert!(debug.contains("High"));
}
#[test]
fn test_severity_ord_all() {
let mut severities = [
WarningSeverity::Medium,
WarningSeverity::Critical,
WarningSeverity::Low,
WarningSeverity::High,
];
severities.sort();
assert_eq!(severities[0], WarningSeverity::Low);
assert_eq!(severities[3], WarningSeverity::Critical);
}
#[test]
fn test_performance_impact_new() {
let impact = PerformanceImpact {
complexity: "O(n²)".to_string(),
scales_with_input: true,
in_hot_path: false,
};
assert_eq!(impact.complexity, "O(n²)");
assert!(impact.scales_with_input);
assert!(!impact.in_hot_path);
}
#[test]
fn test_performance_impact_clone() {
let impact = PerformanceImpact {
complexity: "O(1)".to_string(),
scales_with_input: false,
in_hot_path: true,
};
let cloned = impact.clone();
assert_eq!(cloned.complexity, "O(1)");
}
#[test]
fn test_performance_impact_debug() {
let impact = PerformanceImpact {
complexity: "O(n)".to_string(),
scales_with_input: true,
in_hot_path: true,
};
let debug = format!("{:?}", impact);
assert!(debug.contains("PerformanceImpact"));
}
#[test]
fn test_location_new() {
let loc = Location {
function: "my_func".to_string(),
line: 42,
in_loop: true,
loop_depth: 2,
};
assert_eq!(loc.function, "my_func");
assert_eq!(loc.line, 42);
assert!(loc.in_loop);
assert_eq!(loc.loop_depth, 2);
}
#[test]
fn test_location_clone() {
let loc = Location {
function: "test".to_string(),
line: 1,
in_loop: false,
loop_depth: 0,
};
let cloned = loc.clone();
assert_eq!(loc.function, cloned.function);
}
#[test]
fn test_location_debug() {
let loc = Location {
function: "f".to_string(),
line: 10,
in_loop: true,
loop_depth: 1,
};
let debug = format!("{:?}", loc);
assert!(debug.contains("Location"));
}
#[test]
fn test_performance_warning_new() {
let warning = PerformanceWarning {
category: WarningCategory::StringPerformance,
severity: WarningSeverity::High,
message: "test message".to_string(),
explanation: "test explanation".to_string(),
suggestion: "test suggestion".to_string(),
impact: PerformanceImpact {
complexity: "O(n)".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: "test".to_string(),
line: 1,
in_loop: true,
loop_depth: 1,
}),
};
assert_eq!(warning.category, WarningCategory::StringPerformance);
assert_eq!(warning.severity, WarningSeverity::High);
}
#[test]
fn test_performance_warning_no_location() {
let warning = PerformanceWarning {
category: WarningCategory::MemoryAllocation,
severity: WarningSeverity::Low,
message: "msg".to_string(),
explanation: "exp".to_string(),
suggestion: "sug".to_string(),
impact: PerformanceImpact {
complexity: "O(1)".to_string(),
scales_with_input: false,
in_hot_path: false,
},
location: None,
};
assert!(warning.location.is_none());
}
#[test]
fn test_performance_warning_clone() {
let warning = PerformanceWarning {
category: WarningCategory::CollectionUsage,
severity: WarningSeverity::Medium,
message: "test".to_string(),
explanation: "exp".to_string(),
suggestion: "sug".to_string(),
impact: PerformanceImpact {
complexity: "O(n)".to_string(),
scales_with_input: true,
in_hot_path: false,
},
location: None,
};
let cloned = warning.clone();
assert_eq!(cloned.category, WarningCategory::CollectionUsage);
}
#[test]
fn test_performance_warning_debug() {
let warning = PerformanceWarning {
category: WarningCategory::RedundantComputation,
severity: WarningSeverity::Critical,
message: "debug test".to_string(),
explanation: "".to_string(),
suggestion: "".to_string(),
impact: PerformanceImpact {
complexity: "O(1)".to_string(),
scales_with_input: false,
in_hot_path: false,
},
location: None,
};
let debug = format!("{:?}", warning);
assert!(debug.contains("PerformanceWarning"));
}
#[test]
fn test_analyzer_new() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert_eq!(analyzer.current_loop_depth, 0);
}
#[test]
fn test_analyzer_format_warnings_empty() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let output = analyzer.format_warnings(&[]);
assert!(output.contains("No performance warnings"));
}
#[test]
fn test_analyzer_format_warnings_with_warnings() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = vec![PerformanceWarning {
category: WarningCategory::StringPerformance,
severity: WarningSeverity::High,
message: "Test warning".to_string(),
explanation: "Test explanation".to_string(),
suggestion: "Test suggestion".to_string(),
impact: PerformanceImpact {
complexity: "O(n²)".to_string(),
scales_with_input: true,
in_hot_path: true,
},
location: Some(Location {
function: "test_func".to_string(),
line: 10,
in_loop: true,
loop_depth: 1,
}),
}];
let output = analyzer.format_warnings(&warnings);
assert!(output.contains("Performance Warnings"));
assert!(output.contains("Test warning"));
assert!(output.contains("test_func"));
}
#[test]
fn test_analyzer_empty_program() {
let program = HirProgram {
functions: vec![],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings.is_empty());
}
#[test]
fn test_analyzer_simple_function() {
let func = create_test_function("simple", vec![]);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings.is_empty());
}
#[test]
fn test_string_concat_in_loop_detection() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Call {
func: "range".to_string(),
args: vec![HirExpr::Literal(Literal::Int(10))],
kwargs: vec![],
},
body: vec![HirStmt::Assign {
target: AssignTarget::Symbol("s".to_string()),
value: HirExpr::Binary {
op: BinOp::Add,
left: Box::new(HirExpr::Var("s".to_string())),
right: Box::new(HirExpr::Var("i".to_string())),
},
type_annotation: None,
}],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(!warnings.is_empty());
assert!(warnings
.iter()
.any(|w| w.category == WarningCategory::StringPerformance));
}
#[test]
fn test_nested_loop_detection() {
let inner_loop = HirStmt::For {
target: AssignTarget::Symbol("j".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![HirStmt::Expr(HirExpr::Var("j".to_string()))],
};
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![inner_loop],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig {
max_loop_depth: 2,
..Default::default()
});
let warnings = analyzer.analyze_program(&program);
assert!(warnings.is_empty()); }
#[test]
fn test_deeply_nested_loop_warning() {
let innermost = HirStmt::Expr(HirExpr::Var("x".to_string()));
let level3 = HirStmt::For {
target: AssignTarget::Symbol("d".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![innermost],
};
let level2 = HirStmt::For {
target: AssignTarget::Symbol("c".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![level3],
};
let level1 = HirStmt::For {
target: AssignTarget::Symbol("b".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![level2],
};
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("a".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![level1],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings
.iter()
.any(|w| w.category == WarningCategory::AlgorithmComplexity
&& w.message.contains("Deeply nested")));
}
#[test]
fn test_expensive_function_in_loop() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("item".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![HirStmt::Assign {
target: AssignTarget::Symbol("s".to_string()),
value: HirExpr::Call {
func: "sorted".to_string(),
args: vec![HirExpr::Var("data".to_string())],
kwargs: vec![],
},
type_annotation: None,
}],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(!warnings.is_empty());
assert!(warnings.iter().any(|w| {
w.category == WarningCategory::AlgorithmComplexity
|| w.category == WarningCategory::RedundantComputation
}));
}
#[test]
fn test_while_loop_analysis() {
let body = vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Expr(HirExpr::Var("x".to_string()))],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings.is_empty());
}
#[test]
fn test_range_len_antipattern() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Call {
func: "range".to_string(),
args: vec![HirExpr::Call {
func: "len".to_string(),
args: vec![HirExpr::Var("items".to_string())],
kwargs: vec![],
}],
kwargs: vec![],
},
body: vec![HirStmt::Expr(HirExpr::Var("i".to_string()))],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings.iter().any(|w| w.message.contains("range(len")));
}
#[test]
fn test_large_list_in_loop() {
let large_list = HirExpr::List(vec![HirExpr::Literal(Literal::Int(1)); 15]);
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![HirStmt::Expr(large_list)],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings
.iter()
.any(|w| w.category == WarningCategory::MemoryAllocation));
}
#[test]
fn test_append_in_loop() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![HirStmt::Expr(HirExpr::MethodCall {
object: Box::new(HirExpr::Var("result".to_string())),
method: "append".to_string(),
args: vec![HirExpr::Var("i".to_string())],
kwargs: vec![],
})],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings
.iter()
.any(|w| w.category == WarningCategory::CollectionUsage));
}
#[test]
fn test_linear_search_in_loop() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![HirStmt::Expr(HirExpr::MethodCall {
object: Box::new(HirExpr::Var("data".to_string())),
method: "index".to_string(),
args: vec![HirExpr::Var("i".to_string())],
kwargs: vec![],
})],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings.iter().any(|w| w.message.contains("Linear search")));
}
#[test]
fn test_power_in_loop() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![HirStmt::Assign {
target: AssignTarget::Symbol("result".to_string()),
value: HirExpr::Binary {
op: BinOp::Pow,
left: Box::new(HirExpr::Var("x".to_string())),
right: Box::new(HirExpr::Var("i".to_string())),
},
type_annotation: None,
}],
}];
let func = create_test_function("test", body);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
assert!(warnings
.iter()
.any(|w| w.message.contains("Power operation")));
}
#[test]
fn test_is_large_type_list() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert!(analyzer.is_large_type(&Type::List(Box::new(Type::Int))));
}
#[test]
fn test_is_large_type_dict() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert!(analyzer.is_large_type(&Type::Dict(Box::new(Type::String), Box::new(Type::Int))));
}
#[test]
fn test_is_large_type_string() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert!(analyzer.is_large_type(&Type::String));
}
#[test]
fn test_is_large_type_int() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert!(!analyzer.is_large_type(&Type::Int));
}
#[test]
fn test_is_large_type_custom() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert!(analyzer.is_large_type(&Type::Custom("MyStruct".to_string())));
assert!(!analyzer.is_large_type(&Type::Custom("i32".to_string())));
}
#[test]
fn test_is_expensive_function() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert!(analyzer.is_expensive_function("sorted"));
assert!(analyzer.is_expensive_function("deepcopy"));
assert!(analyzer.is_expensive_function("eval"));
assert!(!analyzer.is_expensive_function("len"));
assert!(!analyzer.is_expensive_function("print"));
}
#[test]
fn test_is_string_concatenation() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let concat = HirExpr::Binary {
op: BinOp::Add,
left: Box::new(HirExpr::Var("a".to_string())),
right: Box::new(HirExpr::Var("b".to_string())),
};
assert!(analyzer.is_string_concatenation(&concat));
let mult = HirExpr::Binary {
op: BinOp::Mul,
left: Box::new(HirExpr::Var("a".to_string())),
right: Box::new(HirExpr::Var("b".to_string())),
};
assert!(!analyzer.is_string_concatenation(&mult));
}
#[test]
fn test_severity_color() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
assert_eq!(
analyzer.get_severity_color(WarningSeverity::Critical),
"red"
);
assert_eq!(
analyzer.get_severity_color(WarningSeverity::High),
"bright red"
);
assert_eq!(
analyzer.get_severity_color(WarningSeverity::Medium),
"yellow"
);
assert_eq!(
analyzer.get_severity_color(WarningSeverity::Low),
"bright yellow"
);
}
#[test]
fn test_format_loop_info() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let loc_in_loop = Location {
function: "f".to_string(),
line: 1,
in_loop: true,
loop_depth: 2,
};
let info = analyzer.format_loop_info(&loc_in_loop);
assert!(info.contains("loop"));
assert!(info.contains("2"));
let loc_not_in_loop = Location {
function: "f".to_string(),
line: 1,
in_loop: false,
loop_depth: 0,
};
let info2 = analyzer.format_loop_info(&loc_not_in_loop);
assert!(info2.is_empty());
}
#[test]
fn test_count_severity_levels() {
let analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = vec![
PerformanceWarning {
category: WarningCategory::StringPerformance,
severity: WarningSeverity::Critical,
message: "".to_string(),
explanation: "".to_string(),
suggestion: "".to_string(),
impact: PerformanceImpact {
complexity: "".to_string(),
scales_with_input: false,
in_hot_path: false,
},
location: None,
},
PerformanceWarning {
category: WarningCategory::StringPerformance,
severity: WarningSeverity::High,
message: "".to_string(),
explanation: "".to_string(),
suggestion: "".to_string(),
impact: PerformanceImpact {
complexity: "".to_string(),
scales_with_input: false,
in_hot_path: false,
},
location: None,
},
PerformanceWarning {
category: WarningCategory::StringPerformance,
severity: WarningSeverity::High,
message: "".to_string(),
explanation: "".to_string(),
suggestion: "".to_string(),
impact: PerformanceImpact {
complexity: "".to_string(),
scales_with_input: false,
in_hot_path: false,
},
location: None,
},
];
let (critical, high) = analyzer.count_severity_levels(&warnings);
assert_eq!(critical, 1);
assert_eq!(high, 2);
}
#[test]
fn test_warnings_sorted_by_severity() {
let func = create_test_function("test", vec![]);
let program = HirProgram {
functions: vec![func],
classes: vec![],
imports: vec![],
};
let mut analyzer = PerformanceAnalyzer::new(PerformanceConfig::default());
let warnings = analyzer.analyze_program(&program);
for window in warnings.windows(2) {
assert!(window[0].severity >= window[1].severity);
}
}
#[test]
fn test_warning_severity_ordering_comprehensive() {
assert!(WarningSeverity::Critical > WarningSeverity::High);
assert!(WarningSeverity::High > WarningSeverity::Medium);
assert!(WarningSeverity::Medium > WarningSeverity::Low);
assert_eq!(WarningSeverity::Critical, WarningSeverity::Critical);
}
#[test]
fn test_warning_severity_copy_trait() {
let s1 = WarningSeverity::High;
let s2 = s1; assert_eq!(s1, s2);
let _s3 = s1;
}
#[test]
fn test_config_all_fields_custom() {
let config = PerformanceConfig {
warn_string_concat: false,
warn_allocations: false,
warn_algorithms: true,
warn_repeated_computation: true,
max_loop_depth: 10,
quadratic_threshold: 50,
};
assert!(!config.warn_string_concat);
assert!(!config.warn_allocations);
assert!(config.warn_algorithms);
assert!(config.warn_repeated_computation);
assert_eq!(config.max_loop_depth, 10);
assert_eq!(config.quadratic_threshold, 50);
}
#[test]
fn test_all_warning_categories_debug() {
let categories = [
WarningCategory::StringPerformance,
WarningCategory::MemoryAllocation,
WarningCategory::AlgorithmComplexity,
WarningCategory::RedundantComputation,
WarningCategory::IoPerformance,
WarningCategory::CollectionUsage,
];
for cat in categories {
let debug = format!("{:?}", cat);
assert!(!debug.is_empty());
}
}
#[test]
fn test_all_warning_severities_debug() {
let severities = [
WarningSeverity::Low,
WarningSeverity::Medium,
WarningSeverity::High,
WarningSeverity::Critical,
];
for sev in severities {
let debug = format!("{:?}", sev);
assert!(!debug.is_empty());
}
}
#[test]
fn test_analyzer_with_disabled_warnings() {
let config = PerformanceConfig {
warn_string_concat: false,
warn_allocations: false,
warn_algorithms: false,
warn_repeated_computation: false,
max_loop_depth: 100,
quadratic_threshold: 1000,
};
let analyzer = PerformanceAnalyzer::new(config);
assert!(analyzer.warnings.is_empty());
}
#[test]
fn test_warning_category_ne() {
assert_ne!(
WarningCategory::StringPerformance,
WarningCategory::IoPerformance
);
assert_ne!(
WarningCategory::MemoryAllocation,
WarningCategory::CollectionUsage
);
assert_ne!(
WarningCategory::AlgorithmComplexity,
WarningCategory::RedundantComputation
);
}
#[test]
fn test_performance_impact_fields() {
let impact = PerformanceImpact {
complexity: "O(n log n)".to_string(),
scales_with_input: true,
in_hot_path: false,
};
assert_eq!(impact.complexity, "O(n log n)");
assert!(impact.scales_with_input);
assert!(!impact.in_hot_path);
}
#[test]
fn test_location_all_fields() {
let loc = Location {
function: "process".to_string(),
line: 42,
in_loop: true,
loop_depth: 2,
};
assert_eq!(loc.function, "process");
assert_eq!(loc.line, 42);
assert!(loc.in_loop);
assert_eq!(loc.loop_depth, 2);
}
}