rusty_cpp/analysis/
mutable_checker.rs

1use crate::parser::CppAst;
2use crate::parser::ast_visitor::Class;
3use crate::parser::safety_annotations::SafetyContext;
4use crate::parser::external_annotations::ExternalAnnotations;
5
6/// Check for mutable fields in safe functions and classes
7///
8/// In Rust, the `mutable` keyword is considered unsafe because it breaks
9/// the const contract and allows interior mutability without proper guarantees.
10/// Instead, users should use `UnsafeCell<T>` and explicitly unsafe code.
11pub fn check_mutable_fields(
12    ast: &CppAst,
13    safety_context: &SafetyContext,
14    external_annotations: Option<&ExternalAnnotations>,
15) -> Result<Vec<String>, String> {
16    use crate::debug_println;
17
18    let mut errors = Vec::new();
19
20    debug_println!("MUTABLE: Checking {} classes for mutable fields", ast.classes.len());
21
22    // Check all classes for mutable fields
23    for class in &ast.classes {
24        debug_println!("MUTABLE: Checking class '{}' with {} members", class.name, class.members.len());
25
26        // First, check if this class is an unsafe_type (e.g., STL container internal)
27        // If so, skip analyzing its internal structure entirely
28        if let Some(ext_annot) = external_annotations {
29            if ext_annot.is_type_unsafe(&class.name) {
30                debug_println!("MUTABLE: Class '{}' is an unsafe_type - skipping internal analysis", class.name);
31                continue;
32            }
33        }
34
35        // Determine if this class or any of its methods are in safe context
36        let class_safe = is_class_safe(class, safety_context);
37
38        debug_println!("MUTABLE: Class '{}' safe = {}", class.name, class_safe);
39
40        if !class_safe {
41            continue; // Skip unsafe classes
42        }
43
44        // Check all member fields
45        for member in &class.members {
46            debug_println!("MUTABLE: Checking member '{}' is_mutable = {}", member.name, member.is_mutable);
47            if member.is_mutable {
48                let error = format!(
49                    "{}:{} - Mutable field '{}' not allowed in safe class '{}'. \
50                    Use UnsafeCell<T> and unsafe blocks for interior mutability instead.",
51                    member.location.file,
52                    member.location.line,
53                    member.name,
54                    class.name
55                );
56                errors.push(error);
57            }
58        }
59    }
60
61    debug_println!("MUTABLE: Found {} mutable field errors", errors.len());
62
63    Ok(errors)
64}
65
66/// Check if a class is marked as safe (either via annotation or file-level safety)
67///
68/// With the two-state model (Safe/Unsafe), mutable field checking is done at the CLASS level:
69/// - @safe class → mutable fields are errors
70/// - @unsafe class (default for unannotated) → mutable fields are allowed
71///
72/// Method-level @safe annotations do NOT affect mutable field checking.
73/// If you have an @unsafe class with a @safe method, mutable fields are still allowed.
74fn is_class_safe(class: &Class, safety_context: &SafetyContext) -> bool {
75    use crate::parser::safety_annotations::SafetyMode;
76    use crate::debug_println;
77
78    // Get the class's source file location
79    let class_file = &class.location.file;
80
81    // Use file-aware safety checking to avoid namespace collisions
82    // This ensures that file_default only applies to classes from the source file
83    let class_safety = safety_context.get_class_safety_for_file(&class.name, class_file);
84    debug_println!("MUTABLE: Class '{}' from '{}' has safety mode: {:?}", class.name, class_file, class_safety);
85
86    // With the two-state model, only check mutable fields for @safe classes
87    // Method-level @safe annotations do NOT trigger mutable field checking
88    if class_safety == SafetyMode::Safe {
89        debug_println!("MUTABLE: Class '{}' is marked @safe - checking for mutable fields", class.name);
90        return true;
91    }
92
93    debug_println!("MUTABLE: Class '{}' is @unsafe - mutable fields allowed", class.name);
94    false
95}