Skip to main content

depyler_analysis/
profiling.rs

1//! Profiling integration for performance analysis of transpiled code
2//!
3//! This module provides tools to profile and analyze the performance characteristics
4//! of Python code and its transpiled Rust equivalent, helping developers understand
5//! performance improvements and bottlenecks.
6
7use depyler_hir::hir::{HirExpr, HirFunction, HirProgram, HirStmt};
8use colored::Colorize;
9use std::collections::HashMap;
10
11/// Profiling configuration and results collector
12pub struct Profiler {
13    /// Configuration for profiling
14    config: ProfileConfig,
15    /// Collected metrics
16    metrics: HashMap<String, FunctionMetrics>,
17    /// Hot path analysis results
18    hot_paths: Vec<HotPath>,
19    /// Performance predictions
20    predictions: Vec<PerformancePrediction>,
21}
22
23#[derive(Debug, Clone)]
24pub struct ProfileConfig {
25    /// Enable instruction counting
26    pub count_instructions: bool,
27    /// Enable memory allocation tracking
28    pub track_allocations: bool,
29    /// Enable hot path detection
30    pub detect_hot_paths: bool,
31    /// Minimum samples for hot path detection
32    pub hot_path_threshold: usize,
33    /// Generate flame graph data
34    pub generate_flamegraph: bool,
35    /// Include performance hints
36    pub include_hints: bool,
37}
38
39impl Default for ProfileConfig {
40    fn default() -> Self {
41        Self {
42            count_instructions: true,
43            track_allocations: true,
44            detect_hot_paths: true,
45            hot_path_threshold: 100,
46            generate_flamegraph: false,
47            include_hints: true,
48        }
49    }
50}
51
52#[derive(Debug, Clone)]
53pub struct FunctionMetrics {
54    /// Function name
55    pub name: String,
56    /// Estimated instruction count
57    pub instruction_count: usize,
58    /// Estimated memory allocations
59    pub allocation_count: usize,
60    /// Estimated execution time (relative)
61    pub estimated_time: f64,
62    /// Number of times called (if detectable)
63    pub call_count: usize,
64    /// Percentage of total program time
65    pub time_percentage: f64,
66    /// Whether this is a hot function
67    pub is_hot: bool,
68}
69
70#[derive(Debug, Clone)]
71pub struct HotPath {
72    /// Functions in the call chain
73    pub call_chain: Vec<String>,
74    /// Estimated percentage of execution time
75    pub time_percentage: f64,
76    /// Loop depth in the path
77    pub loop_depth: usize,
78    /// Whether path contains I/O
79    pub has_io: bool,
80}
81
82#[derive(Debug, Clone)]
83pub struct PerformancePrediction {
84    /// Type of prediction
85    pub category: PredictionCategory,
86    /// Confidence level (0.0 to 1.0)
87    pub confidence: f64,
88    /// Predicted speedup factor
89    pub speedup_factor: f64,
90    /// Explanation
91    pub explanation: String,
92    /// Affected functions
93    pub functions: Vec<String>,
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub enum PredictionCategory {
98    /// Type system eliminates runtime checks
99    TypeSystemOptimization,
100    /// Zero-cost abstractions in Rust
101    ZeroCostAbstraction,
102    /// Memory layout improvements
103    MemoryLayoutOptimization,
104    /// Iterator fusion and optimization
105    IteratorOptimization,
106    /// String handling improvements
107    StringOptimization,
108    /// Parallelization opportunities
109    ParallelizationOpportunity,
110}
111
112/// Profiling annotations that can be added to generated code
113#[derive(Debug, Clone)]
114pub struct ProfilingAnnotation {
115    /// Annotation type
116    pub kind: AnnotationKind,
117    /// Target function or location
118    pub target: String,
119    /// Annotation value
120    pub value: String,
121}
122
123#[derive(Debug, Clone)]
124pub enum AnnotationKind {
125    /// Instrument with timing
126    TimingProbe,
127    /// Count allocations
128    AllocationCounter,
129    /// Mark as hot path
130    HotPathMarker,
131    /// Performance hint
132    PerformanceHint,
133}
134
135impl Profiler {
136    pub fn new(config: ProfileConfig) -> Self {
137        Self {
138            config,
139            metrics: HashMap::new(),
140            hot_paths: Vec::new(),
141            predictions: Vec::new(),
142        }
143    }
144
145    /// Analyze a program for profiling insights
146    pub fn analyze_program(&mut self, program: &HirProgram) -> ProfilingReport {
147        // Clear previous results
148        self.metrics.clear();
149        self.hot_paths.clear();
150        self.predictions.clear();
151
152        // Analyze each function
153        let mut total_instructions = 0;
154        let mut total_allocations = 0;
155
156        for func in &program.functions {
157            let metrics = self.analyze_function(func);
158            total_instructions += metrics.instruction_count;
159            total_allocations += metrics.allocation_count;
160            self.metrics.insert(func.name.clone(), metrics);
161        }
162
163        // Calculate time percentages
164        for metrics in self.metrics.values_mut() {
165            metrics.time_percentage = (metrics.estimated_time / total_instructions as f64) * 100.0;
166            metrics.is_hot = metrics.time_percentage > 10.0;
167        }
168
169        // Detect hot paths
170        if self.config.detect_hot_paths {
171            self.detect_hot_paths(program);
172        }
173
174        // Generate performance predictions
175        self.generate_predictions(program);
176
177        // Create report
178        ProfilingReport {
179            metrics: self.metrics.clone(),
180            hot_paths: self.hot_paths.clone(),
181            predictions: self.predictions.clone(),
182            total_instructions,
183            total_allocations,
184            annotations: self.generate_annotations(),
185        }
186    }
187
188    fn analyze_function(&self, func: &HirFunction) -> FunctionMetrics {
189        let mut instruction_count = 0;
190        let mut allocation_count = 0;
191        let mut loop_multiplier = 1.0;
192
193        // Analyze function body
194        for stmt in &func.body {
195            let (inst, alloc, loop_factor) = self.analyze_stmt(stmt, 1);
196            instruction_count += inst;
197            allocation_count += alloc;
198            loop_multiplier *= loop_factor;
199        }
200
201        // Estimate execution time based on instruction count and loop factors
202        let estimated_time = instruction_count as f64 * loop_multiplier;
203
204        FunctionMetrics {
205            name: func.name.clone(),
206            instruction_count,
207            allocation_count,
208            estimated_time,
209            call_count: 0,        // Would need call graph analysis
210            time_percentage: 0.0, // Calculated later
211            is_hot: false,        // Determined later
212        }
213    }
214
215    fn analyze_stmt(&self, stmt: &HirStmt, loop_depth: usize) -> (usize, usize, f64) {
216        match stmt {
217            HirStmt::Assign { value, .. } => self.analyze_assign(value),
218            HirStmt::Expr(expr) => self.analyze_expr_stmt(expr),
219            HirStmt::Return(Some(expr)) => self.analyze_return_with_value(expr),
220            HirStmt::Return(None) => (1, 0, 1.0),
221            HirStmt::If {
222                condition,
223                then_body,
224                else_body,
225            } => self.analyze_if(condition, then_body, else_body.as_deref(), loop_depth),
226            HirStmt::While { condition, body } => self.analyze_while(condition, body, loop_depth),
227            HirStmt::For { iter, body, .. } => self.analyze_for(iter, body, loop_depth),
228            _ => (1, 0, 1.0),
229        }
230    }
231
232    fn analyze_assign(&self, value: &HirExpr) -> (usize, usize, f64) {
233        let (inst, alloc) = self.analyze_expr(value);
234        (inst + 1, alloc, 1.0)
235    }
236
237    fn analyze_expr_stmt(&self, expr: &HirExpr) -> (usize, usize, f64) {
238        let (inst, alloc) = self.analyze_expr(expr);
239        (inst, alloc, 1.0)
240    }
241
242    fn analyze_return_with_value(&self, expr: &HirExpr) -> (usize, usize, f64) {
243        let (inst, alloc) = self.analyze_expr(expr);
244        (inst + 1, alloc, 1.0)
245    }
246
247    fn analyze_if(
248        &self,
249        condition: &HirExpr,
250        then_body: &[HirStmt],
251        else_body: Option<&[HirStmt]>,
252        loop_depth: usize,
253    ) -> (usize, usize, f64) {
254        let (cond_inst, cond_alloc) = self.analyze_expr(condition);
255        let mut total_inst = cond_inst + 1;
256        let mut total_alloc = cond_alloc;
257
258        for stmt in then_body {
259            let (inst, alloc, _) = self.analyze_stmt(stmt, loop_depth);
260            total_inst += inst;
261            total_alloc += alloc;
262        }
263
264        if let Some(else_stmts) = else_body {
265            for stmt in else_stmts {
266                let (inst, alloc, _) = self.analyze_stmt(stmt, loop_depth);
267                total_inst += inst / 2;
268                total_alloc += alloc / 2;
269            }
270        }
271
272        (total_inst, total_alloc, 1.0)
273    }
274
275    fn analyze_while(
276        &self,
277        condition: &HirExpr,
278        body: &[HirStmt],
279        loop_depth: usize,
280    ) -> (usize, usize, f64) {
281        let (cond_inst, cond_alloc) = self.analyze_expr(condition);
282        let (body_inst, body_alloc) = self.analyze_body(body, loop_depth + 1);
283        let loop_factor = 10.0_f64.powi(loop_depth as i32);
284        (
285            cond_inst + (body_inst * 10),
286            cond_alloc + (body_alloc * 10),
287            loop_factor,
288        )
289    }
290
291    fn analyze_for(
292        &self,
293        iter: &HirExpr,
294        body: &[HirStmt],
295        loop_depth: usize,
296    ) -> (usize, usize, f64) {
297        let (iter_inst, iter_alloc) = self.analyze_expr(iter);
298        let (body_inst, body_alloc) = self.analyze_body(body, loop_depth + 1);
299        let loop_factor = 10.0_f64.powi(loop_depth as i32);
300        (
301            iter_inst + (body_inst * 10),
302            iter_alloc + (body_alloc * 10),
303            loop_factor,
304        )
305    }
306
307    fn analyze_body(&self, body: &[HirStmt], loop_depth: usize) -> (usize, usize) {
308        let mut body_inst = 0;
309        let mut body_alloc = 0;
310        for stmt in body {
311            let (inst, alloc, _) = self.analyze_stmt(stmt, loop_depth);
312            body_inst += inst;
313            body_alloc += alloc;
314        }
315        (body_inst, body_alloc)
316    }
317
318    fn analyze_expr(&self, expr: &HirExpr) -> (usize, usize) {
319        analyze_expr_inner(expr)
320    }
321
322    fn detect_hot_paths(&mut self, _program: &HirProgram) {
323        // Find functions that consume > 10% of time
324        let hot_functions: Vec<_> = self
325            .metrics
326            .values()
327            .filter(|m| m.is_hot)
328            .map(|m| m.name.clone())
329            .collect();
330
331        // For now, create simple hot paths from hot functions
332        for func_name in hot_functions {
333            if let Some(metrics) = self.metrics.get(&func_name) {
334                self.hot_paths.push(HotPath {
335                    call_chain: vec![func_name],
336                    time_percentage: metrics.time_percentage,
337                    loop_depth: 0, // Would need more analysis
338                    has_io: false, // Would need I/O detection
339                });
340            }
341        }
342    }
343
344    fn generate_predictions(&mut self, program: &HirProgram) {
345        // Type system optimization prediction
346        let type_checks_removed = self.count_type_checks(program);
347        if type_checks_removed > 0 {
348            self.predictions.push(PerformancePrediction {
349                category: PredictionCategory::TypeSystemOptimization,
350                confidence: 0.9,
351                speedup_factor: 1.0 + (type_checks_removed as f64 * 0.1),
352                explanation: format!(
353                    "Rust's type system eliminates {} runtime type checks",
354                    type_checks_removed
355                ),
356                functions: vec![],
357            });
358        }
359
360        // Iterator optimization prediction
361        let iterator_opportunities = self.count_iterator_opportunities(program);
362        if iterator_opportunities > 0 {
363            self.predictions.push(PerformancePrediction {
364                category: PredictionCategory::IteratorOptimization,
365                confidence: 0.8,
366                speedup_factor: 1.2,
367                explanation: "Rust's iterator fusion can optimize chained operations".to_string(),
368                functions: vec![],
369            });
370        }
371
372        // Memory layout optimization
373        self.predictions.push(PerformancePrediction {
374            category: PredictionCategory::MemoryLayoutOptimization,
375            confidence: 0.7,
376            speedup_factor: 1.3,
377            explanation: "Rust's memory layout is more cache-friendly than Python".to_string(),
378            functions: vec![],
379        });
380    }
381
382    fn count_type_checks(&self, program: &HirProgram) -> usize {
383        let mut count = 0;
384        for func in &program.functions {
385            for stmt in &func.body {
386                count += self.count_type_checks_in_stmt(stmt);
387            }
388        }
389        count
390    }
391
392    fn count_type_checks_in_stmt(&self, stmt: &HirStmt) -> usize {
393        match stmt {
394            HirStmt::If {
395                condition,
396                then_body,
397                else_body,
398            } => {
399                let mut count = 0;
400                if self.is_type_check_expr(condition) {
401                    count += 1;
402                }
403                for s in then_body {
404                    count += self.count_type_checks_in_stmt(s);
405                }
406                if let Some(else_stmts) = else_body {
407                    for s in else_stmts {
408                        count += self.count_type_checks_in_stmt(s);
409                    }
410                }
411                count
412            }
413            _ => 0,
414        }
415    }
416
417    fn is_type_check_expr(&self, expr: &HirExpr) -> bool {
418        if let HirExpr::Call { func, .. } = expr {
419            func == "isinstance" || func == "type"
420        } else {
421            false
422        }
423    }
424
425    fn count_iterator_opportunities(&self, program: &HirProgram) -> usize {
426        let mut count = 0;
427        for func in &program.functions {
428            for stmt in &func.body {
429                if matches!(stmt, HirStmt::For { .. }) {
430                    count += 1;
431                }
432            }
433        }
434        count
435    }
436
437    fn generate_annotations(&self) -> Vec<ProfilingAnnotation> {
438        let mut annotations = Vec::new();
439
440        // Add timing probes for hot functions
441        for (name, metrics) in &self.metrics {
442            if metrics.is_hot {
443                annotations.push(ProfilingAnnotation {
444                    kind: AnnotationKind::TimingProbe,
445                    target: name.clone(),
446                    value: format!("hot_function_{}", name),
447                });
448            }
449        }
450
451        // Add allocation counters for functions with high allocation
452        for (name, metrics) in &self.metrics {
453            if metrics.allocation_count > 10 {
454                annotations.push(ProfilingAnnotation {
455                    kind: AnnotationKind::AllocationCounter,
456                    target: name.clone(),
457                    value: format!("alloc_count_{}", metrics.allocation_count),
458                });
459            }
460        }
461
462        annotations
463    }
464}
465
466/// Profiling report containing all analysis results
467#[derive(Debug, Clone)]
468pub struct ProfilingReport {
469    /// Function-level metrics
470    pub metrics: HashMap<String, FunctionMetrics>,
471    /// Detected hot paths
472    pub hot_paths: Vec<HotPath>,
473    /// Performance predictions
474    pub predictions: Vec<PerformancePrediction>,
475    /// Total instruction count estimate
476    pub total_instructions: usize,
477    /// Total allocation count estimate
478    pub total_allocations: usize,
479    /// Profiling annotations for code generation
480    pub annotations: Vec<ProfilingAnnotation>,
481}
482
483impl ProfilingReport {
484    /// Format the report for display
485    pub fn format_report(&self) -> String {
486        let mut output = String::new();
487        self.format_header(&mut output);
488        self.format_summary(&mut output);
489        self.format_hot_paths(&mut output);
490        self.format_function_metrics(&mut output);
491        self.format_predictions(&mut output);
492        self.format_overall_speedup(&mut output);
493        output
494    }
495
496    fn format_header(&self, output: &mut String) {
497        output.push_str(&format!("\n{}\n", "Profiling Report".bold().blue()));
498        output.push_str(&format!("{}\n\n", "═".repeat(50)));
499    }
500
501    fn format_summary(&self, output: &mut String) {
502        output.push_str(&format!("{}\n", "Summary".bold()));
503        output.push_str(&format!(
504            "  Total estimated instructions: {}\n",
505            self.total_instructions.to_string().yellow()
506        ));
507        output.push_str(&format!(
508            "  Total estimated allocations: {}\n",
509            self.total_allocations.to_string().yellow()
510        ));
511        output.push_str(&format!(
512            "  Functions analyzed: {}\n\n",
513            self.metrics.len().to_string().yellow()
514        ));
515    }
516
517    fn format_hot_paths(&self, output: &mut String) {
518        if self.hot_paths.is_empty() {
519            return;
520        }
521        output.push_str(&format!("{}\n", "Hot Paths".bold().red()));
522        for (idx, path) in self.hot_paths.iter().enumerate() {
523            output.push_str(&format!(
524                "  [{}] {} ({:.1}% of execution time)\n",
525                idx + 1,
526                path.call_chain.join(" → "),
527                path.time_percentage
528            ));
529        }
530        output.push('\n');
531    }
532
533    fn format_function_metrics(&self, output: &mut String) {
534        output.push_str(&format!("{}\n", "Function Metrics".bold()));
535        let mut sorted_metrics: Vec<_> = self.metrics.values().collect();
536        sorted_metrics.sort_by(|a, b| {
537            b.time_percentage
538                .partial_cmp(&a.time_percentage)
539                .unwrap_or(std::cmp::Ordering::Equal)
540        });
541
542        for metrics in sorted_metrics.iter().take(10) {
543            let hot_marker = if metrics.is_hot { "🔥" } else { "  " };
544            output.push_str(&format!(
545                "{} {:<30} {:>6.1}% time | {:>6} inst | {:>4} alloc\n",
546                hot_marker,
547                metrics.name,
548                metrics.time_percentage,
549                metrics.instruction_count,
550                metrics.allocation_count
551            ));
552        }
553        output.push('\n');
554    }
555
556    fn format_predictions(&self, output: &mut String) {
557        if self.predictions.is_empty() {
558            return;
559        }
560        output.push_str(&format!("{}\n", "Performance Predictions".bold().green()));
561        for pred in &self.predictions {
562            output.push_str(&format!(
563                "  • {} ({}x speedup, {:.0}% confidence)\n",
564                pred.explanation,
565                format!("{:.1}", pred.speedup_factor).green(),
566                pred.confidence * 100.0
567            ));
568        }
569        output.push('\n');
570    }
571
572    fn format_overall_speedup(&self, output: &mut String) {
573        let total_speedup: f64 = self.predictions.iter().map(|p| p.speedup_factor).product();
574        if total_speedup > 1.0 {
575            output.push_str(&format!(
576                "{} Estimated overall speedup: {}x\n",
577                "🚀".green(),
578                format!("{:.1}", total_speedup).bold().green()
579            ));
580        }
581    }
582
583    /// Generate flame graph data in collapsed format
584    pub fn generate_flamegraph_data(&self) -> String {
585        let mut lines = Vec::new();
586
587        for (func_name, metrics) in &self.metrics {
588            // Simple format: function_name sample_count
589            let sample_count = (metrics.time_percentage * 100.0) as usize;
590            if sample_count > 0 {
591                lines.push(format!("{} {}", func_name, sample_count));
592            }
593        }
594
595        lines.join("\n")
596    }
597
598    /// Generate perf-compatible annotations
599    pub fn generate_perf_annotations(&self) -> String {
600        let annotations: Vec<String> = self
601            .annotations
602            .iter()
603            .map(|annotation| self.format_annotation(annotation))
604            .collect();
605        annotations.join("\n")
606    }
607
608    fn format_annotation(&self, annotation: &ProfilingAnnotation) -> String {
609        match annotation.kind {
610            AnnotationKind::TimingProbe => self.format_timing_probe(&annotation.target),
611            AnnotationKind::AllocationCounter => {
612                self.format_allocation_counter(&annotation.target, &annotation.value)
613            }
614            AnnotationKind::HotPathMarker => self.format_hot_path_marker(&annotation.target),
615            AnnotationKind::PerformanceHint => {
616                self.format_performance_hint(&annotation.target, &annotation.value)
617            }
618        }
619    }
620
621    fn format_timing_probe(&self, target: &str) -> String {
622        format!("# @probe {}: timing probe", target)
623    }
624
625    fn format_allocation_counter(&self, target: &str, value: &str) -> String {
626        format!("# @probe {}: allocation counter = {}", target, value)
627    }
628
629    fn format_hot_path_marker(&self, target: &str) -> String {
630        format!("# @hot {}: hot path marker", target)
631    }
632
633    fn format_performance_hint(&self, target: &str, value: &str) -> String {
634        format!("# @hint {}: {}", target, value)
635    }
636}
637
638fn analyze_expr_inner(expr: &HirExpr) -> (usize, usize) {
639    match expr {
640        HirExpr::Literal(_) => (1, 0),
641        HirExpr::Var(_) => (1, 0),
642        HirExpr::Binary { left, right, .. } => analyze_binary_expr(left, right),
643        HirExpr::Call { args, .. } => analyze_call_expr(args),
644        HirExpr::List(items) => analyze_list_expr(items),
645        HirExpr::Dict(pairs) => analyze_dict_expr(pairs),
646        _ => (1, 0),
647    }
648}
649
650fn analyze_binary_expr(left: &HirExpr, right: &HirExpr) -> (usize, usize) {
651    let (l_inst, l_alloc) = analyze_expr_inner(left);
652    let (r_inst, r_alloc) = analyze_expr_inner(right);
653    (l_inst + r_inst + 1, l_alloc + r_alloc)
654}
655
656fn analyze_call_expr(args: &[HirExpr]) -> (usize, usize) {
657    let mut total_inst = 10;
658    let mut total_alloc = 0;
659    for arg in args {
660        let (inst, alloc) = analyze_expr_inner(arg);
661        total_inst += inst;
662        total_alloc += alloc;
663    }
664    (total_inst, total_alloc)
665}
666
667fn analyze_list_expr(items: &[HirExpr]) -> (usize, usize) {
668    let mut total_inst = 1;
669    let total_alloc = 1;
670    for item in items {
671        let (inst, _) = analyze_expr_inner(item);
672        total_inst += inst;
673    }
674    (total_inst, total_alloc)
675}
676
677fn analyze_dict_expr(pairs: &[(HirExpr, HirExpr)]) -> (usize, usize) {
678    let mut total_inst = 1;
679    let total_alloc = 1;
680    for (k, v) in pairs {
681        let (k_inst, _) = analyze_expr_inner(k);
682        let (v_inst, _) = analyze_expr_inner(v);
683        total_inst += k_inst + v_inst + 2;
684    }
685    (total_inst, total_alloc)
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691    use depyler_hir::hir::*;
692    use smallvec::smallvec;
693
694    fn create_test_function(name: &str, body: Vec<HirStmt>) -> HirFunction {
695        HirFunction {
696            name: name.to_string(),
697            params: smallvec![],
698            ret_type: Type::Unknown,
699            body,
700            properties: FunctionProperties::default(),
701            annotations: Default::default(),
702            docstring: None,
703        }
704    }
705
706    // === ProfileConfig tests ===
707
708    #[test]
709    fn test_profile_config_default() {
710        let config = ProfileConfig::default();
711        assert!(config.count_instructions);
712        assert!(config.track_allocations);
713        assert!(config.detect_hot_paths);
714        assert_eq!(config.hot_path_threshold, 100);
715        assert!(!config.generate_flamegraph);
716        assert!(config.include_hints);
717    }
718
719    #[test]
720    fn test_profile_config_clone() {
721        let config = ProfileConfig {
722            count_instructions: false,
723            hot_path_threshold: 50,
724            ..Default::default()
725        };
726        let cloned = config.clone();
727        assert!(!cloned.count_instructions);
728        assert_eq!(cloned.hot_path_threshold, 50);
729    }
730
731    #[test]
732    fn test_profile_config_debug() {
733        let config = ProfileConfig::default();
734        let debug = format!("{:?}", config);
735        assert!(debug.contains("ProfileConfig"));
736    }
737
738    // === FunctionMetrics tests ===
739
740    #[test]
741    fn test_function_metrics_new() {
742        let metrics = FunctionMetrics {
743            name: "test_func".to_string(),
744            instruction_count: 100,
745            allocation_count: 5,
746            estimated_time: 150.0,
747            call_count: 10,
748            time_percentage: 25.0,
749            is_hot: true,
750        };
751        assert_eq!(metrics.name, "test_func");
752        assert!(metrics.is_hot);
753    }
754
755    #[test]
756    fn test_function_metrics_clone() {
757        let metrics = FunctionMetrics {
758            name: "clone_test".to_string(),
759            instruction_count: 50,
760            allocation_count: 2,
761            estimated_time: 75.0,
762            call_count: 5,
763            time_percentage: 10.0,
764            is_hot: false,
765        };
766        let cloned = metrics.clone();
767        assert_eq!(metrics.name, cloned.name);
768    }
769
770    #[test]
771    fn test_function_metrics_debug() {
772        let metrics = FunctionMetrics {
773            name: "debug".to_string(),
774            instruction_count: 0,
775            allocation_count: 0,
776            estimated_time: 0.0,
777            call_count: 0,
778            time_percentage: 0.0,
779            is_hot: false,
780        };
781        let debug = format!("{:?}", metrics);
782        assert!(debug.contains("FunctionMetrics"));
783    }
784
785    // === HotPath tests ===
786
787    #[test]
788    fn test_hot_path_new() {
789        let path = HotPath {
790            call_chain: vec!["main".to_string(), "process".to_string()],
791            time_percentage: 45.0,
792            loop_depth: 2,
793            has_io: true,
794        };
795        assert_eq!(path.call_chain.len(), 2);
796        assert!(path.has_io);
797    }
798
799    #[test]
800    fn test_hot_path_clone() {
801        let path = HotPath {
802            call_chain: vec!["func".to_string()],
803            time_percentage: 30.0,
804            loop_depth: 1,
805            has_io: false,
806        };
807        let cloned = path.clone();
808        assert_eq!(path.time_percentage, cloned.time_percentage);
809    }
810
811    #[test]
812    fn test_hot_path_debug() {
813        let path = HotPath {
814            call_chain: vec![],
815            time_percentage: 0.0,
816            loop_depth: 0,
817            has_io: false,
818        };
819        let debug = format!("{:?}", path);
820        assert!(debug.contains("HotPath"));
821    }
822
823    // === PerformancePrediction tests ===
824
825    #[test]
826    fn test_performance_prediction_new() {
827        let pred = PerformancePrediction {
828            category: PredictionCategory::TypeSystemOptimization,
829            confidence: 0.9,
830            speedup_factor: 2.5,
831            explanation: "Type checks removed".to_string(),
832            functions: vec!["func1".to_string()],
833        };
834        assert_eq!(pred.confidence, 0.9);
835        assert_eq!(pred.speedup_factor, 2.5);
836    }
837
838    #[test]
839    fn test_performance_prediction_clone() {
840        let pred = PerformancePrediction {
841            category: PredictionCategory::IteratorOptimization,
842            confidence: 0.8,
843            speedup_factor: 1.5,
844            explanation: "test".to_string(),
845            functions: vec![],
846        };
847        let cloned = pred.clone();
848        assert_eq!(pred.category, cloned.category);
849    }
850
851    #[test]
852    fn test_performance_prediction_debug() {
853        let pred = PerformancePrediction {
854            category: PredictionCategory::StringOptimization,
855            confidence: 0.5,
856            speedup_factor: 1.0,
857            explanation: "".to_string(),
858            functions: vec![],
859        };
860        let debug = format!("{:?}", pred);
861        assert!(debug.contains("PerformancePrediction"));
862    }
863
864    // === PredictionCategory tests ===
865
866    #[test]
867    fn test_prediction_category_variants() {
868        let categories = [
869            PredictionCategory::TypeSystemOptimization,
870            PredictionCategory::ZeroCostAbstraction,
871            PredictionCategory::MemoryLayoutOptimization,
872            PredictionCategory::IteratorOptimization,
873            PredictionCategory::StringOptimization,
874            PredictionCategory::ParallelizationOpportunity,
875        ];
876        assert_eq!(categories.len(), 6);
877    }
878
879    #[test]
880    fn test_prediction_category_eq() {
881        assert_eq!(
882            PredictionCategory::TypeSystemOptimization,
883            PredictionCategory::TypeSystemOptimization
884        );
885        assert_ne!(
886            PredictionCategory::TypeSystemOptimization,
887            PredictionCategory::StringOptimization
888        );
889    }
890
891    #[test]
892    fn test_prediction_category_clone() {
893        let cat = PredictionCategory::MemoryLayoutOptimization;
894        let cloned = cat.clone();
895        assert_eq!(cat, cloned);
896    }
897
898    #[test]
899    fn test_prediction_category_debug() {
900        let debug = format!("{:?}", PredictionCategory::ZeroCostAbstraction);
901        assert!(debug.contains("ZeroCostAbstraction"));
902    }
903
904    // === ProfilingAnnotation tests ===
905
906    #[test]
907    fn test_profiling_annotation_new() {
908        let annotation = ProfilingAnnotation {
909            kind: AnnotationKind::TimingProbe,
910            target: "func".to_string(),
911            value: "probe_1".to_string(),
912        };
913        assert_eq!(annotation.target, "func");
914    }
915
916    #[test]
917    fn test_profiling_annotation_clone() {
918        let annotation = ProfilingAnnotation {
919            kind: AnnotationKind::AllocationCounter,
920            target: "test".to_string(),
921            value: "100".to_string(),
922        };
923        let cloned = annotation.clone();
924        assert_eq!(annotation.target, cloned.target);
925    }
926
927    #[test]
928    fn test_profiling_annotation_debug() {
929        let annotation = ProfilingAnnotation {
930            kind: AnnotationKind::HotPathMarker,
931            target: "".to_string(),
932            value: "".to_string(),
933        };
934        let debug = format!("{:?}", annotation);
935        assert!(debug.contains("ProfilingAnnotation"));
936    }
937
938    // === AnnotationKind tests ===
939
940    #[test]
941    fn test_annotation_kind_variants() {
942        let kinds = [
943            AnnotationKind::TimingProbe,
944            AnnotationKind::AllocationCounter,
945            AnnotationKind::HotPathMarker,
946            AnnotationKind::PerformanceHint,
947        ];
948        assert_eq!(kinds.len(), 4);
949    }
950
951    #[test]
952    fn test_annotation_kind_clone() {
953        let kind = AnnotationKind::PerformanceHint;
954        let cloned = kind.clone();
955        assert!(matches!(cloned, AnnotationKind::PerformanceHint));
956    }
957
958    #[test]
959    fn test_annotation_kind_debug() {
960        let debug = format!("{:?}", AnnotationKind::TimingProbe);
961        assert!(debug.contains("TimingProbe"));
962    }
963
964    // === Profiler tests ===
965
966    #[test]
967    fn test_profiler_creation() {
968        let config = ProfileConfig::default();
969        let profiler = Profiler::new(config);
970        assert!(profiler.metrics.is_empty());
971        assert!(profiler.hot_paths.is_empty());
972    }
973
974    #[test]
975    fn test_profiler_empty_program() {
976        let mut profiler = Profiler::new(ProfileConfig::default());
977        let program = HirProgram {
978            functions: vec![],
979            classes: vec![],
980            imports: vec![],
981        };
982        let report = profiler.analyze_program(&program);
983        assert!(report.metrics.is_empty());
984        assert_eq!(report.total_instructions, 0);
985    }
986
987    #[test]
988    fn test_simple_function_profiling() {
989        let mut profiler = Profiler::new(ProfileConfig::default());
990
991        let func = create_test_function(
992            "simple",
993            vec![
994                HirStmt::Assign {
995                    target: AssignTarget::Symbol("x".to_string()),
996                    value: HirExpr::Literal(Literal::Int(42)),
997                    type_annotation: None,
998                },
999                HirStmt::Return(Some(HirExpr::Var("x".to_string()))),
1000            ],
1001        );
1002
1003        let program = HirProgram {
1004            functions: vec![func],
1005            classes: vec![],
1006            imports: vec![],
1007        };
1008
1009        let report = profiler.analyze_program(&program);
1010        assert_eq!(report.metrics.len(), 1);
1011        assert!(report.total_instructions > 0);
1012    }
1013
1014    #[test]
1015    fn test_loop_detection_increases_cost() {
1016        let mut profiler = Profiler::new(ProfileConfig::default());
1017
1018        let func = create_test_function(
1019            "with_loop",
1020            vec![HirStmt::For {
1021                target: AssignTarget::Symbol("i".to_string()),
1022                iter: HirExpr::Call {
1023                    func: "range".to_string(),
1024                    args: vec![HirExpr::Literal(Literal::Int(10))],
1025                    kwargs: vec![],
1026                },
1027                body: vec![HirStmt::Expr(HirExpr::Var("i".to_string()))],
1028            }],
1029        );
1030
1031        let program = HirProgram {
1032            functions: vec![func],
1033            classes: vec![],
1034            imports: vec![],
1035        };
1036
1037        let report = profiler.analyze_program(&program);
1038        let metrics = report.metrics.get("with_loop").unwrap();
1039        assert!(metrics.instruction_count > 10); // Loop body executed multiple times
1040    }
1041
1042    #[test]
1043    fn test_while_loop_analysis() {
1044        let mut profiler = Profiler::new(ProfileConfig::default());
1045
1046        let func = create_test_function(
1047            "with_while",
1048            vec![HirStmt::While {
1049                condition: HirExpr::Literal(Literal::Bool(true)),
1050                body: vec![HirStmt::Expr(HirExpr::Var("x".to_string()))],
1051            }],
1052        );
1053
1054        let program = HirProgram {
1055            functions: vec![func],
1056            classes: vec![],
1057            imports: vec![],
1058        };
1059
1060        let report = profiler.analyze_program(&program);
1061        assert!(report.metrics.contains_key("with_while"));
1062    }
1063
1064    #[test]
1065    fn test_if_else_analysis() {
1066        let mut profiler = Profiler::new(ProfileConfig::default());
1067
1068        let func = create_test_function(
1069            "with_if",
1070            vec![HirStmt::If {
1071                condition: HirExpr::Literal(Literal::Bool(true)),
1072                then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
1073                else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(
1074                    0,
1075                ))))]),
1076            }],
1077        );
1078
1079        let program = HirProgram {
1080            functions: vec![func],
1081            classes: vec![],
1082            imports: vec![],
1083        };
1084
1085        let report = profiler.analyze_program(&program);
1086        assert!(report.metrics.contains_key("with_if"));
1087    }
1088
1089    #[test]
1090    fn test_hot_path_detection() {
1091        let mut profiler = Profiler::new(ProfileConfig {
1092            detect_hot_paths: true,
1093            ..Default::default()
1094        });
1095
1096        // Create a function that will be "hot" (high percentage of time)
1097        let func = create_test_function(
1098            "hot_function",
1099            vec![HirStmt::For {
1100                target: AssignTarget::Symbol("i".to_string()),
1101                iter: HirExpr::Call {
1102                    func: "range".to_string(),
1103                    args: vec![HirExpr::Literal(Literal::Int(1000))],
1104                    kwargs: vec![],
1105                },
1106                body: vec![HirStmt::For {
1107                    target: AssignTarget::Symbol("j".to_string()),
1108                    iter: HirExpr::Call {
1109                        func: "range".to_string(),
1110                        args: vec![HirExpr::Literal(Literal::Int(1000))],
1111                        kwargs: vec![],
1112                    },
1113                    body: vec![HirStmt::Expr(HirExpr::Binary {
1114                        op: BinOp::Add,
1115                        left: Box::new(HirExpr::Var("i".to_string())),
1116                        right: Box::new(HirExpr::Var("j".to_string())),
1117                    })],
1118                }],
1119            }],
1120        );
1121
1122        let program = HirProgram {
1123            functions: vec![func],
1124            classes: vec![],
1125            imports: vec![],
1126        };
1127
1128        let report = profiler.analyze_program(&program);
1129        assert!(!report.hot_paths.is_empty());
1130    }
1131
1132    #[test]
1133    fn test_hot_path_disabled() {
1134        let mut profiler = Profiler::new(ProfileConfig {
1135            detect_hot_paths: false,
1136            ..Default::default()
1137        });
1138
1139        let func = create_test_function("test", vec![]);
1140        let program = HirProgram {
1141            functions: vec![func],
1142            classes: vec![],
1143            imports: vec![],
1144        };
1145
1146        let report = profiler.analyze_program(&program);
1147        assert!(report.hot_paths.is_empty());
1148    }
1149
1150    #[test]
1151    fn test_performance_predictions() {
1152        let mut profiler = Profiler::new(ProfileConfig::default());
1153
1154        // Function with type check
1155        let func = create_test_function(
1156            "with_type_check",
1157            vec![HirStmt::If {
1158                condition: HirExpr::Call {
1159                    func: "isinstance".to_string(),
1160                    args: vec![
1161                        HirExpr::Var("x".to_string()),
1162                        HirExpr::Var("int".to_string()),
1163                    ],
1164                    kwargs: vec![],
1165                },
1166                then_body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
1167                else_body: None,
1168            }],
1169        );
1170
1171        let program = HirProgram {
1172            functions: vec![func],
1173            classes: vec![],
1174            imports: vec![],
1175        };
1176
1177        let report = profiler.analyze_program(&program);
1178        assert!(!report.predictions.is_empty());
1179
1180        // Should have type system optimization prediction
1181        assert!(report
1182            .predictions
1183            .iter()
1184            .any(|p| p.category == PredictionCategory::TypeSystemOptimization));
1185    }
1186
1187    #[test]
1188    fn test_iterator_optimization_prediction() {
1189        let mut profiler = Profiler::new(ProfileConfig::default());
1190
1191        let func = create_test_function(
1192            "with_for",
1193            vec![HirStmt::For {
1194                target: AssignTarget::Symbol("i".to_string()),
1195                iter: HirExpr::Var("items".to_string()),
1196                body: vec![HirStmt::Expr(HirExpr::Var("i".to_string()))],
1197            }],
1198        );
1199
1200        let program = HirProgram {
1201            functions: vec![func],
1202            classes: vec![],
1203            imports: vec![],
1204        };
1205
1206        let report = profiler.analyze_program(&program);
1207        assert!(report
1208            .predictions
1209            .iter()
1210            .any(|p| p.category == PredictionCategory::IteratorOptimization));
1211    }
1212
1213    #[test]
1214    fn test_memory_layout_prediction() {
1215        let mut profiler = Profiler::new(ProfileConfig::default());
1216
1217        let func = create_test_function("empty", vec![]);
1218        let program = HirProgram {
1219            functions: vec![func],
1220            classes: vec![],
1221            imports: vec![],
1222        };
1223
1224        let report = profiler.analyze_program(&program);
1225        assert!(report
1226            .predictions
1227            .iter()
1228            .any(|p| p.category == PredictionCategory::MemoryLayoutOptimization));
1229    }
1230
1231    // === ProfilingReport tests ===
1232
1233    #[test]
1234    fn test_report_formatting() {
1235        let mut profiler = Profiler::new(ProfileConfig::default());
1236
1237        let func = create_test_function(
1238            "test",
1239            vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
1240        );
1241
1242        let program = HirProgram {
1243            functions: vec![func],
1244            classes: vec![],
1245            imports: vec![],
1246        };
1247
1248        let report = profiler.analyze_program(&program);
1249        let formatted = report.format_report();
1250
1251        assert!(formatted.contains("Profiling Report"));
1252        assert!(formatted.contains("Summary"));
1253        assert!(formatted.contains("Function Metrics"));
1254    }
1255
1256    #[test]
1257    fn test_report_with_hot_paths() {
1258        let report = ProfilingReport {
1259            metrics: HashMap::new(),
1260            hot_paths: vec![HotPath {
1261                call_chain: vec!["main".to_string(), "process".to_string()],
1262                time_percentage: 50.0,
1263                loop_depth: 1,
1264                has_io: false,
1265            }],
1266            predictions: vec![],
1267            total_instructions: 100,
1268            total_allocations: 10,
1269            annotations: vec![],
1270        };
1271
1272        let formatted = report.format_report();
1273        assert!(formatted.contains("Hot Paths"));
1274        assert!(formatted.contains("main"));
1275    }
1276
1277    #[test]
1278    fn test_report_with_predictions() {
1279        let report = ProfilingReport {
1280            metrics: HashMap::new(),
1281            hot_paths: vec![],
1282            predictions: vec![PerformancePrediction {
1283                category: PredictionCategory::TypeSystemOptimization,
1284                confidence: 0.9,
1285                speedup_factor: 2.0,
1286                explanation: "Type checks removed".to_string(),
1287                functions: vec![],
1288            }],
1289            total_instructions: 100,
1290            total_allocations: 10,
1291            annotations: vec![],
1292        };
1293
1294        let formatted = report.format_report();
1295        assert!(formatted.contains("Performance Predictions"));
1296        assert!(formatted.contains("Type checks removed"));
1297    }
1298
1299    #[test]
1300    fn test_report_clone() {
1301        let report = ProfilingReport {
1302            metrics: HashMap::new(),
1303            hot_paths: vec![],
1304            predictions: vec![],
1305            total_instructions: 50,
1306            total_allocations: 5,
1307            annotations: vec![],
1308        };
1309        let cloned = report.clone();
1310        assert_eq!(report.total_instructions, cloned.total_instructions);
1311    }
1312
1313    #[test]
1314    fn test_report_debug() {
1315        let report = ProfilingReport {
1316            metrics: HashMap::new(),
1317            hot_paths: vec![],
1318            predictions: vec![],
1319            total_instructions: 0,
1320            total_allocations: 0,
1321            annotations: vec![],
1322        };
1323        let debug = format!("{:?}", report);
1324        assert!(debug.contains("ProfilingReport"));
1325    }
1326
1327    #[test]
1328    fn test_generate_flamegraph_data() {
1329        let mut metrics = HashMap::new();
1330        metrics.insert(
1331            "func1".to_string(),
1332            FunctionMetrics {
1333                name: "func1".to_string(),
1334                instruction_count: 100,
1335                allocation_count: 5,
1336                estimated_time: 100.0,
1337                call_count: 10,
1338                time_percentage: 50.0,
1339                is_hot: true,
1340            },
1341        );
1342
1343        let report = ProfilingReport {
1344            metrics,
1345            hot_paths: vec![],
1346            predictions: vec![],
1347            total_instructions: 200,
1348            total_allocations: 10,
1349            annotations: vec![],
1350        };
1351
1352        let flamegraph = report.generate_flamegraph_data();
1353        assert!(flamegraph.contains("func1"));
1354    }
1355
1356    #[test]
1357    fn test_generate_perf_annotations() {
1358        let report = ProfilingReport {
1359            metrics: HashMap::new(),
1360            hot_paths: vec![],
1361            predictions: vec![],
1362            total_instructions: 0,
1363            total_allocations: 0,
1364            annotations: vec![
1365                ProfilingAnnotation {
1366                    kind: AnnotationKind::TimingProbe,
1367                    target: "func1".to_string(),
1368                    value: "probe".to_string(),
1369                },
1370                ProfilingAnnotation {
1371                    kind: AnnotationKind::AllocationCounter,
1372                    target: "func2".to_string(),
1373                    value: "100".to_string(),
1374                },
1375            ],
1376        };
1377
1378        let perf = report.generate_perf_annotations();
1379        assert!(perf.contains("@probe func1"));
1380        assert!(perf.contains("allocation counter"));
1381    }
1382
1383    #[test]
1384    fn test_format_annotation_hot_path() {
1385        let report = ProfilingReport {
1386            metrics: HashMap::new(),
1387            hot_paths: vec![],
1388            predictions: vec![],
1389            total_instructions: 0,
1390            total_allocations: 0,
1391            annotations: vec![],
1392        };
1393
1394        let annotation = ProfilingAnnotation {
1395            kind: AnnotationKind::HotPathMarker,
1396            target: "hot_func".to_string(),
1397            value: "".to_string(),
1398        };
1399
1400        let formatted = report.format_annotation(&annotation);
1401        assert!(formatted.contains("@hot"));
1402        assert!(formatted.contains("hot_func"));
1403    }
1404
1405    #[test]
1406    fn test_format_annotation_performance_hint() {
1407        let report = ProfilingReport {
1408            metrics: HashMap::new(),
1409            hot_paths: vec![],
1410            predictions: vec![],
1411            total_instructions: 0,
1412            total_allocations: 0,
1413            annotations: vec![],
1414        };
1415
1416        let annotation = ProfilingAnnotation {
1417            kind: AnnotationKind::PerformanceHint,
1418            target: "func".to_string(),
1419            value: "Consider caching".to_string(),
1420        };
1421
1422        let formatted = report.format_annotation(&annotation);
1423        assert!(formatted.contains("@hint"));
1424        assert!(formatted.contains("Consider caching"));
1425    }
1426
1427    // === Expression analysis tests ===
1428
1429    #[test]
1430    fn test_analyze_literal() {
1431        let (inst, alloc) = analyze_expr_inner(&HirExpr::Literal(Literal::Int(42)));
1432        assert_eq!(inst, 1);
1433        assert_eq!(alloc, 0);
1434    }
1435
1436    #[test]
1437    fn test_analyze_var() {
1438        let (inst, alloc) = analyze_expr_inner(&HirExpr::Var("x".to_string()));
1439        assert_eq!(inst, 1);
1440        assert_eq!(alloc, 0);
1441    }
1442
1443    #[test]
1444    fn test_analyze_binary() {
1445        let expr = HirExpr::Binary {
1446            op: BinOp::Add,
1447            left: Box::new(HirExpr::Literal(Literal::Int(1))),
1448            right: Box::new(HirExpr::Literal(Literal::Int(2))),
1449        };
1450        let (inst, alloc) = analyze_expr_inner(&expr);
1451        assert_eq!(inst, 3); // 1 + 1 + 1
1452        assert_eq!(alloc, 0);
1453    }
1454
1455    #[test]
1456    fn test_analyze_call() {
1457        let expr = HirExpr::Call {
1458            func: "foo".to_string(),
1459            args: vec![HirExpr::Literal(Literal::Int(1))],
1460            kwargs: vec![],
1461        };
1462        let (inst, alloc) = analyze_expr_inner(&expr);
1463        assert!(inst > 10); // Base cost + arg
1464        assert_eq!(alloc, 0);
1465    }
1466
1467    #[test]
1468    fn test_analyze_list() {
1469        let expr = HirExpr::List(vec![
1470            HirExpr::Literal(Literal::Int(1)),
1471            HirExpr::Literal(Literal::Int(2)),
1472        ]);
1473        let (inst, alloc) = analyze_expr_inner(&expr);
1474        assert!(inst >= 3); // Base + 2 items
1475        assert_eq!(alloc, 1); // List allocation
1476    }
1477
1478    #[test]
1479    fn test_analyze_dict() {
1480        let expr = HirExpr::Dict(vec![(
1481            HirExpr::Literal(Literal::String("key".to_string())),
1482            HirExpr::Literal(Literal::Int(1)),
1483        )]);
1484        let (inst, alloc) = analyze_expr_inner(&expr);
1485        assert!(inst >= 4); // Base + key + value + overhead
1486        assert_eq!(alloc, 1); // Dict allocation
1487    }
1488
1489    // === Multiple functions test ===
1490
1491    #[test]
1492    fn test_multiple_functions() {
1493        let mut profiler = Profiler::new(ProfileConfig::default());
1494
1495        let func1 = create_test_function("func1", vec![HirStmt::Return(None)]);
1496        let func2 = create_test_function(
1497            "func2",
1498            vec![HirStmt::Expr(HirExpr::Literal(Literal::Int(1)))],
1499        );
1500
1501        let program = HirProgram {
1502            functions: vec![func1, func2],
1503            classes: vec![],
1504            imports: vec![],
1505        };
1506
1507        let report = profiler.analyze_program(&program);
1508        assert_eq!(report.metrics.len(), 2);
1509        assert!(report.metrics.contains_key("func1"));
1510        assert!(report.metrics.contains_key("func2"));
1511    }
1512
1513    // === Type check detection tests ===
1514
1515    #[test]
1516    fn test_type_check_isinstance() {
1517        let profiler = Profiler::new(ProfileConfig::default());
1518        let expr = HirExpr::Call {
1519            func: "isinstance".to_string(),
1520            args: vec![],
1521            kwargs: vec![],
1522        };
1523        assert!(profiler.is_type_check_expr(&expr));
1524    }
1525
1526    #[test]
1527    fn test_type_check_type() {
1528        let profiler = Profiler::new(ProfileConfig::default());
1529        let expr = HirExpr::Call {
1530            func: "type".to_string(),
1531            args: vec![],
1532            kwargs: vec![],
1533        };
1534        assert!(profiler.is_type_check_expr(&expr));
1535    }
1536
1537    #[test]
1538    fn test_not_type_check() {
1539        let profiler = Profiler::new(ProfileConfig::default());
1540        let expr = HirExpr::Call {
1541            func: "print".to_string(),
1542            args: vec![],
1543            kwargs: vec![],
1544        };
1545        assert!(!profiler.is_type_check_expr(&expr));
1546    }
1547
1548    #[test]
1549    fn test_not_type_check_non_call() {
1550        let profiler = Profiler::new(ProfileConfig::default());
1551        let expr = HirExpr::Var("isinstance".to_string());
1552        assert!(!profiler.is_type_check_expr(&expr));
1553    }
1554
1555    // === Annotations generation tests ===
1556
1557    #[test]
1558    fn test_generate_annotations_for_hot_function() {
1559        let mut profiler = Profiler::new(ProfileConfig::default());
1560        profiler.metrics.insert(
1561            "hot".to_string(),
1562            FunctionMetrics {
1563                name: "hot".to_string(),
1564                instruction_count: 1000,
1565                allocation_count: 5,
1566                estimated_time: 1000.0,
1567                call_count: 100,
1568                time_percentage: 80.0,
1569                is_hot: true,
1570            },
1571        );
1572
1573        let annotations = profiler.generate_annotations();
1574        assert!(annotations
1575            .iter()
1576            .any(|a| matches!(a.kind, AnnotationKind::TimingProbe)));
1577    }
1578
1579    #[test]
1580    fn test_generate_annotations_for_high_allocation() {
1581        let mut profiler = Profiler::new(ProfileConfig::default());
1582        profiler.metrics.insert(
1583            "alloc_heavy".to_string(),
1584            FunctionMetrics {
1585                name: "alloc_heavy".to_string(),
1586                instruction_count: 100,
1587                allocation_count: 50,
1588                estimated_time: 100.0,
1589                call_count: 10,
1590                time_percentage: 10.0,
1591                is_hot: false,
1592            },
1593        );
1594
1595        let annotations = profiler.generate_annotations();
1596        assert!(annotations
1597            .iter()
1598            .any(|a| matches!(a.kind, AnnotationKind::AllocationCounter)));
1599    }
1600}