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
182 .entry(entry.stage.to_string())
183 .or_insert(0u64) += 1;
184 total_confidence += entry.confidence;
185 }
186
187 TraceSummary {
188 total_decisions: self.entries.len(),
189 avg_confidence: if self.entries.is_empty() {
190 0.0
191 } else {
192 total_confidence / self.entries.len() as f64
193 },
194 decisions_by_stage,
195 }
196 }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct TraceSummary {
202 pub total_decisions: usize,
204 pub avg_confidence: f64,
206 pub decisions_by_stage: std::collections::HashMap<String, u64>,
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_trace_collector_new_is_empty() {
216 let collector = TraceCollector::new();
217 assert!(collector.is_empty());
218 assert_eq!(collector.len(), 0);
219 }
220
221 #[test]
222 fn test_trace_collector_record_entry() {
223 let mut collector = TraceCollector::new();
224 collector.record(TraceEntry {
225 stage: PipelineStage::OwnershipInference,
226 source_location: Some("line 5".to_string()),
227 decision_type: DecisionType::PointerClassification,
228 chosen: "Box<i32>".to_string(),
229 alternatives: vec!["&i32".to_string()],
230 confidence: 0.9,
231 reason: "malloc detected".to_string(),
232 });
233
234 assert_eq!(collector.len(), 1);
235 assert!(!collector.is_empty());
236 assert_eq!(collector.entries()[0].chosen, "Box<i32>");
237 }
238
239 #[test]
240 fn test_trace_collector_to_json() {
241 let mut collector = TraceCollector::new();
242 collector.record(TraceEntry {
243 stage: PipelineStage::CodeGeneration,
244 source_location: None,
245 decision_type: DecisionType::TypeMapping,
246 chosen: "i32".to_string(),
247 alternatives: vec![],
248 confidence: 1.0,
249 reason: "direct mapping".to_string(),
250 });
251
252 let json = collector.to_json();
253 assert!(json.contains("i32"));
254 assert!(json.contains("code_generation"));
255 }
256
257 #[test]
258 fn test_trace_collector_filter_by_stage() {
259 let mut collector = TraceCollector::new();
260 collector.record(TraceEntry {
261 stage: PipelineStage::Parsing,
262 source_location: None,
263 decision_type: DecisionType::TypeMapping,
264 chosen: "int".to_string(),
265 alternatives: vec![],
266 confidence: 1.0,
267 reason: "parsed".to_string(),
268 });
269 collector.record(TraceEntry {
270 stage: PipelineStage::OwnershipInference,
271 source_location: None,
272 decision_type: DecisionType::PointerClassification,
273 chosen: "&i32".to_string(),
274 alternatives: vec![],
275 confidence: 0.8,
276 reason: "read-only".to_string(),
277 });
278
279 let parsing = collector.entries_for_stage(&PipelineStage::Parsing);
280 assert_eq!(parsing.len(), 1);
281
282 let ownership = collector.entries_for_stage(&PipelineStage::OwnershipInference);
283 assert_eq!(ownership.len(), 1);
284 }
285
286 #[test]
287 fn test_trace_summary() {
288 let mut collector = TraceCollector::new();
289 collector.record(TraceEntry {
290 stage: PipelineStage::OwnershipInference,
291 source_location: None,
292 decision_type: DecisionType::PointerClassification,
293 chosen: "Box<i32>".to_string(),
294 alternatives: vec![],
295 confidence: 0.8,
296 reason: "test".to_string(),
297 });
298 collector.record(TraceEntry {
299 stage: PipelineStage::OwnershipInference,
300 source_location: None,
301 decision_type: DecisionType::PointerClassification,
302 chosen: "&i32".to_string(),
303 alternatives: vec![],
304 confidence: 1.0,
305 reason: "test".to_string(),
306 });
307
308 let summary = collector.summary();
309 assert_eq!(summary.total_decisions, 2);
310 assert!((summary.avg_confidence - 0.9).abs() < 0.001);
311 assert_eq!(
312 summary.decisions_by_stage.get("ownership_inference"),
313 Some(&2)
314 );
315 }
316
317 #[test]
322 fn test_pipeline_stage_display_all_variants() {
323 assert_eq!(format!("{}", PipelineStage::Parsing), "parsing");
324 assert_eq!(format!("{}", PipelineStage::HirConversion), "hir_conversion");
325 assert_eq!(
326 format!("{}", PipelineStage::OwnershipInference),
327 "ownership_inference"
328 );
329 assert_eq!(
330 format!("{}", PipelineStage::LifetimeAnalysis),
331 "lifetime_analysis"
332 );
333 assert_eq!(
334 format!("{}", PipelineStage::CodeGeneration),
335 "code_generation"
336 );
337 }
338
339 #[test]
340 fn test_decision_type_display_all_variants() {
341 assert_eq!(
342 format!("{}", DecisionType::PointerClassification),
343 "pointer_classification"
344 );
345 assert_eq!(format!("{}", DecisionType::TypeMapping), "type_mapping");
346 assert_eq!(
347 format!("{}", DecisionType::SafetyTransformation),
348 "safety_transformation"
349 );
350 assert_eq!(
351 format!("{}", DecisionType::LifetimeAnnotation),
352 "lifetime_annotation"
353 );
354 assert_eq!(
355 format!("{}", DecisionType::PatternDetection),
356 "pattern_detection"
357 );
358 assert_eq!(
359 format!("{}", DecisionType::SignatureTransformation),
360 "signature_transformation"
361 );
362 }
363
364 #[test]
369 fn test_trace_summary_empty() {
370 let collector = TraceCollector::new();
371 let summary = collector.summary();
372 assert_eq!(summary.total_decisions, 0);
373 assert_eq!(summary.avg_confidence, 0.0);
374 assert!(summary.decisions_by_stage.is_empty());
375 }
376
377 #[test]
378 fn test_trace_collector_entries_for_stage_no_match() {
379 let mut collector = TraceCollector::new();
380 collector.record(TraceEntry {
381 stage: PipelineStage::Parsing,
382 source_location: None,
383 decision_type: DecisionType::TypeMapping,
384 chosen: "int".to_string(),
385 alternatives: vec![],
386 confidence: 1.0,
387 reason: "test".to_string(),
388 });
389
390 let codegen = collector.entries_for_stage(&PipelineStage::CodeGeneration);
391 assert!(codegen.is_empty());
392 }
393
394 #[test]
395 fn test_trace_collector_to_json_empty() {
396 let collector = TraceCollector::new();
397 let json = collector.to_json();
398 assert_eq!(json, "[]");
399 }
400
401 #[test]
402 fn test_trace_collector_multiple_stages() {
403 let mut collector = TraceCollector::new();
404 collector.record(TraceEntry {
405 stage: PipelineStage::Parsing,
406 source_location: Some("line 1".to_string()),
407 decision_type: DecisionType::TypeMapping,
408 chosen: "i32".to_string(),
409 alternatives: vec!["i64".to_string()],
410 confidence: 0.9,
411 reason: "int maps to i32".to_string(),
412 });
413 collector.record(TraceEntry {
414 stage: PipelineStage::HirConversion,
415 source_location: Some("line 5".to_string()),
416 decision_type: DecisionType::PatternDetection,
417 chosen: "for_loop".to_string(),
418 alternatives: vec!["while_loop".to_string()],
419 confidence: 0.85,
420 reason: "C for → Rust for".to_string(),
421 });
422 collector.record(TraceEntry {
423 stage: PipelineStage::LifetimeAnalysis,
424 source_location: None,
425 decision_type: DecisionType::LifetimeAnnotation,
426 chosen: "'a".to_string(),
427 alternatives: vec!["'static".to_string()],
428 confidence: 0.7,
429 reason: "scope analysis".to_string(),
430 });
431 collector.record(TraceEntry {
432 stage: PipelineStage::CodeGeneration,
433 source_location: Some("line 10".to_string()),
434 decision_type: DecisionType::SafetyTransformation,
435 chosen: "safe_indexing".to_string(),
436 alternatives: vec!["raw_pointer".to_string()],
437 confidence: 0.95,
438 reason: "bounds check possible".to_string(),
439 });
440 collector.record(TraceEntry {
441 stage: PipelineStage::OwnershipInference,
442 source_location: None,
443 decision_type: DecisionType::SignatureTransformation,
444 chosen: "&[i32]".to_string(),
445 alternatives: vec!["*const i32".to_string()],
446 confidence: 0.88,
447 reason: "array param to slice".to_string(),
448 });
449
450 assert_eq!(collector.len(), 5);
451
452 let summary = collector.summary();
453 assert_eq!(summary.total_decisions, 5);
454 assert_eq!(summary.decisions_by_stage.len(), 5);
455 assert_eq!(summary.decisions_by_stage.get("parsing"), Some(&1));
456 assert_eq!(summary.decisions_by_stage.get("hir_conversion"), Some(&1));
457 assert_eq!(summary.decisions_by_stage.get("lifetime_analysis"), Some(&1));
458 assert_eq!(summary.decisions_by_stage.get("code_generation"), Some(&1));
459 assert_eq!(
460 summary.decisions_by_stage.get("ownership_inference"),
461 Some(&1)
462 );
463
464 let json = collector.to_json();
465 assert!(json.contains("parsing"));
466 assert!(json.contains("hir_conversion"));
467 assert!(json.contains("lifetime_analysis"));
468 assert!(json.contains("safety_transformation"));
469 assert!(json.contains("signature_transformation"));
470 }
471
472 #[test]
473 fn test_trace_entry_serialization_roundtrip() {
474 let entry = TraceEntry {
475 stage: PipelineStage::OwnershipInference,
476 source_location: Some("test.c:42:5".to_string()),
477 decision_type: DecisionType::PointerClassification,
478 chosen: "Box<i32>".to_string(),
479 alternatives: vec!["&i32".to_string(), "&mut i32".to_string()],
480 confidence: 0.92,
481 reason: "single_alloc_single_free_pattern".to_string(),
482 };
483
484 let json = serde_json::to_string(&entry).unwrap();
485 let deserialized: TraceEntry = serde_json::from_str(&json).unwrap();
486 assert_eq!(deserialized.chosen, "Box<i32>");
487 assert_eq!(deserialized.alternatives.len(), 2);
488 assert_eq!(deserialized.confidence, 0.92);
489 }
490
491 #[test]
492 fn test_trace_summary_serialization() {
493 let mut collector = TraceCollector::new();
494 collector.record(TraceEntry {
495 stage: PipelineStage::Parsing,
496 source_location: None,
497 decision_type: DecisionType::TypeMapping,
498 chosen: "i32".to_string(),
499 alternatives: vec![],
500 confidence: 1.0,
501 reason: "test".to_string(),
502 });
503
504 let summary = collector.summary();
505 let json = serde_json::to_string(&summary).unwrap();
506 assert!(json.contains("total_decisions"));
507 assert!(json.contains("avg_confidence"));
508 }
509}