1use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum PipelineStage {
31 Parsing,
33 HirConversion,
35 OwnershipInference,
37 LifetimeAnalysis,
39 CodeGeneration,
41}
42
43impl std::fmt::Display for PipelineStage {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 PipelineStage::Parsing => write!(f, "parsing"),
47 PipelineStage::HirConversion => write!(f, "hir_conversion"),
48 PipelineStage::OwnershipInference => write!(f, "ownership_inference"),
49 PipelineStage::LifetimeAnalysis => write!(f, "lifetime_analysis"),
50 PipelineStage::CodeGeneration => write!(f, "code_generation"),
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum DecisionType {
59 PointerClassification,
61 TypeMapping,
63 SafetyTransformation,
65 LifetimeAnnotation,
67 PatternDetection,
69 SignatureTransformation,
71}
72
73impl std::fmt::Display for DecisionType {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 DecisionType::PointerClassification => write!(f, "pointer_classification"),
77 DecisionType::TypeMapping => write!(f, "type_mapping"),
78 DecisionType::SafetyTransformation => write!(f, "safety_transformation"),
79 DecisionType::LifetimeAnnotation => write!(f, "lifetime_annotation"),
80 DecisionType::PatternDetection => write!(f, "pattern_detection"),
81 DecisionType::SignatureTransformation => write!(f, "signature_transformation"),
82 }
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct TraceEntry {
92 pub stage: PipelineStage,
94 pub source_location: Option<String>,
96 pub decision_type: DecisionType,
98 pub chosen: String,
100 pub alternatives: Vec<String>,
102 pub confidence: f64,
104 pub reason: String,
106}
107
108#[derive(Debug, Clone, Default)]
135pub struct TraceCollector {
136 entries: Vec<TraceEntry>,
137}
138
139impl TraceCollector {
140 pub fn new() -> Self {
142 Self::default()
143 }
144
145 pub fn record(&mut self, entry: TraceEntry) {
147 self.entries.push(entry);
148 }
149
150 pub fn entries(&self) -> &[TraceEntry] {
152 &self.entries
153 }
154
155 pub fn len(&self) -> usize {
157 self.entries.len()
158 }
159
160 pub fn is_empty(&self) -> bool {
162 self.entries.is_empty()
163 }
164
165 pub fn to_json(&self) -> String {
167 serde_json::to_string_pretty(&self.entries).unwrap_or_else(|_| "[]".to_string())
168 }
169
170 pub fn entries_for_stage(&self, stage: &PipelineStage) -> Vec<&TraceEntry> {
172 self.entries.iter().filter(|e| &e.stage == stage).collect()
173 }
174
175 pub fn summary(&self) -> TraceSummary {
177 let mut decisions_by_stage = std::collections::HashMap::new();
178 let mut total_confidence = 0.0;
179
180 for entry in &self.entries {
181 *decisions_by_stage.entry(entry.stage.to_string()).or_insert(0u64) += 1;
182 total_confidence += entry.confidence;
183 }
184
185 TraceSummary {
186 total_decisions: self.entries.len(),
187 avg_confidence: if self.entries.is_empty() {
188 0.0
189 } else {
190 total_confidence / self.entries.len() as f64
191 },
192 decisions_by_stage,
193 }
194 }
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct TraceSummary {
200 pub total_decisions: usize,
202 pub avg_confidence: f64,
204 pub decisions_by_stage: std::collections::HashMap<String, u64>,
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_trace_collector_new_is_empty() {
214 let collector = TraceCollector::new();
215 assert!(collector.is_empty());
216 assert_eq!(collector.len(), 0);
217 }
218
219 #[test]
220 fn test_trace_collector_record_entry() {
221 let mut collector = TraceCollector::new();
222 collector.record(TraceEntry {
223 stage: PipelineStage::OwnershipInference,
224 source_location: Some("line 5".to_string()),
225 decision_type: DecisionType::PointerClassification,
226 chosen: "Box<i32>".to_string(),
227 alternatives: vec!["&i32".to_string()],
228 confidence: 0.9,
229 reason: "malloc detected".to_string(),
230 });
231
232 assert_eq!(collector.len(), 1);
233 assert!(!collector.is_empty());
234 assert_eq!(collector.entries()[0].chosen, "Box<i32>");
235 }
236
237 #[test]
238 fn test_trace_collector_to_json() {
239 let mut collector = TraceCollector::new();
240 collector.record(TraceEntry {
241 stage: PipelineStage::CodeGeneration,
242 source_location: None,
243 decision_type: DecisionType::TypeMapping,
244 chosen: "i32".to_string(),
245 alternatives: vec![],
246 confidence: 1.0,
247 reason: "direct mapping".to_string(),
248 });
249
250 let json = collector.to_json();
251 assert!(json.contains("i32"));
252 assert!(json.contains("code_generation"));
253 }
254
255 #[test]
256 fn test_trace_collector_filter_by_stage() {
257 let mut collector = TraceCollector::new();
258 collector.record(TraceEntry {
259 stage: PipelineStage::Parsing,
260 source_location: None,
261 decision_type: DecisionType::TypeMapping,
262 chosen: "int".to_string(),
263 alternatives: vec![],
264 confidence: 1.0,
265 reason: "parsed".to_string(),
266 });
267 collector.record(TraceEntry {
268 stage: PipelineStage::OwnershipInference,
269 source_location: None,
270 decision_type: DecisionType::PointerClassification,
271 chosen: "&i32".to_string(),
272 alternatives: vec![],
273 confidence: 0.8,
274 reason: "read-only".to_string(),
275 });
276
277 let parsing = collector.entries_for_stage(&PipelineStage::Parsing);
278 assert_eq!(parsing.len(), 1);
279
280 let ownership = collector.entries_for_stage(&PipelineStage::OwnershipInference);
281 assert_eq!(ownership.len(), 1);
282 }
283
284 #[test]
285 fn test_trace_summary() {
286 let mut collector = TraceCollector::new();
287 collector.record(TraceEntry {
288 stage: PipelineStage::OwnershipInference,
289 source_location: None,
290 decision_type: DecisionType::PointerClassification,
291 chosen: "Box<i32>".to_string(),
292 alternatives: vec![],
293 confidence: 0.8,
294 reason: "test".to_string(),
295 });
296 collector.record(TraceEntry {
297 stage: PipelineStage::OwnershipInference,
298 source_location: None,
299 decision_type: DecisionType::PointerClassification,
300 chosen: "&i32".to_string(),
301 alternatives: vec![],
302 confidence: 1.0,
303 reason: "test".to_string(),
304 });
305
306 let summary = collector.summary();
307 assert_eq!(summary.total_decisions, 2);
308 assert!((summary.avg_confidence - 0.9).abs() < 0.001);
309 assert_eq!(summary.decisions_by_stage.get("ownership_inference"), Some(&2));
310 }
311
312 #[test]
317 fn test_pipeline_stage_display_all_variants() {
318 assert_eq!(format!("{}", PipelineStage::Parsing), "parsing");
319 assert_eq!(format!("{}", PipelineStage::HirConversion), "hir_conversion");
320 assert_eq!(format!("{}", PipelineStage::OwnershipInference), "ownership_inference");
321 assert_eq!(format!("{}", PipelineStage::LifetimeAnalysis), "lifetime_analysis");
322 assert_eq!(format!("{}", PipelineStage::CodeGeneration), "code_generation");
323 }
324
325 #[test]
326 fn test_decision_type_display_all_variants() {
327 assert_eq!(format!("{}", DecisionType::PointerClassification), "pointer_classification");
328 assert_eq!(format!("{}", DecisionType::TypeMapping), "type_mapping");
329 assert_eq!(format!("{}", DecisionType::SafetyTransformation), "safety_transformation");
330 assert_eq!(format!("{}", DecisionType::LifetimeAnnotation), "lifetime_annotation");
331 assert_eq!(format!("{}", DecisionType::PatternDetection), "pattern_detection");
332 assert_eq!(
333 format!("{}", DecisionType::SignatureTransformation),
334 "signature_transformation"
335 );
336 }
337
338 #[test]
343 fn test_trace_summary_empty() {
344 let collector = TraceCollector::new();
345 let summary = collector.summary();
346 assert_eq!(summary.total_decisions, 0);
347 assert_eq!(summary.avg_confidence, 0.0);
348 assert!(summary.decisions_by_stage.is_empty());
349 }
350
351 #[test]
352 fn test_trace_collector_entries_for_stage_no_match() {
353 let mut collector = TraceCollector::new();
354 collector.record(TraceEntry {
355 stage: PipelineStage::Parsing,
356 source_location: None,
357 decision_type: DecisionType::TypeMapping,
358 chosen: "int".to_string(),
359 alternatives: vec![],
360 confidence: 1.0,
361 reason: "test".to_string(),
362 });
363
364 let codegen = collector.entries_for_stage(&PipelineStage::CodeGeneration);
365 assert!(codegen.is_empty());
366 }
367
368 #[test]
369 fn test_trace_collector_to_json_empty() {
370 let collector = TraceCollector::new();
371 let json = collector.to_json();
372 assert_eq!(json, "[]");
373 }
374
375 #[test]
376 fn test_trace_collector_multiple_stages() {
377 let mut collector = TraceCollector::new();
378 collector.record(TraceEntry {
379 stage: PipelineStage::Parsing,
380 source_location: Some("line 1".to_string()),
381 decision_type: DecisionType::TypeMapping,
382 chosen: "i32".to_string(),
383 alternatives: vec!["i64".to_string()],
384 confidence: 0.9,
385 reason: "int maps to i32".to_string(),
386 });
387 collector.record(TraceEntry {
388 stage: PipelineStage::HirConversion,
389 source_location: Some("line 5".to_string()),
390 decision_type: DecisionType::PatternDetection,
391 chosen: "for_loop".to_string(),
392 alternatives: vec!["while_loop".to_string()],
393 confidence: 0.85,
394 reason: "C for → Rust for".to_string(),
395 });
396 collector.record(TraceEntry {
397 stage: PipelineStage::LifetimeAnalysis,
398 source_location: None,
399 decision_type: DecisionType::LifetimeAnnotation,
400 chosen: "'a".to_string(),
401 alternatives: vec!["'static".to_string()],
402 confidence: 0.7,
403 reason: "scope analysis".to_string(),
404 });
405 collector.record(TraceEntry {
406 stage: PipelineStage::CodeGeneration,
407 source_location: Some("line 10".to_string()),
408 decision_type: DecisionType::SafetyTransformation,
409 chosen: "safe_indexing".to_string(),
410 alternatives: vec!["raw_pointer".to_string()],
411 confidence: 0.95,
412 reason: "bounds check possible".to_string(),
413 });
414 collector.record(TraceEntry {
415 stage: PipelineStage::OwnershipInference,
416 source_location: None,
417 decision_type: DecisionType::SignatureTransformation,
418 chosen: "&[i32]".to_string(),
419 alternatives: vec!["*const i32".to_string()],
420 confidence: 0.88,
421 reason: "array param to slice".to_string(),
422 });
423
424 assert_eq!(collector.len(), 5);
425
426 let summary = collector.summary();
427 assert_eq!(summary.total_decisions, 5);
428 assert_eq!(summary.decisions_by_stage.len(), 5);
429 assert_eq!(summary.decisions_by_stage.get("parsing"), Some(&1));
430 assert_eq!(summary.decisions_by_stage.get("hir_conversion"), Some(&1));
431 assert_eq!(summary.decisions_by_stage.get("lifetime_analysis"), Some(&1));
432 assert_eq!(summary.decisions_by_stage.get("code_generation"), Some(&1));
433 assert_eq!(summary.decisions_by_stage.get("ownership_inference"), Some(&1));
434
435 let json = collector.to_json();
436 assert!(json.contains("parsing"));
437 assert!(json.contains("hir_conversion"));
438 assert!(json.contains("lifetime_analysis"));
439 assert!(json.contains("safety_transformation"));
440 assert!(json.contains("signature_transformation"));
441 }
442
443 #[test]
444 fn test_trace_entry_serialization_roundtrip() {
445 let entry = TraceEntry {
446 stage: PipelineStage::OwnershipInference,
447 source_location: Some("test.c:42:5".to_string()),
448 decision_type: DecisionType::PointerClassification,
449 chosen: "Box<i32>".to_string(),
450 alternatives: vec!["&i32".to_string(), "&mut i32".to_string()],
451 confidence: 0.92,
452 reason: "single_alloc_single_free_pattern".to_string(),
453 };
454
455 let json = serde_json::to_string(&entry).unwrap();
456 let deserialized: TraceEntry = serde_json::from_str(&json).unwrap();
457 assert_eq!(deserialized.chosen, "Box<i32>");
458 assert_eq!(deserialized.alternatives.len(), 2);
459 assert_eq!(deserialized.confidence, 0.92);
460 }
461
462 #[test]
463 fn test_trace_summary_serialization() {
464 let mut collector = TraceCollector::new();
465 collector.record(TraceEntry {
466 stage: PipelineStage::Parsing,
467 source_location: None,
468 decision_type: DecisionType::TypeMapping,
469 chosen: "i32".to_string(),
470 alternatives: vec![],
471 confidence: 1.0,
472 reason: "test".to_string(),
473 });
474
475 let summary = collector.summary();
476 let json = serde_json::to_string(&summary).unwrap();
477 assert!(json.contains("total_decisions"));
478 assert!(json.contains("avg_confidence"));
479 }
480}