Skip to main content

decy_ownership/
ml_features.rs

1//! ML-enhanced ownership inference features.
2//!
3//! DECY-ML-001: OwnershipFeatures struct for ML-based classification
4//! DECY-ML-003: Ownership defect taxonomy (8 categories)
5//!
6//! Based on:
7//! - Type4Py (ICSE 2022): Similarity learning for type inference
8//! - Typilus (PLDI 2020): GNN-based type hints with data flow
9//! - OIP: Hybrid classification with confidence fallback
10
11use serde::{Deserialize, Serialize};
12use std::fmt;
13
14// ============================================================================
15// DECY-ML-003: OWNERSHIP DEFECT TAXONOMY
16// ============================================================================
17
18/// Severity level for ownership defects.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
20pub enum Severity {
21    /// Informational - optimization opportunity
22    Info,
23    /// Medium - suboptimal but safe
24    Medium,
25    /// High - incorrect but may compile
26    High,
27    /// Critical - causes memory unsafety
28    Critical,
29}
30
31/// Ownership inference defect categories.
32///
33/// Based on OIP's 18-category system, focused on 8 transpiler-specific
34/// categories for C→Rust ownership inference.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
36pub enum OwnershipDefect {
37    /// DECY-O-001: Owning pointer classified as borrowing or vice versa
38    PointerMisclassification,
39    /// DECY-O-002: Missing or incorrect lifetime annotations
40    LifetimeInferenceGap,
41    /// DECY-O-003: Use-after-free pattern not caught
42    DanglingPointerRisk,
43    /// DECY-O-004: Multiple mutable aliases generated
44    AliasViolation,
45    /// DECY-O-005: Unnecessary unsafe blocks in output
46    UnsafeMinimizationFailure,
47    /// DECY-O-006: Array vs slice semantics error
48    ArraySliceMismatch,
49    /// DECY-O-007: Allocation without corresponding deallocation
50    ResourceLeakPattern,
51    /// DECY-O-008: Const pointer vs mutable reference error
52    MutabilityMismatch,
53}
54
55impl OwnershipDefect {
56    /// Get the defect code (DECY-O-XXX format).
57    pub fn code(&self) -> &'static str {
58        match self {
59            Self::PointerMisclassification => "DECY-O-001",
60            Self::LifetimeInferenceGap => "DECY-O-002",
61            Self::DanglingPointerRisk => "DECY-O-003",
62            Self::AliasViolation => "DECY-O-004",
63            Self::UnsafeMinimizationFailure => "DECY-O-005",
64            Self::ArraySliceMismatch => "DECY-O-006",
65            Self::ResourceLeakPattern => "DECY-O-007",
66            Self::MutabilityMismatch => "DECY-O-008",
67        }
68    }
69
70    /// Get human-readable description.
71    pub fn description(&self) -> &'static str {
72        match self {
73            Self::PointerMisclassification => {
74                "Owning pointer classified as borrowing or vice versa"
75            }
76            Self::LifetimeInferenceGap => "Missing or incorrect lifetime annotations",
77            Self::DanglingPointerRisk => "Use-after-free pattern not caught",
78            Self::AliasViolation => "Multiple mutable aliases generated",
79            Self::UnsafeMinimizationFailure => "Unnecessary unsafe blocks in output",
80            Self::ArraySliceMismatch => "Array vs slice semantics error",
81            Self::ResourceLeakPattern => "Allocation without corresponding deallocation",
82            Self::MutabilityMismatch => "Const pointer vs mutable reference error",
83        }
84    }
85
86    /// Get severity level for this defect.
87    pub fn severity(&self) -> Severity {
88        match self {
89            // Critical: memory safety violations
90            Self::DanglingPointerRisk | Self::AliasViolation => Severity::Critical,
91            // High: incorrect but may compile
92            Self::PointerMisclassification
93            | Self::LifetimeInferenceGap
94            | Self::MutabilityMismatch => Severity::High,
95            // Medium: suboptimal but safe
96            Self::UnsafeMinimizationFailure
97            | Self::ArraySliceMismatch
98            | Self::ResourceLeakPattern => Severity::Medium,
99        }
100    }
101
102    /// Parse defect from code string.
103    pub fn from_code(code: &str) -> Option<Self> {
104        match code {
105            "DECY-O-001" => Some(Self::PointerMisclassification),
106            "DECY-O-002" => Some(Self::LifetimeInferenceGap),
107            "DECY-O-003" => Some(Self::DanglingPointerRisk),
108            "DECY-O-004" => Some(Self::AliasViolation),
109            "DECY-O-005" => Some(Self::UnsafeMinimizationFailure),
110            "DECY-O-006" => Some(Self::ArraySliceMismatch),
111            "DECY-O-007" => Some(Self::ResourceLeakPattern),
112            "DECY-O-008" => Some(Self::MutabilityMismatch),
113            _ => None,
114        }
115    }
116}
117
118impl fmt::Display for OwnershipDefect {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(f, "[{}] {}", self.code(), self.description())
121    }
122}
123
124// ============================================================================
125// DECY-ML-001: OWNERSHIP FEATURES STRUCT
126// ============================================================================
127
128/// Kind of memory allocation site.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
130pub enum AllocationKind {
131    /// malloc() call
132    Malloc,
133    /// calloc() call
134    Calloc,
135    /// realloc() call
136    Realloc,
137    /// Stack allocation (local variable)
138    Stack,
139    /// Static/global allocation
140    Static,
141    /// Function parameter (externally provided)
142    Parameter,
143    /// Unknown allocation source
144    #[default]
145    Unknown,
146}
147
148/// Features for ML-based ownership classification.
149///
150/// 142-dimension feature vector for batch processing, following:
151/// - Type4Py: Similarity learning approach
152/// - Typilus: Data flow integration
153#[derive(Debug, Clone, Default, Serialize, Deserialize)]
154pub struct OwnershipFeatures {
155    // Syntactic features (4)
156    /// Pointer indirection depth (int*, int**, etc.)
157    pub pointer_depth: u8,
158    /// const qualifier present
159    pub is_const: bool,
160    /// Array decay pattern (T[] → T* parameter)
161    pub is_array_decay: bool,
162    /// Has accompanying size parameter (T* arr, size_t n)
163    pub has_size_param: bool,
164
165    // Semantic features from dataflow (4)
166    /// How the memory was allocated
167    pub allocation_site: AllocationKind,
168    /// Number of free() calls on this pointer
169    pub deallocation_count: u8,
170    /// Number of aliases to this pointer
171    pub alias_count: u8,
172    /// Whether pointer escapes function scope
173    pub escape_scope: bool,
174
175    // Usage pattern features (4)
176    /// Dereference for read count
177    pub read_count: u32,
178    /// Dereference for write count
179    pub write_count: u32,
180    /// Pointer arithmetic operations (p++, p+n)
181    pub arithmetic_ops: u8,
182    /// Null check patterns (if (p != NULL))
183    pub null_checks: u8,
184}
185
186impl OwnershipFeatures {
187    /// Fixed dimension for batch ML processing.
188    /// Syntactic(4) + Semantic(4) + Usage(4) = 12 core features
189    /// + padding for embeddings = 142 total
190    pub const DIMENSION: usize = 142;
191
192    /// Create a new builder for OwnershipFeatures.
193    pub fn builder() -> OwnershipFeaturesBuilder {
194        OwnershipFeaturesBuilder::default()
195    }
196
197    /// Convert features to a flat f32 vector for ML input.
198    pub fn to_vector(&self) -> Vec<f32> {
199        let mut vec = Vec::with_capacity(Self::DIMENSION);
200
201        // Syntactic features
202        vec.push(self.pointer_depth as f32);
203        vec.push(if self.is_const { 1.0 } else { 0.0 });
204        vec.push(if self.is_array_decay { 1.0 } else { 0.0 });
205        vec.push(if self.has_size_param { 1.0 } else { 0.0 });
206
207        // Semantic features
208        vec.push(self.allocation_kind_to_f32());
209        vec.push(self.deallocation_count as f32);
210        vec.push(self.alias_count as f32);
211        vec.push(if self.escape_scope { 1.0 } else { 0.0 });
212
213        // Usage patterns
214        vec.push(self.read_count as f32);
215        vec.push(self.write_count as f32);
216        vec.push(self.arithmetic_ops as f32);
217        vec.push(self.null_checks as f32);
218
219        // Pad to DIMENSION (reserved for embeddings in future)
220        vec.resize(Self::DIMENSION, 0.0);
221
222        vec
223    }
224
225    fn allocation_kind_to_f32(&self) -> f32 {
226        match self.allocation_site {
227            AllocationKind::Malloc => 1.0,
228            AllocationKind::Calloc => 2.0,
229            AllocationKind::Realloc => 3.0,
230            AllocationKind::Stack => 4.0,
231            AllocationKind::Static => 5.0,
232            AllocationKind::Parameter => 6.0,
233            AllocationKind::Unknown => 0.0,
234        }
235    }
236}
237
238/// Builder for OwnershipFeatures.
239#[derive(Debug, Default)]
240pub struct OwnershipFeaturesBuilder {
241    features: OwnershipFeatures,
242}
243
244impl OwnershipFeaturesBuilder {
245    /// Set pointer depth.
246    pub fn pointer_depth(mut self, depth: u8) -> Self {
247        self.features.pointer_depth = depth;
248        self
249    }
250
251    /// Set const qualifier.
252    pub fn const_qualified(mut self, is_const: bool) -> Self {
253        self.features.is_const = is_const;
254        self
255    }
256
257    /// Set array decay flag.
258    pub fn array_decay(mut self, is_decay: bool) -> Self {
259        self.features.is_array_decay = is_decay;
260        self
261    }
262
263    /// Set size parameter flag.
264    pub fn has_size_param(mut self, has_size: bool) -> Self {
265        self.features.has_size_param = has_size;
266        self
267    }
268
269    /// Set allocation site.
270    pub fn allocation_site(mut self, kind: AllocationKind) -> Self {
271        self.features.allocation_site = kind;
272        self
273    }
274
275    /// Set deallocation count.
276    pub fn deallocation_count(mut self, count: u8) -> Self {
277        self.features.deallocation_count = count;
278        self
279    }
280
281    /// Set alias count.
282    pub fn alias_count(mut self, count: u8) -> Self {
283        self.features.alias_count = count;
284        self
285    }
286
287    /// Set escape scope flag.
288    pub fn escape_scope(mut self, escapes: bool) -> Self {
289        self.features.escape_scope = escapes;
290        self
291    }
292
293    /// Set read count.
294    pub fn read_count(mut self, count: u32) -> Self {
295        self.features.read_count = count;
296        self
297    }
298
299    /// Set write count.
300    pub fn write_count(mut self, count: u32) -> Self {
301        self.features.write_count = count;
302        self
303    }
304
305    /// Set arithmetic operations count.
306    pub fn arithmetic_ops(mut self, count: u8) -> Self {
307        self.features.arithmetic_ops = count;
308        self
309    }
310
311    /// Set null checks count.
312    pub fn null_checks(mut self, count: u8) -> Self {
313        self.features.null_checks = count;
314        self
315    }
316
317    /// Build the OwnershipFeatures.
318    pub fn build(self) -> OwnershipFeatures {
319        self.features
320    }
321}
322
323// ============================================================================
324// INFERRED OWNERSHIP KIND
325// ============================================================================
326
327/// Inferred Rust ownership kind from C pointer analysis.
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
329pub enum InferredOwnership {
330    /// Box<T> - owned heap allocation
331    Owned,
332    /// &T - immutable borrow
333    Borrowed,
334    /// &mut T - mutable borrow
335    BorrowedMut,
336    /// Rc<T> or Arc<T> - shared ownership
337    Shared,
338    /// *const T or *mut T - raw pointer (requires unsafe)
339    RawPointer,
340    /// Vec<T> - owned dynamic array
341    Vec,
342    /// &[T] - immutable slice
343    Slice,
344    /// &mut [T] - mutable slice
345    SliceMut,
346}
347
348impl InferredOwnership {
349    /// Convert to Rust type string.
350    pub fn to_rust_type(self, inner_type: &str) -> String {
351        match self {
352            Self::Owned => format!("Box<{}>", inner_type),
353            Self::Borrowed => format!("&{}", inner_type),
354            Self::BorrowedMut => format!("&mut {}", inner_type),
355            Self::Shared => format!("Rc<{}>", inner_type),
356            Self::RawPointer => format!("*const {}", inner_type),
357            Self::Vec => format!("Vec<{}>", inner_type),
358            Self::Slice => format!("&[{}]", inner_type),
359            Self::SliceMut => format!("&mut [{}]", inner_type),
360        }
361    }
362
363    /// Whether this ownership kind requires unsafe code.
364    pub fn requires_unsafe(&self) -> bool {
365        matches!(self, Self::RawPointer)
366    }
367}
368
369/// Ownership prediction with confidence score.
370#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct OwnershipPrediction {
372    /// Predicted ownership kind
373    pub kind: InferredOwnership,
374    /// Confidence score (0.0 - 1.0)
375    pub confidence: f32,
376    /// Fallback if confidence is below threshold
377    pub fallback: Option<InferredOwnership>,
378}
379
380impl OwnershipPrediction {
381    /// Default confidence threshold (from spec: 0.65).
382    pub const CONFIDENCE_THRESHOLD: f32 = 0.65;
383
384    /// Check if prediction is confident enough to use.
385    pub fn is_confident(&self) -> bool {
386        self.confidence >= Self::CONFIDENCE_THRESHOLD
387    }
388
389    /// Get the ownership to use (predicted if confident, fallback otherwise).
390    pub fn effective_ownership(&self) -> InferredOwnership {
391        if self.is_confident() {
392            self.kind
393        } else {
394            self.fallback.unwrap_or(InferredOwnership::RawPointer)
395        }
396    }
397}
398
399impl PartialEq for OwnershipPrediction {
400    fn eq(&self, other: &Self) -> bool {
401        self.kind == other.kind && (self.confidence - other.confidence).abs() < f32::EPSILON
402    }
403}
404
405// ============================================================================
406// DECY-ML-002: FEATURE EXTRACTION FROM HIR
407// ============================================================================
408
409use decy_hir::{HirExpression, HirFunction, HirStatement, HirType};
410
411/// Extracts OwnershipFeatures from HIR functions.
412///
413/// DECY-ML-002: Converts HIR analysis into 142-dimension feature vectors
414/// for ML-based ownership classification.
415#[derive(Debug, Default)]
416pub struct FeatureExtractor {
417    /// Count of features extracted (for statistics)
418    extracted_count: u64,
419}
420
421impl FeatureExtractor {
422    /// Create a new feature extractor.
423    pub fn new() -> Self {
424        Self { extracted_count: 0 }
425    }
426
427    /// Get count of features extracted.
428    pub fn extracted_count(&self) -> u64 {
429        self.extracted_count
430    }
431
432    /// Extract features for a specific parameter by name.
433    pub fn extract_for_parameter(
434        &self,
435        func: &HirFunction,
436        param_name: &str,
437    ) -> Option<OwnershipFeatures> {
438        // Find the parameter
439        let param = func.parameters().iter().find(|p| p.name() == param_name)?;
440        let param_type = param.param_type();
441
442        // Only extract features for pointer-like types
443        if !self.is_pointer_like(param_type) {
444            return None;
445        }
446
447        let param_index = func
448            .parameters()
449            .iter()
450            .position(|p| p.name() == param_name)?;
451
452        Some(self.extract_features(func, param_name, param_type, param_index, true))
453    }
454
455    /// Extract features for a local variable by name.
456    pub fn extract_for_variable(
457        &self,
458        func: &HirFunction,
459        var_name: &str,
460    ) -> Option<OwnershipFeatures> {
461        // Find variable declaration in body
462        for stmt in func.body() {
463            if let HirStatement::VariableDeclaration {
464                name,
465                var_type,
466                initializer,
467            } = stmt
468            {
469                if name == var_name && self.is_pointer_like(var_type) {
470                    let allocation = self.classify_allocation(initializer.as_ref());
471                    let mut features = self.extract_features(func, var_name, var_type, 0, false);
472                    features.allocation_site = allocation;
473                    return Some(features);
474                }
475            }
476        }
477        None
478    }
479
480    /// Extract features for all pointer parameters in a function.
481    pub fn extract_all(&self, func: &HirFunction) -> Vec<(String, OwnershipFeatures)> {
482        let mut result = Vec::new();
483
484        for (idx, param) in func.parameters().iter().enumerate() {
485            if self.is_pointer_like(param.param_type()) {
486                let features =
487                    self.extract_features(func, param.name(), param.param_type(), idx, true);
488                result.push((param.name().to_string(), features));
489            }
490        }
491
492        result
493    }
494
495    /// Check if a type is pointer-like (pointer, reference, box, etc.).
496    fn is_pointer_like(&self, ty: &HirType) -> bool {
497        matches!(
498            ty,
499            HirType::Pointer(_) | HirType::Box(_) | HirType::Reference { .. } | HirType::Vec(_)
500        )
501    }
502
503    /// Extract features for a pointer variable.
504    fn extract_features(
505        &self,
506        func: &HirFunction,
507        var_name: &str,
508        var_type: &HirType,
509        param_index: usize,
510        is_param: bool,
511    ) -> OwnershipFeatures {
512        let (reads, writes) = self.count_accesses(func.body(), var_name);
513
514        OwnershipFeatures {
515            // Syntactic features
516            pointer_depth: self.compute_pointer_depth(var_type),
517            is_const: self.is_const_type(var_type),
518            is_array_decay: self.detect_array_decay(func, param_index),
519            has_size_param: self.has_size_parameter(func, param_index),
520            // Semantic features
521            allocation_site: if is_param {
522                AllocationKind::Parameter
523            } else {
524                AllocationKind::Unknown
525            },
526            deallocation_count: self.count_deallocations(func.body(), var_name),
527            alias_count: self.count_aliases(func.body(), var_name),
528            escape_scope: self.check_escape(func, var_name),
529            // Usage patterns
530            read_count: reads,
531            write_count: writes,
532            arithmetic_ops: self.count_pointer_arithmetic(func.body(), var_name),
533            null_checks: self.count_null_checks(func.body(), var_name),
534        }
535    }
536
537    /// Compute pointer indirection depth.
538    fn compute_pointer_depth(&self, ty: &HirType) -> u8 {
539        match ty {
540            HirType::Pointer(inner) => 1 + self.compute_pointer_depth(inner),
541            HirType::Box(inner) => 1 + self.compute_pointer_depth(inner),
542            HirType::Reference { inner, .. } => 1 + self.compute_pointer_depth(inner),
543            _ => 0,
544        }
545    }
546
547    /// Check if type is const/immutable.
548    fn is_const_type(&self, ty: &HirType) -> bool {
549        match ty {
550            HirType::Reference { mutable, .. } => !mutable,
551            _ => false,
552        }
553    }
554
555    /// Detect array decay pattern (pointer followed by size).
556    fn detect_array_decay(&self, func: &HirFunction, param_index: usize) -> bool {
557        let params = func.parameters();
558        if param_index + 1 >= params.len() {
559            return false;
560        }
561
562        // Check if current is pointer and next is integer
563        let current = &params[param_index];
564        let next = &params[param_index + 1];
565
566        if !matches!(current.param_type(), HirType::Pointer(_)) {
567            return false;
568        }
569
570        if !matches!(next.param_type(), HirType::Int | HirType::UnsignedInt) {
571            return false;
572        }
573
574        // Check naming patterns
575        let next_name = next.name().to_lowercase();
576        next_name.contains("len")
577            || next_name.contains("size")
578            || next_name.contains("count")
579            || next_name.contains("num")
580            || next_name == "n"
581    }
582
583    /// Check if there's an associated size parameter.
584    fn has_size_parameter(&self, func: &HirFunction, param_index: usize) -> bool {
585        self.detect_array_decay(func, param_index)
586    }
587
588    /// Classify allocation kind from initializer.
589    fn classify_allocation(&self, initializer: Option<&HirExpression>) -> AllocationKind {
590        match initializer {
591            Some(HirExpression::Malloc { .. }) => AllocationKind::Malloc,
592            Some(HirExpression::Calloc { .. }) => AllocationKind::Calloc,
593            Some(HirExpression::Realloc { .. }) => AllocationKind::Realloc,
594            Some(HirExpression::FunctionCall { function, .. }) if function == "malloc" => {
595                AllocationKind::Malloc
596            }
597            Some(HirExpression::FunctionCall { function, .. }) if function == "calloc" => {
598                AllocationKind::Calloc
599            }
600            Some(HirExpression::FunctionCall { function, .. }) if function == "realloc" => {
601                AllocationKind::Realloc
602            }
603            _ => AllocationKind::Unknown,
604        }
605    }
606
607    /// Count free() calls on a variable.
608    fn count_deallocations(&self, body: &[HirStatement], var_name: &str) -> u8 {
609        let mut count = 0u8;
610        for stmt in body {
611            count = count.saturating_add(self.count_free_in_stmt(stmt, var_name));
612        }
613        count
614    }
615
616    fn count_free_in_stmt(&self, stmt: &HirStatement, var_name: &str) -> u8 {
617        match stmt {
618            HirStatement::Free { pointer } => {
619                if self.expr_uses_var(pointer, var_name) {
620                    1
621                } else {
622                    0
623                }
624            }
625            HirStatement::If {
626                then_block,
627                else_block,
628                ..
629            } => {
630                let mut count = 0u8;
631                for s in then_block {
632                    count = count.saturating_add(self.count_free_in_stmt(s, var_name));
633                }
634                if let Some(else_stmts) = else_block {
635                    for s in else_stmts {
636                        count = count.saturating_add(self.count_free_in_stmt(s, var_name));
637                    }
638                }
639                count
640            }
641            HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
642                let mut count = 0u8;
643                for s in body {
644                    count = count.saturating_add(self.count_free_in_stmt(s, var_name));
645                }
646                count
647            }
648            _ => 0,
649        }
650    }
651
652    /// Count aliases (assignments to other variables).
653    fn count_aliases(&self, _body: &[HirStatement], _var_name: &str) -> u8 {
654        // Simplified: would need full dataflow analysis
655        0
656    }
657
658    /// Check if variable escapes function scope.
659    fn check_escape(&self, func: &HirFunction, var_name: &str) -> bool {
660        for stmt in func.body() {
661            if let HirStatement::Return(Some(expr)) = stmt {
662                if self.expr_uses_var(expr, var_name) {
663                    return true;
664                }
665            }
666        }
667        false
668    }
669
670    /// Count read/write accesses to a variable.
671    fn count_accesses(&self, body: &[HirStatement], var_name: &str) -> (u32, u32) {
672        let mut reads = 0u32;
673        let mut writes = 0u32;
674
675        for stmt in body {
676            let (r, w) = self.count_stmt_accesses(stmt, var_name);
677            reads = reads.saturating_add(r);
678            writes = writes.saturating_add(w);
679        }
680
681        (reads, writes)
682    }
683
684    fn count_stmt_accesses(&self, stmt: &HirStatement, var_name: &str) -> (u32, u32) {
685        match stmt {
686            HirStatement::Assignment { target, value } => {
687                let mut reads: u32 = if self.expr_uses_var(value, var_name) {
688                    1
689                } else {
690                    0
691                };
692                let writes: u32 = if target == var_name { 1 } else { 0 };
693                // Target might also be a dereference read
694                if target != var_name && self.expr_uses_var(value, var_name) {
695                    reads += 1;
696                }
697                (reads, writes)
698            }
699            HirStatement::DerefAssignment { target, value } => {
700                let reads: u32 = if self.expr_uses_var(value, var_name)
701                    || self.expr_uses_var(target, var_name)
702                {
703                    1
704                } else {
705                    0
706                };
707                let writes: u32 = if self.expr_uses_var(target, var_name) {
708                    1
709                } else {
710                    0
711                };
712                (reads, writes)
713            }
714            HirStatement::If {
715                condition,
716                then_block,
717                else_block,
718            } => {
719                let mut reads: u32 = if self.expr_uses_var(condition, var_name) {
720                    1
721                } else {
722                    0
723                };
724                let mut writes: u32 = 0;
725                for s in then_block {
726                    let (r, w) = self.count_stmt_accesses(s, var_name);
727                    reads = reads.saturating_add(r);
728                    writes = writes.saturating_add(w);
729                }
730                if let Some(else_stmts) = else_block {
731                    for s in else_stmts {
732                        let (r, w) = self.count_stmt_accesses(s, var_name);
733                        reads = reads.saturating_add(r);
734                        writes = writes.saturating_add(w);
735                    }
736                }
737                (reads, writes)
738            }
739            _ => (0, 0),
740        }
741    }
742
743    /// Count pointer arithmetic operations.
744    fn count_pointer_arithmetic(&self, _body: &[HirStatement], _var_name: &str) -> u8 {
745        // Simplified: would need expression analysis
746        0
747    }
748
749    /// Count null checks on a variable.
750    fn count_null_checks(&self, body: &[HirStatement], var_name: &str) -> u8 {
751        let mut count = 0u8;
752        for stmt in body {
753            count = count.saturating_add(self.count_null_checks_in_stmt(stmt, var_name));
754        }
755        count
756    }
757
758    fn count_null_checks_in_stmt(&self, stmt: &HirStatement, var_name: &str) -> u8 {
759        match stmt {
760            HirStatement::If {
761                condition,
762                then_block,
763                else_block,
764            } => {
765                let mut count: u8 = if self.is_null_check(condition, var_name) {
766                    1
767                } else {
768                    0
769                };
770                for s in then_block {
771                    count = count.saturating_add(self.count_null_checks_in_stmt(s, var_name));
772                }
773                if let Some(else_stmts) = else_block {
774                    for s in else_stmts {
775                        count = count.saturating_add(self.count_null_checks_in_stmt(s, var_name));
776                    }
777                }
778                count
779            }
780            HirStatement::While { condition, body } => {
781                let mut count: u8 = if self.is_null_check(condition, var_name) {
782                    1
783                } else {
784                    0
785                };
786                for s in body {
787                    count = count.saturating_add(self.count_null_checks_in_stmt(s, var_name));
788                }
789                count
790            }
791            _ => 0,
792        }
793    }
794
795    /// Check if expression is a null check for the variable.
796    fn is_null_check(&self, expr: &HirExpression, var_name: &str) -> bool {
797        match expr {
798            HirExpression::IsNotNull(inner) => self.expr_uses_var(inner, var_name),
799            HirExpression::BinaryOp { left, right, .. } => {
800                // Check for ptr != NULL or ptr == NULL patterns
801                (self.expr_uses_var(left, var_name)
802                    && matches!(**right, HirExpression::NullLiteral))
803                    || (self.expr_uses_var(right, var_name)
804                        && matches!(**left, HirExpression::NullLiteral))
805            }
806            _ => false,
807        }
808    }
809
810    /// Check if expression uses a variable.
811    fn expr_uses_var(&self, expr: &HirExpression, var_name: &str) -> bool {
812        match expr {
813            HirExpression::Variable(name) => name == var_name,
814            HirExpression::Dereference(inner) => self.expr_uses_var(inner, var_name),
815            HirExpression::AddressOf(inner) => self.expr_uses_var(inner, var_name),
816            HirExpression::BinaryOp { left, right, .. } => {
817                self.expr_uses_var(left, var_name) || self.expr_uses_var(right, var_name)
818            }
819            HirExpression::UnaryOp { operand, .. } => self.expr_uses_var(operand, var_name),
820            HirExpression::ArrayIndex { array, index } => {
821                self.expr_uses_var(array, var_name) || self.expr_uses_var(index, var_name)
822            }
823            HirExpression::FunctionCall { arguments, .. } => arguments
824                .iter()
825                .any(|arg| self.expr_uses_var(arg, var_name)),
826            HirExpression::IsNotNull(inner) => self.expr_uses_var(inner, var_name),
827            _ => false,
828        }
829    }
830}
831
832// ============================================================================
833// DECY-ML-009: ERROR PATTERN LIBRARY
834// ============================================================================
835
836/// Error kind for ownership inference failures.
837///
838/// Maps to OwnershipDefect for consistent categorization.
839#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
840pub enum OwnershipErrorKind {
841    /// Owning pointer classified as borrowing or vice versa
842    PointerMisclassification,
843    /// Missing or incorrect lifetime annotations
844    LifetimeInferenceGap,
845    /// Use-after-free pattern not caught
846    DanglingPointerRisk,
847    /// Multiple mutable aliases generated
848    AliasViolation,
849    /// Unnecessary unsafe blocks in output
850    UnsafeMinimizationFailure,
851    /// Array vs slice semantics error
852    ArraySliceMismatch,
853    /// Allocation without corresponding deallocation
854    ResourceLeakPattern,
855    /// Const pointer vs mutable reference error
856    MutabilityMismatch,
857}
858
859impl OwnershipErrorKind {
860    /// Convert to OwnershipDefect for taxonomy integration.
861    pub fn to_defect(self) -> OwnershipDefect {
862        match self {
863            Self::PointerMisclassification => OwnershipDefect::PointerMisclassification,
864            Self::LifetimeInferenceGap => OwnershipDefect::LifetimeInferenceGap,
865            Self::DanglingPointerRisk => OwnershipDefect::DanglingPointerRisk,
866            Self::AliasViolation => OwnershipDefect::AliasViolation,
867            Self::UnsafeMinimizationFailure => OwnershipDefect::UnsafeMinimizationFailure,
868            Self::ArraySliceMismatch => OwnershipDefect::ArraySliceMismatch,
869            Self::ResourceLeakPattern => OwnershipDefect::ResourceLeakPattern,
870            Self::MutabilityMismatch => OwnershipDefect::MutabilityMismatch,
871        }
872    }
873}
874
875/// Severity level for error patterns.
876#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
877pub enum ErrorSeverity {
878    /// Informational - optimization opportunity
879    #[default]
880    Info,
881    /// Warning - suboptimal but compiles
882    Warning,
883    /// Error - does not compile
884    Error,
885    /// Critical - memory safety issue
886    Critical,
887}
888
889/// Suggested fix for an error pattern.
890#[derive(Debug, Clone, Serialize, Deserialize)]
891pub struct SuggestedFix {
892    /// Human-readable description
893    description: String,
894    /// Code template for the fix
895    code_template: String,
896    /// Confidence in this fix (0.0 - 1.0)
897    confidence: f32,
898}
899
900impl SuggestedFix {
901    /// Create a new suggested fix.
902    pub fn new(description: impl Into<String>, code_template: impl Into<String>) -> Self {
903        Self {
904            description: description.into(),
905            code_template: code_template.into(),
906            confidence: 0.5,
907        }
908    }
909
910    /// Set confidence score.
911    pub fn with_confidence(mut self, confidence: f32) -> Self {
912        self.confidence = confidence;
913        self
914    }
915
916    /// Get description.
917    pub fn description(&self) -> &str {
918        &self.description
919    }
920
921    /// Get code template.
922    pub fn code_template(&self) -> &str {
923        &self.code_template
924    }
925
926    /// Get confidence score.
927    pub fn confidence(&self) -> f32 {
928        self.confidence
929    }
930}
931
932/// An ownership inference error pattern.
933///
934/// Represents a specific C pattern that causes ownership inference failures,
935/// along with metadata for curriculum learning and error recovery.
936#[derive(Debug, Clone, Serialize, Deserialize)]
937pub struct ErrorPattern {
938    /// Unique identifier for this pattern
939    id: String,
940    /// Error category
941    error_kind: OwnershipErrorKind,
942    /// Human-readable description
943    description: String,
944    /// Example C code that triggers this error
945    c_pattern: Option<String>,
946    /// Rust compiler error message (if applicable)
947    rust_error: Option<String>,
948    /// Suggested fix
949    suggested_fix: Option<SuggestedFix>,
950    /// Severity level
951    severity: ErrorSeverity,
952    /// Curriculum learning level (1 = easiest, higher = harder)
953    curriculum_level: u8,
954    /// Number of times this pattern has been encountered
955    occurrence_count: u64,
956}
957
958impl ErrorPattern {
959    /// Create a new error pattern.
960    pub fn new(
961        id: impl Into<String>,
962        error_kind: OwnershipErrorKind,
963        description: impl Into<String>,
964    ) -> Self {
965        Self {
966            id: id.into(),
967            error_kind,
968            description: description.into(),
969            c_pattern: None,
970            rust_error: None,
971            suggested_fix: None,
972            severity: ErrorSeverity::Error,
973            curriculum_level: 1,
974            occurrence_count: 0,
975        }
976    }
977
978    /// Set the C pattern example.
979    pub fn with_c_pattern(mut self, pattern: impl Into<String>) -> Self {
980        self.c_pattern = Some(pattern.into());
981        self
982    }
983
984    /// Set the Rust error message.
985    pub fn with_rust_error(mut self, error: impl Into<String>) -> Self {
986        self.rust_error = Some(error.into());
987        self
988    }
989
990    /// Set the suggested fix.
991    pub fn with_fix(mut self, fix: SuggestedFix) -> Self {
992        self.suggested_fix = Some(fix);
993        self
994    }
995
996    /// Set severity level.
997    pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
998        self.severity = severity;
999        self
1000    }
1001
1002    /// Set curriculum level.
1003    pub fn with_curriculum_level(mut self, level: u8) -> Self {
1004        self.curriculum_level = level;
1005        self
1006    }
1007
1008    /// Get pattern ID.
1009    pub fn id(&self) -> &str {
1010        &self.id
1011    }
1012
1013    /// Get error kind.
1014    pub fn error_kind(&self) -> OwnershipErrorKind {
1015        self.error_kind
1016    }
1017
1018    /// Get description.
1019    pub fn description(&self) -> &str {
1020        &self.description
1021    }
1022
1023    /// Get C pattern.
1024    pub fn c_pattern(&self) -> Option<&str> {
1025        self.c_pattern.as_deref()
1026    }
1027
1028    /// Get Rust error.
1029    pub fn rust_error(&self) -> Option<&str> {
1030        self.rust_error.as_deref()
1031    }
1032
1033    /// Get suggested fix.
1034    pub fn suggested_fix(&self) -> Option<&SuggestedFix> {
1035        self.suggested_fix.as_ref()
1036    }
1037
1038    /// Get severity.
1039    pub fn severity(&self) -> ErrorSeverity {
1040        self.severity
1041    }
1042
1043    /// Get curriculum level.
1044    pub fn curriculum_level(&self) -> u8 {
1045        self.curriculum_level
1046    }
1047
1048    /// Increment occurrence count.
1049    pub fn record_occurrence(&mut self) {
1050        self.occurrence_count = self.occurrence_count.saturating_add(1);
1051    }
1052
1053    /// Get occurrence count.
1054    pub fn occurrence_count(&self) -> u64 {
1055        self.occurrence_count
1056    }
1057}
1058
1059/// Library of error patterns for ownership inference.
1060///
1061/// Supports curriculum ordering and pattern matching for error recovery.
1062#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1063pub struct PatternLibrary {
1064    /// All patterns indexed by ID
1065    patterns: std::collections::HashMap<String, ErrorPattern>,
1066}
1067
1068impl PatternLibrary {
1069    /// Create a new empty pattern library.
1070    pub fn new() -> Self {
1071        Self {
1072            patterns: std::collections::HashMap::new(),
1073        }
1074    }
1075
1076    /// Add a pattern to the library.
1077    pub fn add(&mut self, pattern: ErrorPattern) {
1078        self.patterns.insert(pattern.id.clone(), pattern);
1079    }
1080
1081    /// Get a pattern by ID.
1082    pub fn get(&self, id: &str) -> Option<&ErrorPattern> {
1083        self.patterns.get(id)
1084    }
1085
1086    /// Get a mutable pattern by ID.
1087    pub fn get_mut(&mut self, id: &str) -> Option<&mut ErrorPattern> {
1088        self.patterns.get_mut(id)
1089    }
1090
1091    /// Get all patterns matching an error kind.
1092    pub fn get_by_error_kind(&self, kind: OwnershipErrorKind) -> Vec<&ErrorPattern> {
1093        self.patterns
1094            .values()
1095            .filter(|p| p.error_kind == kind)
1096            .collect()
1097    }
1098
1099    /// Get patterns ordered by curriculum level (easiest first).
1100    pub fn curriculum_ordered(&self) -> Vec<&ErrorPattern> {
1101        let mut patterns: Vec<_> = self.patterns.values().collect();
1102        patterns.sort_by_key(|p| p.curriculum_level);
1103        patterns
1104    }
1105
1106    /// Match patterns against a Rust error message.
1107    pub fn match_rust_error(&self, error_msg: &str) -> Vec<&ErrorPattern> {
1108        self.patterns
1109            .values()
1110            .filter(|p| p.rust_error.as_ref().is_some_and(|e| error_msg.contains(e)))
1111            .collect()
1112    }
1113
1114    /// Get number of patterns.
1115    pub fn len(&self) -> usize {
1116        self.patterns.len()
1117    }
1118
1119    /// Check if library is empty.
1120    pub fn is_empty(&self) -> bool {
1121        self.patterns.is_empty()
1122    }
1123
1124    /// Iterate over all patterns.
1125    pub fn iter(&self) -> impl Iterator<Item = &ErrorPattern> {
1126        self.patterns.values()
1127    }
1128}
1129
1130// ============================================================================
1131// DECY-ML-007: DEFAULT PATTERN LIBRARY
1132// ============================================================================
1133
1134/// Create a default pattern library with common ownership inference patterns.
1135///
1136/// Returns a `PatternLibrary` pre-populated with patterns for all 8 error kinds,
1137/// organized by curriculum level (simpler patterns first).
1138///
1139/// # Example
1140///
1141/// ```
1142/// use decy_ownership::ml_features::default_pattern_library;
1143///
1144/// let library = default_pattern_library();
1145/// assert!(!library.is_empty());
1146///
1147/// // Get patterns ordered by curriculum level
1148/// let ordered = library.curriculum_ordered();
1149/// for pattern in ordered {
1150///     println!("{}: {}", pattern.id(), pattern.description());
1151/// }
1152/// ```
1153pub fn default_pattern_library() -> PatternLibrary {
1154    let mut library = PatternLibrary::new();
1155
1156    // ========================================================================
1157    // Level 1: Basic Ownership Patterns (Easiest)
1158    // ========================================================================
1159
1160    // DECY-O-001: Pointer Misclassification - malloc → Box
1161    library.add(
1162        ErrorPattern::new(
1163            "malloc-to-box",
1164            OwnershipErrorKind::PointerMisclassification,
1165            "Single allocation with malloc should use Box<T>",
1166        )
1167        .with_severity(ErrorSeverity::Warning)
1168        .with_curriculum_level(1)
1169        .with_c_pattern("void *ptr = malloc(sizeof(T))")
1170        .with_rust_error("E0308")
1171        .with_fix(SuggestedFix::new(
1172            "Replace raw pointer with Box",
1173            "let ptr: Box<T> = Box::new(T::default());",
1174        )),
1175    );
1176
1177    // DECY-O-001: Pointer Misclassification - array → Vec
1178    library.add(
1179        ErrorPattern::new(
1180            "array-to-vec",
1181            OwnershipErrorKind::PointerMisclassification,
1182            "Dynamic array allocation should use Vec<T>",
1183        )
1184        .with_severity(ErrorSeverity::Warning)
1185        .with_curriculum_level(1)
1186        .with_c_pattern("T *arr = malloc(n * sizeof(T))")
1187        .with_rust_error("E0308")
1188        .with_fix(SuggestedFix::new(
1189            "Replace pointer array with Vec",
1190            "let arr: Vec<T> = Vec::with_capacity(n);",
1191        )),
1192    );
1193
1194    // DECY-O-008: Mutability Mismatch - const pointer
1195    library.add(
1196        ErrorPattern::new(
1197            "const-to-immut-ref",
1198            OwnershipErrorKind::MutabilityMismatch,
1199            "Const pointer should be immutable reference",
1200        )
1201        .with_severity(ErrorSeverity::Warning)
1202        .with_curriculum_level(1)
1203        .with_c_pattern("const T *ptr")
1204        .with_rust_error("E0596")
1205        .with_fix(SuggestedFix::new(
1206            "Use immutable reference",
1207            "fn foo(ptr: &T)",
1208        )),
1209    );
1210
1211    // ========================================================================
1212    // Level 2: Lifetime Patterns (Medium)
1213    // ========================================================================
1214
1215    // DECY-O-002: Lifetime Inference Gap - missing lifetime
1216    library.add(
1217        ErrorPattern::new(
1218            "missing-lifetime",
1219            OwnershipErrorKind::LifetimeInferenceGap,
1220            "Function returning reference needs lifetime annotation",
1221        )
1222        .with_severity(ErrorSeverity::Error)
1223        .with_curriculum_level(2)
1224        .with_c_pattern("T* get_field(Struct *s) { return &s->field; }")
1225        .with_rust_error("E0106")
1226        .with_fix(SuggestedFix::new(
1227            "Add lifetime parameter",
1228            "fn get_field<'a>(s: &'a Struct) -> &'a T",
1229        )),
1230    );
1231
1232    // DECY-O-002: Lifetime Inference Gap - struct lifetime
1233    library.add(
1234        ErrorPattern::new(
1235            "struct-lifetime",
1236            OwnershipErrorKind::LifetimeInferenceGap,
1237            "Struct containing reference needs lifetime parameter",
1238        )
1239        .with_severity(ErrorSeverity::Error)
1240        .with_curriculum_level(2)
1241        .with_c_pattern("struct View { T *data; }")
1242        .with_rust_error("E0106")
1243        .with_fix(SuggestedFix::new(
1244            "Add lifetime to struct",
1245            "struct View<'a> { data: &'a T }",
1246        )),
1247    );
1248
1249    // DECY-O-006: Array/Slice Mismatch - parameter
1250    library.add(
1251        ErrorPattern::new(
1252            "array-param-to-slice",
1253            OwnershipErrorKind::ArraySliceMismatch,
1254            "Array parameter should be slice reference",
1255        )
1256        .with_severity(ErrorSeverity::Warning)
1257        .with_curriculum_level(2)
1258        .with_c_pattern("void process(int arr[], size_t len)")
1259        .with_rust_error("E0308")
1260        .with_fix(SuggestedFix::new(
1261            "Use slice parameter",
1262            "fn process(arr: &[i32])",
1263        )),
1264    );
1265
1266    // ========================================================================
1267    // Level 3: Borrow Checker Patterns (Harder)
1268    // ========================================================================
1269
1270    // DECY-O-004: Alias Violation - mutable aliasing
1271    library.add(
1272        ErrorPattern::new(
1273            "mutable-aliasing",
1274            OwnershipErrorKind::AliasViolation,
1275            "Cannot have multiple mutable references",
1276        )
1277        .with_severity(ErrorSeverity::Error)
1278        .with_curriculum_level(3)
1279        .with_c_pattern("T *a = ptr; T *b = ptr; *a = x; *b = y;")
1280        .with_rust_error("E0499")
1281        .with_fix(SuggestedFix::new(
1282            "Use single mutable reference or split borrows",
1283            "// Ensure only one &mut exists at a time",
1284        )),
1285    );
1286
1287    // DECY-O-004: Alias Violation - immut + mut
1288    library.add(
1289        ErrorPattern::new(
1290            "immut-mut-aliasing",
1291            OwnershipErrorKind::AliasViolation,
1292            "Cannot have mutable reference while immutable exists",
1293        )
1294        .with_severity(ErrorSeverity::Error)
1295        .with_curriculum_level(3)
1296        .with_c_pattern("const T *r = ptr; *ptr = x; use(r);")
1297        .with_rust_error("E0502")
1298        .with_fix(SuggestedFix::new(
1299            "End immutable borrow before mutating",
1300            "let r = &*ptr; use(r); *ptr = x;",
1301        )),
1302    );
1303
1304    // DECY-O-003: Dangling Pointer Risk - use after free
1305    library.add(
1306        ErrorPattern::new(
1307            "use-after-free",
1308            OwnershipErrorKind::DanglingPointerRisk,
1309            "Use of pointer after free causes undefined behavior",
1310        )
1311        .with_severity(ErrorSeverity::Critical)
1312        .with_curriculum_level(3)
1313        .with_c_pattern("free(ptr); use(ptr);")
1314        .with_rust_error("E0382")
1315        .with_fix(SuggestedFix::new(
1316            "Use Option<Box<T>> and take() to consume",
1317            "let val = box_opt.take(); // Consumes ownership",
1318        )),
1319    );
1320
1321    // DECY-O-003: Dangling Pointer Risk - return local
1322    library.add(
1323        ErrorPattern::new(
1324            "return-local-ref",
1325            OwnershipErrorKind::DanglingPointerRisk,
1326            "Returning pointer to local variable is undefined behavior",
1327        )
1328        .with_severity(ErrorSeverity::Critical)
1329        .with_curriculum_level(3)
1330        .with_c_pattern("int* foo() { int x = 1; return &x; }")
1331        .with_rust_error("E0515")
1332        .with_fix(SuggestedFix::new(
1333            "Return owned value or use parameter lifetime",
1334            "fn foo() -> i32 { 1 } // Return by value",
1335        )),
1336    );
1337
1338    // ========================================================================
1339    // Level 4: Resource Management (Advanced)
1340    // ========================================================================
1341
1342    // DECY-O-007: Resource Leak - missing free
1343    library.add(
1344        ErrorPattern::new(
1345            "missing-free",
1346            OwnershipErrorKind::ResourceLeakPattern,
1347            "Allocated memory not freed causes leak",
1348        )
1349        .with_severity(ErrorSeverity::Warning)
1350        .with_curriculum_level(4)
1351        .with_c_pattern("void* p = malloc(...); return; // leak!")
1352        .with_fix(SuggestedFix::new(
1353            "Use RAII with Box/Vec for automatic cleanup",
1354            "let p = Box::new(...); // Automatically freed",
1355        )),
1356    );
1357
1358    // DECY-O-007: Resource Leak - file handle
1359    library.add(
1360        ErrorPattern::new(
1361            "file-handle-leak",
1362            OwnershipErrorKind::ResourceLeakPattern,
1363            "File handle not closed causes resource leak",
1364        )
1365        .with_severity(ErrorSeverity::Warning)
1366        .with_curriculum_level(4)
1367        .with_c_pattern("FILE *f = fopen(...); return; // leak!")
1368        .with_fix(SuggestedFix::new(
1369            "Use File type with automatic Drop",
1370            "let f = File::open(...)?; // Closed on drop",
1371        )),
1372    );
1373
1374    // DECY-O-005: Unsafe Minimization Failure
1375    library.add(
1376        ErrorPattern::new(
1377            "unnecessary-unsafe",
1378            OwnershipErrorKind::UnsafeMinimizationFailure,
1379            "Safe alternative exists for this unsafe operation",
1380        )
1381        .with_severity(ErrorSeverity::Warning)
1382        .with_curriculum_level(4)
1383        .with_c_pattern("*(ptr + i) = value; // pointer arithmetic")
1384        .with_fix(SuggestedFix::new(
1385            "Use safe slice indexing",
1386            "slice[i] = value;",
1387        )),
1388    );
1389
1390    // DECY-O-005: Unsafe Minimization - null check
1391    library.add(
1392        ErrorPattern::new(
1393            "null-check-to-option",
1394            OwnershipErrorKind::UnsafeMinimizationFailure,
1395            "Null pointer check should use Option<T>",
1396        )
1397        .with_severity(ErrorSeverity::Warning)
1398        .with_curriculum_level(4)
1399        .with_c_pattern("if (ptr != NULL) { use(ptr); }")
1400        .with_fix(SuggestedFix::new(
1401            "Use Option<T> with if let or match",
1402            "if let Some(val) = opt { use(val); }",
1403        )),
1404    );
1405
1406    // ========================================================================
1407    // Level 5: Complex Patterns (Expert)
1408    // ========================================================================
1409
1410    // DECY-O-004: Alias Violation - self-referential
1411    library.add(
1412        ErrorPattern::new(
1413            "self-referential-struct",
1414            OwnershipErrorKind::AliasViolation,
1415            "Self-referential struct needs Pin or unsafe",
1416        )
1417        .with_severity(ErrorSeverity::Error)
1418        .with_curriculum_level(5)
1419        .with_c_pattern("struct Node { struct Node *next; int data; }")
1420        .with_rust_error("E0597")
1421        .with_fix(SuggestedFix::new(
1422            "Use Box for indirection or Pin for self-reference",
1423            "struct Node { next: Option<Box<Node>>, data: i32 }",
1424        )),
1425    );
1426
1427    // DECY-O-002: Lifetime Inference - multiple lifetimes
1428    library.add(
1429        ErrorPattern::new(
1430            "multiple-lifetimes",
1431            OwnershipErrorKind::LifetimeInferenceGap,
1432            "Function with multiple reference params needs explicit lifetimes",
1433        )
1434        .with_severity(ErrorSeverity::Error)
1435        .with_curriculum_level(5)
1436        .with_c_pattern("T* pick(T *a, T *b, int cond)")
1437        .with_rust_error("E0106")
1438        .with_fix(SuggestedFix::new(
1439            "Add explicit lifetime bounds",
1440            "fn pick<'a>(a: &'a T, b: &'a T, cond: bool) -> &'a T",
1441        )),
1442    );
1443
1444    // DECY-O-008: Mutability - interior mutability
1445    library.add(
1446        ErrorPattern::new(
1447            "interior-mutability",
1448            OwnershipErrorKind::MutabilityMismatch,
1449            "Mutation through shared reference needs Cell/RefCell",
1450        )
1451        .with_severity(ErrorSeverity::Warning)
1452        .with_curriculum_level(5)
1453        .with_c_pattern("void inc(Counter *c) { c->count++; } // called via const ptr")
1454        .with_rust_error("E0596")
1455        .with_fix(SuggestedFix::new(
1456            "Use Cell<T> or RefCell<T> for interior mutability",
1457            "struct Counter { count: Cell<i32> }",
1458        )),
1459    );
1460
1461    library
1462}
1463
1464#[cfg(test)]
1465#[path = "ml_features_coverage_tests.rs"]
1466mod ml_features_coverage_tests;