rusty_cpp/analysis/
mod.rs

1use crate::ir::{IrProgram, IrFunction, OwnershipState, BorrowKind};
2use crate::parser::HeaderCache;
3use std::collections::{HashMap, HashSet};
4use crate::debug_println;
5
6/// Check if a file path is from a system header (not user code)
7/// System headers are from standard library or third-party installations
8fn is_system_header(file_path: &str) -> bool {
9    // Common system header paths (absolute)
10    let system_paths = [
11        "/usr/include",
12        "/usr/local/include",
13        "/opt/homebrew/include",
14        "/Library/Developer",
15        "C:\\Program Files",
16        "/Applications/Xcode.app",
17    ];
18
19    for path in &system_paths {
20        if file_path.starts_with(path) {
21            return true;
22        }
23    }
24
25    // STL and system library patterns (works for relative paths too)
26    if file_path.contains("/include/c++/") ||
27       file_path.contains("/bits/") ||
28       file_path.contains("/ext/") ||
29       file_path.contains("stl_") ||
30       file_path.contains("/lib/gcc/") {
31        return true;
32    }
33
34    // Also skip the project's include/ directory (third-party headers like rusty::Box)
35    // These are library headers that shouldn't be analyzed internally
36    if file_path.contains("/include/rusty/") || file_path.contains("/include/unified_") {
37        return true;
38    }
39
40    false
41}
42
43/// Check if a type name represents a primitive type that can't contain references
44fn is_primitive_type(type_name: &str) -> bool {
45    // Strip template parameters and qualifiers
46    let base_type = type_name
47        .split('<').next().unwrap_or(type_name)
48        .trim();
49
50    matches!(base_type,
51        "int" | "char" | "bool" | "float" | "double" |
52        "long" | "short" | "unsigned" | "signed" |
53        "int8_t" | "int16_t" | "int32_t" | "int64_t" |
54        "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" |
55        "size_t" | "ptrdiff_t" | "void"
56    )
57}
58
59pub mod ownership;
60pub mod borrows;
61pub mod lifetimes;
62pub mod lifetime_checker;
63pub mod scope_lifetime;
64pub mod lifetime_inference;
65pub mod pointer_safety;
66pub mod unsafe_propagation;
67pub mod this_tracking;
68pub mod liveness;
69pub mod mutable_checker;
70pub mod lambda_capture_safety;
71pub mod raii_tracking;
72pub mod inheritance_safety;
73pub mod function_pointer_safety;
74
75#[derive(Debug, Clone)]
76#[allow(dead_code)]
77pub struct BorrowCheckError {
78    pub kind: ErrorKind,
79    pub location: String,
80    pub message: String,
81}
82
83#[derive(Debug, Clone)]
84#[allow(dead_code)]
85pub enum ErrorKind {
86    UseAfterMove,
87    DoubleBorrow,
88    MutableBorrowWhileImmutable,
89    DanglingReference,
90    LifetimeViolation,
91}
92
93#[allow(dead_code)]
94pub fn check_borrows(program: IrProgram) -> Result<Vec<String>, String> {
95    let mut errors = Vec::new();
96    
97    for function in &program.functions {
98        let function_errors = check_function(function)?;
99        errors.extend(function_errors);
100    }
101    
102    Ok(errors)
103}
104
105#[allow(dead_code)]
106pub fn check_borrows_with_annotations_and_safety(
107    program: IrProgram, 
108    header_cache: HeaderCache,
109    file_safe: bool
110) -> Result<Vec<String>, String> {
111    // If file is marked unsafe and no functions are marked safe, skip checking
112    if !file_safe && !has_any_safe_functions(&program, &header_cache) {
113        return Ok(Vec::new()); // No checking for unsafe code
114    }
115    
116    check_borrows_with_annotations(program, header_cache)
117}
118
119pub fn check_borrows_with_safety_context(
120    program: IrProgram,
121    header_cache: HeaderCache,
122    safety_context: crate::parser::safety_annotations::SafetyContext
123) -> Result<Vec<String>, String> {
124    use crate::parser::safety_annotations::SafetyMode;
125
126    // If the file default is unsafe and no functions are marked safe, skip checking
127    if safety_context.file_default != SafetyMode::Safe &&
128       !safety_context.function_overrides.iter().any(|(_, mode)| *mode == SafetyMode::Safe) &&
129       !has_any_safe_functions(&program, &header_cache) {
130        return Ok(Vec::new()); // No checking for unsafe code
131    }
132
133    let mut errors = Vec::new();
134
135    // PHASE 1: Check that safe functions returning references have lifetime annotations
136    let annotation_errors = check_lifetime_annotation_requirements(&program, &header_cache, &safety_context)?;
137    errors.extend(annotation_errors);
138
139    // Check each function based on its safety mode
140    for function in &program.functions {
141        debug_println!("DEBUG: Checking function '{}'", function.name);
142
143        // Skip borrow checking for system header functions
144        // They are tracked for safety status but not analyzed internally
145        if is_system_header(&function.source_file) {
146            debug_println!("DEBUG: Skipping system header function '{}' from {}", function.name, function.source_file);
147            continue;
148        }
149
150        // Check if this function should be checked
151        if !safety_context.should_check_function(&function.name) {
152            debug_println!("DEBUG: Skipping unsafe function '{}'", function.name);
153            continue; // Skip unsafe functions
154        }
155        debug_println!("DEBUG: Function '{}' is safe, checking...", function.name);
156
157        // Phase 2: Use version with header_cache for return value borrow detection
158        let function_errors = check_function_with_header_cache(function, &header_cache)?;
159        errors.extend(function_errors);
160    }
161
162    // Run lifetime inference and validation for safe functions
163    for function in &program.functions {
164        // Skip system headers
165        if is_system_header(&function.source_file) {
166            continue;
167        }
168
169        if safety_context.should_check_function(&function.name) {
170            let inference_errors = lifetime_inference::infer_and_validate_lifetimes(function)?;
171            errors.extend(inference_errors);
172
173            // Phase 1-7: Run RAII tracking checks
174            let raii_errors = raii_tracking::check_raii_issues(function, &header_cache)?;
175            errors.extend(raii_errors);
176        }
177    }
178
179    // If we have header annotations, also check lifetime constraints
180    if header_cache.has_signatures() {
181        // Bug #9 fix: pass safety_context to filter by @safe functions only
182        let lifetime_errors = lifetime_checker::check_lifetimes_with_annotations(&program, &header_cache, &safety_context)?;
183        errors.extend(lifetime_errors);
184
185        // Also run scope-based lifetime checking
186        let scope_errors = scope_lifetime::check_scoped_lifetimes(&program, &header_cache, &safety_context)?;
187        errors.extend(scope_errors);
188    }
189
190    Ok(errors)
191}
192
193fn has_any_safe_functions(program: &IrProgram, header_cache: &HeaderCache) -> bool {
194    use crate::parser::annotations::SafetyAnnotation;
195
196    for function in &program.functions {
197        if let Some(sig) = header_cache.get_signature(&function.name) {
198            if let Some(SafetyAnnotation::Safe) = sig.safety {
199                return true;
200            }
201        }
202    }
203    false
204}
205
206/// Check if a return type string represents a reference
207fn returns_reference(return_type: &str) -> bool {
208    // Check for reference types: &, const &, const Type&, Type&, etc.
209    // This is a simple heuristic based on the string representation
210    return_type.contains('&') && !return_type.contains("&&") // Exclude rvalue references for now
211}
212
213/// Phase 1: Check that safe functions returning references have lifetime annotations
214fn check_lifetime_annotation_requirements(
215    program: &IrProgram,
216    header_cache: &HeaderCache,
217    safety_context: &crate::parser::safety_annotations::SafetyContext
218) -> Result<Vec<String>, String> {
219    let mut errors = Vec::new();
220
221    for function in &program.functions {
222        // Skip system header functions
223        if is_system_header(&function.source_file) {
224            continue;
225        }
226
227        // Only check safe functions
228        if !safety_context.should_check_function(&function.name) {
229            continue;
230        }
231
232        // Get the function's return type from the original AST
233        // We need to access this through the parser/AST, but for now we can check
234        // if the function has a Return statement with a reference
235
236        // Check if function signature has lifetime annotation
237        let has_lifetime_annotation = if let Some(sig) = header_cache.get_signature(&function.name) {
238            sig.return_lifetime.is_some()
239        } else {
240            false
241        };
242
243        // Check if the function returns a reference by analyzing return statements
244        let returns_ref = check_if_function_returns_reference(function);
245
246        if returns_ref && !has_lifetime_annotation {
247            errors.push(format!(
248                "Safe function '{}' returns a reference but has no @lifetime annotation",
249                function.name
250            ));
251        }
252    }
253
254    Ok(errors)
255}
256
257/// Check if a function returns a reference by analyzing its return type
258fn check_if_function_returns_reference(function: &IrFunction) -> bool {
259    // Check if the return type is a reference
260    // References have & in the type (e.g., "const int&", "int&", "Type&")
261    function.return_type.contains('&') && !function.return_type.contains("&&")
262}
263
264#[allow(dead_code)]
265pub fn check_borrows_with_annotations(program: IrProgram, header_cache: HeaderCache) -> Result<Vec<String>, String> {
266    use crate::parser::annotations::SafetyAnnotation;
267    use crate::parser::safety_annotations::SafetyContext;
268    let mut errors = Vec::new();
269
270    // Create a SafetyContext from header annotations (Bug #9 fix)
271    let mut safety_context = SafetyContext::new();
272    safety_context.merge_header_annotations(&header_cache);
273
274    // Run regular borrow checking, but skip unsafe functions
275    for function in &program.functions {
276        // Check if this function is marked as unsafe
277        let is_unsafe = if let Some(sig) = header_cache.get_signature(&function.name) {
278            matches!(sig.safety, Some(SafetyAnnotation::Unsafe))
279        } else {
280            false
281        };
282
283        // Skip checking if function is marked unsafe
284        if !is_unsafe {
285            let function_errors = check_function(function)?;
286            errors.extend(function_errors);
287        }
288    }
289
290    // Run lifetime inference and validation
291    for function in &program.functions {
292        let inference_errors = lifetime_inference::infer_and_validate_lifetimes(function)?;
293        errors.extend(inference_errors);
294    }
295
296    // If we have header annotations, also check lifetime constraints
297    if header_cache.has_signatures() {
298        // Bug #9 fix: pass safety_context to filter by @safe functions only
299        let lifetime_errors = lifetime_checker::check_lifetimes_with_annotations(&program, &header_cache, &safety_context)?;
300        errors.extend(lifetime_errors);
301
302        // Also run scope-based lifetime checking
303        let scope_errors = scope_lifetime::check_scoped_lifetimes(&program, &header_cache, &safety_context)?;
304        errors.extend(scope_errors);
305    }
306
307    Ok(errors)
308}
309
310// Phase 2: Wrapper for backward compatibility
311fn check_function(function: &IrFunction) -> Result<Vec<String>, String> {
312    // Create an empty HeaderCache for functions that don't have annotations
313    let empty_cache = HeaderCache::new();
314    check_function_with_header_cache(function, &empty_cache)
315}
316
317// Phase 2: Added header_cache parameter for return value borrow detection
318fn check_function_with_header_cache(function: &IrFunction, header_cache: &HeaderCache) -> Result<Vec<String>, String> {
319    let mut errors = Vec::new();
320
321    // NEW: Run liveness analysis first
322    let mut liveness_analyzer = liveness::LivenessAnalyzer::new();
323    let last_uses = liveness_analyzer.analyze(function);
324
325    // Create ownership tracker with liveness information
326    let mut ownership_tracker = OwnershipTracker::with_liveness(last_uses);
327
328    // Create this pointer tracker if this is a method
329    let mut this_tracker = if function.is_method {
330        Some(this_tracking::ThisPointerTracker::new(function.method_qualifier.clone()))
331    } else {
332        None
333    };
334
335    // Initialize ownership for parameters and variables
336    for (name, var_info) in &function.variables {
337        ownership_tracker.set_ownership(name.clone(), var_info.ownership.clone());
338
339        // Track reference types
340        match &var_info.ty {
341            crate::ir::VariableType::Reference(_) => {
342                ownership_tracker.mark_as_reference(name.clone(), false);
343            }
344            crate::ir::VariableType::MutableReference(_) => {
345                ownership_tracker.mark_as_reference(name.clone(), true);
346            }
347            _ => {}
348        }
349    }
350    
351    // Traverse CFG and check each block
352    for node_idx in function.cfg.node_indices() {
353        let block = &function.cfg[node_idx];
354        
355        // Process statements, handling loops specially
356        let mut i = 0;
357        while i < block.statements.len() {
358            let statement = &block.statements[i];
359            
360            // Check if we're entering a loop
361            if matches!(statement, crate::ir::IrStatement::EnterLoop) {
362                // Find the matching ExitLoop
363                let mut loop_end = i + 1;
364                let mut loop_depth = 1;
365                while loop_end < block.statements.len() && loop_depth > 0 {
366                    match &block.statements[loop_end] {
367                        crate::ir::IrStatement::EnterLoop => loop_depth += 1,
368                        crate::ir::IrStatement::ExitLoop => loop_depth -= 1,
369                        _ => {}
370                    }
371                    loop_end += 1;
372                }
373                
374                // Process the loop body twice to simulate 2 iterations
375                let loop_body = &block.statements[i+1..loop_end-1];
376                
377                // First iteration
378                ownership_tracker.enter_loop();
379                
380                // Track variables declared in the loop (including nested If/else blocks)
381                let mut loop_local_vars = HashSet::new();
382                collect_loop_local_vars(&loop_body, &mut loop_local_vars);
383
384                // First iteration: process all statements
385                for (loop_idx, loop_stmt) in loop_body.iter().enumerate() {
386                    process_statement(loop_stmt, &mut ownership_tracker, &mut this_tracker, &mut errors, header_cache, function);
387
388                    // NEW: Check for last uses (after processing statement)
389                    // Statement index is i+1+loop_idx (i is EnterLoop, +1 for first statement)
390                    ownership_tracker.check_and_clear_last_uses(i + 1 + loop_idx);
391                }
392
393                // Save state after first iteration (but only for non-loop-local variables)
394                let state_after_first = ownership_tracker.ownership.clone();
395
396                // Clear loop-local borrows at end of first iteration
397                ownership_tracker.clear_loop_locals(&loop_local_vars);
398
399                // Second iteration - check for use-after-move
400                for (loop_idx, loop_stmt) in loop_body.iter().enumerate() {
401                    // Before processing each statement in second iteration,
402                    // check if it would cause use-after-move (but only for non-loop-local vars)
403                    check_statement_for_loop_errors(loop_stmt, &state_after_first, &loop_local_vars, &mut errors);
404                    process_statement(loop_stmt, &mut ownership_tracker, &mut this_tracker, &mut errors, header_cache, function);
405
406                    // NEW: Check for last uses (after processing statement)
407                    ownership_tracker.check_and_clear_last_uses(i + 1 + loop_idx);
408                }
409                
410                // Clear loop-local borrows at end of second iteration
411                ownership_tracker.clear_loop_locals(&loop_local_vars);
412                
413                ownership_tracker.exit_loop();
414                
415                // Skip past the loop
416                i = loop_end;
417            } else {
418                // Normal statement processing
419                process_statement(statement, &mut ownership_tracker, &mut this_tracker, &mut errors, header_cache, function);
420
421                // NEW: Check for last uses (after processing statement)
422                ownership_tracker.check_and_clear_last_uses(i);
423
424                i += 1;
425            }
426        }
427    }
428    
429    Ok(errors)
430}
431
432/// Recursively collect loop-local variables from statements, including nested If/else blocks.
433/// A loop-local variable is any variable declared/initialized inside the loop body.
434fn collect_loop_local_vars(statements: &[crate::ir::IrStatement], loop_local_vars: &mut HashSet<String>) {
435    for stmt in statements {
436        match stmt {
437            crate::ir::IrStatement::VarDecl { name, .. } => {
438                loop_local_vars.insert(name.clone());
439            }
440            crate::ir::IrStatement::Borrow { to, .. } => {
441                loop_local_vars.insert(to.clone());
442            }
443            crate::ir::IrStatement::Move { to, .. } => {
444                loop_local_vars.insert(to.clone());
445            }
446            crate::ir::IrStatement::Assign { lhs, .. } => {
447                loop_local_vars.insert(lhs.clone());
448            }
449            crate::ir::IrStatement::CallExpr { result: Some(var), .. } => {
450                loop_local_vars.insert(var.clone());
451            }
452            // Recursively search nested blocks
453            crate::ir::IrStatement::If { then_branch, else_branch } => {
454                collect_loop_local_vars(then_branch, loop_local_vars);
455                if let Some(else_stmts) = else_branch {
456                    collect_loop_local_vars(else_stmts, loop_local_vars);
457                }
458            }
459            _ => {}
460        }
461    }
462}
463
464/// Helper function to check for loop-specific errors in second iteration.
465/// Recursively checks nested If/else blocks.
466fn check_statement_for_loop_errors(
467    statement: &crate::ir::IrStatement,
468    state_after_first: &HashMap<String, OwnershipState>,
469    loop_local_vars: &HashSet<String>,
470    errors: &mut Vec<String>,
471) {
472    match statement {
473        crate::ir::IrStatement::Move { from, .. } => {
474            // Skip loop-local variables - they are fresh each iteration
475            // A variable declared inside the loop body (via Move, Borrow, or Assign)
476            // is a new variable on each iteration, not reused from previous iteration
477            if loop_local_vars.contains(from) {
478                return;
479            }
480            if let Some(state) = state_after_first.get(from) {
481                if *state == OwnershipState::Moved {
482                    errors.push(format!(
483                        "Use after move in loop: variable '{}' was moved in first iteration and used again in second iteration",
484                        from
485                    ));
486                }
487            }
488        }
489        crate::ir::IrStatement::Assign { rhs, .. } => {
490            if let crate::ir::IrExpression::Variable(var) = rhs {
491                // Skip loop-local variables - they are fresh each iteration
492                if loop_local_vars.contains(var) {
493                    return;
494                }
495                if let Some(state) = state_after_first.get(var) {
496                    if *state == OwnershipState::Moved {
497                        errors.push(format!(
498                            "Use after move in loop: variable '{}' was moved in first iteration and used again in second iteration",
499                            var
500                        ));
501                    }
502                }
503            }
504        }
505        // Recursively check nested If/else blocks
506        crate::ir::IrStatement::If { then_branch, else_branch } => {
507            for stmt in then_branch {
508                check_statement_for_loop_errors(stmt, state_after_first, loop_local_vars, errors);
509            }
510            if let Some(else_stmts) = else_branch {
511                for stmt in else_stmts {
512                    check_statement_for_loop_errors(stmt, state_after_first, loop_local_vars, errors);
513                }
514            }
515        }
516        _ => {}
517    }
518}
519
520// Phase 3: Helper function to check for borrow conflicts
521fn check_borrow_conflicts(
522    from: &str,
523    kind: &BorrowKind,
524    ownership_tracker: &OwnershipTracker,
525    errors: &mut Vec<String>,
526) -> bool {
527    let current_borrows = ownership_tracker.get_borrows(from);
528
529    match kind {
530        BorrowKind::Immutable => {
531            // Can have multiple immutable borrows, but not if there's a mutable borrow
532            if current_borrows.has_mutable {
533                errors.push(format!(
534                    "Cannot create immutable reference to '{}': already mutably borrowed",
535                    from
536                ));
537                return false;
538            }
539        }
540        BorrowKind::Mutable => {
541            // Can only have one mutable borrow, and no immutable borrows
542            if current_borrows.immutable_count > 0 {
543                errors.push(format!(
544                    "Cannot create mutable reference to '{}': already immutably borrowed",
545                    from
546                ));
547                return false;
548            } else if current_borrows.has_mutable {
549                errors.push(format!(
550                    "Cannot create mutable reference to '{}': already mutably borrowed",
551                    from
552                ));
553                return false;
554            }
555        }
556    }
557
558    true
559}
560
561/// Check for field-level borrow conflicts (partial borrow tracking)
562/// Returns false if there's a conflict, true if the borrow is allowed
563fn check_field_borrow_conflicts(
564    object: &str,
565    field: &str,
566    kind: &BorrowKind,
567    ownership_tracker: &OwnershipTracker,
568    errors: &mut Vec<String>,
569) -> bool {
570    // First, check if the whole object is already borrowed
571    let whole_object_borrows = ownership_tracker.get_borrows(object);
572    if whole_object_borrows.has_mutable {
573        errors.push(format!(
574            "Cannot borrow field '{}.{}': '{}' is already mutably borrowed",
575            object, field, object
576        ));
577        return false;
578    }
579    if whole_object_borrows.immutable_count > 0 && *kind == BorrowKind::Mutable {
580        errors.push(format!(
581            "Cannot mutably borrow field '{}.{}': '{}' is already immutably borrowed",
582            object, field, object
583        ));
584        return false;
585    }
586
587    // Now check field-level borrows
588    let field_borrows = ownership_tracker.get_field_borrows(object, field);
589
590    match kind {
591        BorrowKind::Immutable => {
592            // Can have multiple immutable borrows, but not if there's a mutable borrow
593            if field_borrows.has_mutable {
594                errors.push(format!(
595                    "Cannot create immutable reference to '{}.{}': already mutably borrowed",
596                    object, field
597                ));
598                return false;
599            }
600        }
601        BorrowKind::Mutable => {
602            // Can only have one mutable borrow, and no immutable borrows
603            if field_borrows.immutable_count > 0 {
604                errors.push(format!(
605                    "Cannot create mutable reference to '{}.{}': already immutably borrowed",
606                    object, field
607                ));
608                return false;
609            } else if field_borrows.has_mutable {
610                errors.push(format!(
611                    "Cannot create mutable reference to '{}.{}': already mutably borrowed",
612                    object, field
613                ));
614                return false;
615            }
616        }
617    }
618
619    true
620}
621
622/// Check if borrowing the whole object conflicts with existing field borrows
623fn check_whole_object_vs_field_borrows(
624    object: &str,
625    kind: &BorrowKind,
626    ownership_tracker: &OwnershipTracker,
627    errors: &mut Vec<String>,
628) -> bool {
629    // Check if any fields are borrowed
630    let borrowed_fields = ownership_tracker.get_borrowed_fields(object);
631
632    if !borrowed_fields.is_empty() {
633        let field_list: Vec<String> = borrowed_fields.iter().map(|(f, _)| f.clone()).collect();
634
635        match kind {
636            BorrowKind::Mutable => {
637                errors.push(format!(
638                    "Cannot mutably borrow '{}': fields are already borrowed ({})",
639                    object, field_list.join(", ")
640                ));
641                return false;
642            }
643            BorrowKind::Immutable => {
644                // Check if any field is mutably borrowed
645                let any_mutable = borrowed_fields.iter().any(|(_, is_mut)| *is_mut);
646                if any_mutable {
647                    errors.push(format!(
648                        "Cannot immutably borrow '{}': field is already mutably borrowed",
649                        object
650                    ));
651                    return false;
652                }
653                // Multiple immutable is OK
654            }
655        }
656    }
657
658    true
659}
660
661// Extract statement processing logic into a separate function
662// Phase 2: Added header_cache and function parameters for return value borrow detection
663fn process_statement(
664    statement: &crate::ir::IrStatement,
665    ownership_tracker: &mut OwnershipTracker,
666    this_tracker: &mut Option<this_tracking::ThisPointerTracker>,
667    errors: &mut Vec<String>,
668    header_cache: &HeaderCache,  // Phase 2: For looking up function signatures
669    function: &IrFunction,       // Phase 2: For checking variable types
670) {
671    match statement {
672        crate::ir::IrStatement::Move { from, to } => {
673            debug_println!("DEBUG ANALYSIS: Processing Move from '{}' to '{}'", from, to);
674            // Skip checks if we're in an unsafe block
675            if ownership_tracker.is_in_unsafe_block() {
676                // Still update ownership state for consistency
677                ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
678                ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
679                return;
680            }
681            
682            // Check if 'from' is owned and not moved
683            let from_state = ownership_tracker.get_ownership(from);
684            debug_println!("DEBUG ANALYSIS: '{}' state: {:?}", from, from_state);
685            
686            // Can't move from a reference
687            if ownership_tracker.is_reference(from) {
688                errors.push(format!(
689                    "Cannot move out of '{}' because it is behind a reference",
690                    from
691                ));
692                return;
693            }
694
695            // Phase 4: Can't move from a variable that is transitively borrowed
696            // Check both direct borrows AND transitive borrows (borrow chains)
697            if ownership_tracker.is_transitively_borrowed(from) {
698                let borrowers = ownership_tracker.get_transitive_borrowers(from);
699                errors.push(format!(
700                    "Cannot move '{}' because it is borrowed by: {}",
701                    from,
702                    borrowers.join(", ")
703                ));
704                return;
705            }
706
707            // REASSIGNMENT TRACKING: Check if 'to' has active borrows
708            // In Rust, assignment drops the old value first, so we can't assign if borrowed
709            // Example: box1 = std::move(box2); drops old value of box1
710            if let Some(borrows) = ownership_tracker.get_active_borrows(to) {
711                if !borrows.is_empty() {
712                    let borrower_names: Vec<String> = borrows.iter().map(|b| b.borrower.clone()).collect();
713                    errors.push(format!(
714                        "Cannot assign to '{}' because it is borrowed by: {} (assignment would drop the old value)",
715                        to,
716                        borrower_names.join(", ")
717                    ));
718                    return;
719                }
720            }
721
722            if from_state == Some(&OwnershipState::Moved) {
723                errors.push(format!(
724                    "Use after move: variable '{}' has already been moved",
725                    from
726                ));
727            }
728
729            // NEW: Check if the object has any moved fields (partial move)
730            if ownership_tracker.has_moved_fields(from) {
731                let moved_fields = ownership_tracker.get_moved_fields(from);
732                errors.push(format!(
733                    "Cannot move '{}' because it has been partially moved (moved fields: {})",
734                    from,
735                    moved_fields.join(", ")
736                ));
737                return;
738            }
739
740            // Handle temporary move markers (from std::move in function calls)
741            if to.starts_with("_temp_move_") || to.starts_with("_moved_") {
742                // Just mark the source as moved, don't create the temporary
743                ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
744            } else {
745                // Transfer ownership for regular moves
746                ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
747                ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
748            }
749        }
750        
751        // NEW: Handle field-level operations
752        crate::ir::IrStatement::MoveField { object, field, to } => {
753            debug_println!("DEBUG ANALYSIS: Processing MoveField from '{}.{}' to '{}'", object, field, to);
754
755            // Skip checks if we're in an unsafe block
756            if ownership_tracker.is_in_unsafe_block() {
757                ownership_tracker.mark_field_moved(object.clone(), field.clone());
758                ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
759                return;
760            }
761
762            // Check if the object itself has been moved
763            let object_state = ownership_tracker.get_ownership(object);
764            if object_state == Some(&OwnershipState::Moved) {
765                errors.push(format!(
766                    "Cannot move field '{}' from '{}' because '{}' has been moved",
767                    field, object, object
768                ));
769                return;
770            }
771
772            // Check if the field has already been moved
773            let field_state = ownership_tracker.get_field_ownership(object, field);
774            if field_state == OwnershipState::Moved {
775                errors.push(format!(
776                    "Use after move: field '{}.{}' has already been moved",
777                    object, field
778                ));
779                return;
780            }
781
782            // Phase 4: Check if the object is transitively borrowed
783            if ownership_tracker.is_transitively_borrowed(object) {
784                let borrowers = ownership_tracker.get_transitive_borrowers(object);
785                errors.push(format!(
786                    "Cannot move field '{}.{}' because '{}' is borrowed by: {}",
787                    object, field, object, borrowers.join(", ")
788                ));
789                return;
790            }
791
792            // NEW: Check method qualifier restrictions on field moves
793            // If object is "this" (or we're in a method context), check this pointer rules
794            if let Some(tracker) = this_tracker {
795                if object == "this" {
796                    if let Err(err) = tracker.can_move_member(field) {
797                        errors.push(err);
798                        return;
799                    }
800                }
801            }
802
803            // Mark the field as moved
804            ownership_tracker.mark_field_moved(object.clone(), field.clone());
805            ownership_tracker.set_ownership(to.clone(), OwnershipState::Owned);
806
807            // Update this tracker state if this is a field of 'this'
808            if let Some(tracker) = this_tracker {
809                if object == "this" {
810                    tracker.mark_field_moved(field.clone());
811                }
812            }
813        }
814
815        crate::ir::IrStatement::UseField { object, field, operation } => {
816            debug_println!("DEBUG ANALYSIS: UseField object='{}', field='{}', operation='{}'", object, field, operation);
817
818            // Skip checking if we're in an unsafe block
819            if ownership_tracker.is_in_unsafe_block() {
820                return;
821            }
822
823            // Check if the object has been moved
824            let object_state = ownership_tracker.get_ownership(object);
825            if object_state == Some(&OwnershipState::Moved) {
826                errors.push(format!(
827                    "Cannot {} field '{}.{}' because '{}' has been moved",
828                    operation, object, field, object
829                ));
830                return;
831            }
832
833            // Check if the field has been moved
834            let field_state = ownership_tracker.get_field_ownership(object, field);
835            if field_state == OwnershipState::Moved {
836                errors.push(format!(
837                    "Cannot {} field '{}.{}' because it has been moved",
838                    operation, object, field
839                ));
840                return;
841            }
842
843            // NEW: Check method qualifier restrictions on field usage
844            if let Some(tracker) = this_tracker {
845                if object == "this" {
846                    // For read operations, just check if we can read
847                    if operation == "read" {
848                        if let Err(err) = tracker.can_read_member(field) {
849                            errors.push(err);
850                            return;
851                        }
852                    }
853                    // For write operations, check if we can modify
854                    else if operation == "write" {
855                        if let Err(err) = tracker.can_modify_member(field) {
856                            errors.push(err);
857                            return;
858                        }
859                    }
860                }
861            }
862        }
863
864        crate::ir::IrStatement::BorrowField { object, field, to, kind } => {
865            debug_println!("DEBUG ANALYSIS: BorrowField from '{}.{}' to '{}'", object, field, to);
866
867            // Skip checking if we're in an unsafe block
868            if ownership_tracker.is_in_unsafe_block() {
869                // Still record the borrow for consistency
870                ownership_tracker.add_field_borrow(object.clone(), field.clone(), to.clone(), kind.clone());
871                ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
872                return;
873            }
874
875            // Check if the object has been moved
876            let object_state = ownership_tracker.get_ownership(object);
877            if object_state == Some(&OwnershipState::Moved) {
878                errors.push(format!(
879                    "Cannot borrow field '{}.{}' because '{}' has been moved",
880                    field, object, object
881                ));
882                return;
883            }
884
885            // Check if the field has been moved
886            let field_state = ownership_tracker.get_field_ownership(object, field);
887            if field_state == OwnershipState::Moved {
888                errors.push(format!(
889                    "Cannot borrow field '{}.{}' because it has been moved",
890                    object, field
891                ));
892                return;
893            }
894
895            // NEW: Check method qualifier restrictions on field borrows
896            if let Some(tracker) = this_tracker {
897                if object == "this" {
898                    if let Err(err) = tracker.can_borrow_member(field, kind.clone()) {
899                        errors.push(err);
900                        return;
901                    }
902                }
903            }
904
905            // NEW: Check for field-level borrow conflicts (Partial Borrow Tracking)
906            if !check_field_borrow_conflicts(object, field, kind, ownership_tracker, errors) {
907                return;
908            }
909
910            // Record the field-level borrow (NOT whole object)
911            ownership_tracker.add_field_borrow(object.clone(), field.clone(), to.clone(), kind.clone());
912            ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
913
914            // Update this tracker state if this is a field of 'this'
915            if let Some(tracker) = this_tracker {
916                if object == "this" {
917                    tracker.mark_field_borrowed(field.clone(), kind.clone());
918                }
919            }
920        }
921
922        crate::ir::IrStatement::Borrow { from, to, kind } => {
923            // Skip checks if we're in an unsafe block
924            if ownership_tracker.is_in_unsafe_block() {
925                // Still record the borrow for consistency
926                ownership_tracker.add_borrow(from.clone(), to.clone(), kind.clone());
927                ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
928                return;
929            }
930
931            // Check if the source is accessible
932            let from_state = ownership_tracker.get_ownership(from);
933
934            if from_state == Some(&OwnershipState::Moved) {
935                errors.push(format!(
936                    "Cannot borrow '{}' because it has been moved",
937                    from
938                ));
939                return;
940            }
941
942            // Rust-like reference assignment semantics:
943            // - Mutable references (&mut T) are NOT Copy - assigning moves the reference
944            // - Immutable references (&T) ARE Copy - assigning copies the reference
945            let from_is_mutable_ref = ownership_tracker.is_mutable_reference(from);
946            let from_is_immutable_ref = ownership_tracker.is_reference(from) && !from_is_mutable_ref;
947
948            // Phase 3: Check for borrow conflicts using helper function
949            // Skip conflict checking for reference-to-reference assignments (they don't create new borrows on the underlying object)
950            if !from_is_mutable_ref && !from_is_immutable_ref {
951                if !check_borrow_conflicts(from, kind, ownership_tracker, errors) {
952                    return;
953                }
954
955                // NEW: Check if whole-object borrow conflicts with existing field borrows
956                if !check_whole_object_vs_field_borrows(from, kind, ownership_tracker, errors) {
957                    return;
958                }
959            }
960
961            // Record the borrow
962            ownership_tracker.add_borrow(from.clone(), to.clone(), kind.clone());
963            ownership_tracker.mark_as_reference(to.clone(), *kind == BorrowKind::Mutable);
964
965            // If `from` is a mutable reference, mark it as moved (mutable refs are not Copy)
966            // If `from` is an immutable reference, it remains valid (immutable refs are Copy)
967            if from_is_mutable_ref {
968                ownership_tracker.set_ownership(from.clone(), OwnershipState::Moved);
969            }
970            // Immutable references are Copy - from remains valid
971        }
972        
973        crate::ir::IrStatement::Assign { lhs, rhs } => {
974            // Skip checks if we're in an unsafe block
975            if ownership_tracker.is_in_unsafe_block() {
976                return;
977            }
978            
979            // Check if we're trying to modify through a const reference
980            if ownership_tracker.is_reference(lhs) && !ownership_tracker.is_mutable_reference(lhs) {
981                errors.push(format!(
982                    "Cannot assign to '{}' through const reference",
983                    lhs
984                ));
985            }
986            
987            // Check if the rhs uses a moved variable
988            if let crate::ir::IrExpression::Variable(rhs_var) = rhs {
989                if ownership_tracker.get_ownership(rhs_var) == Some(&OwnershipState::Moved) {
990                    errors.push(format!(
991                        "Use after move: variable '{}' has been moved",
992                        rhs_var
993                    ));
994                }
995            }
996
997            // REASSIGNMENT FIX: After assignment, lhs becomes Owned again
998            // This handles the case where a moved variable is reassigned a new value
999            // Example: x = std::move(y); x = 42;  // x is valid again after reassignment
1000            if !ownership_tracker.is_reference(lhs) {
1001                ownership_tracker.set_ownership(lhs.clone(), OwnershipState::Owned);
1002            }
1003        }
1004
1005        crate::ir::IrStatement::Drop(var) => {
1006            debug_println!("DEBUG ANALYSIS: Processing explicit Drop for '{}'", var);
1007            // Skip checks if we're in an unsafe block
1008            if ownership_tracker.is_in_unsafe_block() {
1009                // Don't mark as moved - the subsequent assignment will handle ownership
1010                return;
1011            }
1012
1013            // Check if the variable has active borrows
1014            // Explicit Drop (e.g., from reassignment of RAII type) checks borrows
1015            if let Some(borrows) = ownership_tracker.get_active_borrows(var) {
1016                if !borrows.is_empty() {
1017                    let borrower_names: Vec<String> = borrows.iter().map(|b| b.borrower.clone()).collect();
1018                    errors.push(format!(
1019                        "Cannot assign to '{}' because it is borrowed by: {} (assignment would drop the old value)",
1020                        var,
1021                        borrower_names.join(", ")
1022                    ));
1023                    return;
1024                }
1025            }
1026
1027            // Check if variable is already moved
1028            let state = ownership_tracker.get_ownership(var);
1029            if state == Some(&OwnershipState::Moved) {
1030                debug_println!("DEBUG ANALYSIS: Skipping drop check for '{}' - already moved", var);
1031                // Still allow the drop check, but subsequent assignment will fail
1032            }
1033
1034            // For explicit Drop (reassignment), we only check borrows.
1035            // We do NOT mark the variable as moved here!
1036            // The subsequent assignment IR statement (CallExpr, Assign, Move) will
1037            // handle the actual ownership transfer.
1038            debug_println!("DEBUG ANALYSIS: Drop check passed for '{}' - subsequent assignment will transfer ownership", var);
1039        }
1040
1041        crate::ir::IrStatement::ImplicitDrop { var, has_destructor, .. } => {
1042            debug_println!("DEBUG ANALYSIS: Processing ImplicitDrop for '{}' (has_destructor={})", var, has_destructor);
1043            // Skip checks if we're in an unsafe block
1044            if ownership_tracker.is_in_unsafe_block() {
1045                // Still update ownership state for consistency (only for RAII types)
1046                if *has_destructor {
1047                    ownership_tracker.set_ownership(var.clone(), OwnershipState::Moved);
1048                }
1049                // Always clear borrows from the variable (references and RAII types)
1050                ownership_tracker.clear_borrows_from(var);
1051                return;
1052            }
1053
1054            // Only check active borrows for RAII types (which actually drop)
1055            // References just clear their borrows without error checking
1056            if *has_destructor {
1057                // Check if the variable has active borrows
1058                // In Rust, implicit drop (scope end) is a move/consume operation
1059                if let Some(borrows) = ownership_tracker.get_active_borrows(var) {
1060                    if !borrows.is_empty() {
1061                        let borrower_names: Vec<String> = borrows.iter().map(|b| b.borrower.clone()).collect();
1062                        errors.push(format!(
1063                            "Cannot drop '{}' because it is borrowed by: {} (implicit drop at scope end)",
1064                            var,
1065                            borrower_names.join(", ")
1066                        ));
1067                        return;
1068                    }
1069                }
1070
1071                // Check if variable is already moved
1072                let state = ownership_tracker.get_ownership(var);
1073                if state == Some(&OwnershipState::Moved) {
1074                    debug_println!("DEBUG ANALYSIS: Skipping implicit drop for '{}' - already moved", var);
1075                    return;  // Don't drop if already moved
1076                }
1077
1078                // Mark as dropped (moved/consumed) - only for RAII types
1079                debug_println!("DEBUG ANALYSIS: Marking '{}' as dropped (implicit drop)", var);
1080                ownership_tracker.set_ownership(var.clone(), OwnershipState::Moved);
1081            }
1082
1083            // NEW: Always clear borrows FROM this variable (for both RAII and non-RAII)
1084            // When a variable goes out of scope, any references it made become invalid
1085            // In C++, variables drop in reverse declaration order, so clearing borrows
1086            // after each "drop" simulates this correctly
1087            debug_println!("DEBUG ANALYSIS: Clearing borrows from '{}'", var);
1088            ownership_tracker.clear_borrows_from(var);
1089        }
1090
1091        crate::ir::IrStatement::EnterScope => {
1092            ownership_tracker.enter_scope();
1093        }
1094        
1095        crate::ir::IrStatement::ExitScope => {
1096            // Before exiting scope, check for dangling references
1097            // A dangling reference occurs when:
1098            // 1. A variable x is defined in the current scope (will die)
1099            // 2. A reference ref from an outer scope borrows from x
1100            // 3. ref will outlive x, becoming a dangling reference
1101            let current_scope = ownership_tracker.scope_stack.len();
1102
1103            // Find all variables defined at the current scope level
1104            for (var_name, var_info) in &function.variables {
1105                if var_info.scope_level == current_scope {
1106                    // This variable is dying - check if any outer-scope references borrow from it
1107                    if let Some(active_borrows) = ownership_tracker.active_borrows.get(var_name) {
1108                        for borrow in active_borrows {
1109                            // Check if the borrower (reference) is from an outer scope
1110                            if borrow.scope < current_scope {
1111                                errors.push(format!(
1112                                    "Dangling reference: '{}' borrows from '{}' which goes out of scope",
1113                                    borrow.borrower, var_name
1114                                ));
1115                            }
1116                        }
1117                    }
1118                }
1119            }
1120
1121            ownership_tracker.exit_scope();
1122        }
1123        
1124        crate::ir::IrStatement::EnterLoop => {
1125            // Handled at the higher level
1126        }
1127        
1128        crate::ir::IrStatement::ExitLoop => {
1129            // Handled at the higher level
1130        }
1131        
1132        crate::ir::IrStatement::EnterUnsafe => {
1133            ownership_tracker.unsafe_depth += 1;
1134        }
1135        
1136        crate::ir::IrStatement::ExitUnsafe => {
1137            if ownership_tracker.unsafe_depth > 0 {
1138                ownership_tracker.unsafe_depth -= 1;
1139            }
1140        }
1141        
1142        crate::ir::IrStatement::If { then_branch, else_branch } => {
1143            // Skip checking if we're in an unsafe block
1144            if ownership_tracker.is_in_unsafe_block() {
1145                return;
1146            }
1147            // Handle conditional execution with path-sensitive analysis
1148            // Save current state before branching
1149            let state_before_if = ownership_tracker.clone_state();
1150            
1151            // Process then branch
1152            for stmt in then_branch {
1153                process_statement(stmt, ownership_tracker, this_tracker, errors, header_cache, function);
1154            }
1155            let state_after_then = ownership_tracker.clone_state();
1156
1157            // Restore state and process else branch if it exists
1158            ownership_tracker.restore_state(&state_before_if);
1159
1160            if let Some(else_stmts) = else_branch {
1161                for stmt in else_stmts {
1162                    process_statement(stmt, ownership_tracker, this_tracker, errors, header_cache, function);
1163                }
1164                let state_after_else = ownership_tracker.clone_state();
1165
1166                // Merge states: a variable is moved if moved in ANY branch (Rust's aggressive approach)
1167                ownership_tracker.merge_states(&state_after_then, &state_after_else);
1168            } else {
1169                // No else branch: merge with original state
1170                // Variable is moved if moved in then branch (aggressive approach)
1171                ownership_tracker.merge_states(&state_after_then, &state_before_if);
1172            }
1173        }
1174
1175        crate::ir::IrStatement::UseVariable { var, operation } => {
1176            debug_println!("DEBUG ANALYSIS: UseVariable var='{}', operation='{}'", var, operation);
1177
1178            // Skip checking if we're in an unsafe block
1179            if ownership_tracker.is_in_unsafe_block() {
1180                debug_println!("DEBUG ANALYSIS: Skipping check - in unsafe block");
1181                return;
1182            }
1183
1184            // Check if the variable has been moved
1185            let var_state = ownership_tracker.get_ownership(var);
1186            debug_println!("DEBUG ANALYSIS: var_state for '{}' = {:?}", var, var_state);
1187
1188            if var_state == Some(&OwnershipState::Moved) {
1189                errors.push(format!(
1190                    "Use after move: cannot {} variable '{}' because it has been moved",
1191                    operation, var
1192                ));
1193            }
1194        }
1195
1196        crate::ir::IrStatement::Return { value } => {
1197            // Skip if in unsafe block
1198            if ownership_tracker.is_in_unsafe_block() {
1199                return;
1200            }
1201
1202            if let Some(val) = value {
1203                // Check if returning a moved value
1204                let var_state = ownership_tracker.get_ownership(val);
1205
1206                if var_state == Some(&OwnershipState::Moved) {
1207                    errors.push(format!(
1208                        "Cannot return '{}' because it has been moved",
1209                        val
1210                    ));
1211                }
1212            }
1213        }
1214
1215        crate::ir::IrStatement::PackExpansion { pack_name, operation } => {
1216            // Phase 4: Handle pack expansion semantics
1217            debug_println!("DEBUG ANALYSIS: PackExpansion pack='{}', operation='{}'", pack_name, operation);
1218
1219            // Skip checking if we're in an unsafe block
1220            if ownership_tracker.is_in_unsafe_block() {
1221                debug_println!("DEBUG ANALYSIS: Skipping pack check - in unsafe block");
1222                return;
1223            }
1224
1225            // Check if the pack has been moved
1226            let pack_state = ownership_tracker.get_ownership(pack_name);
1227            debug_println!("DEBUG ANALYSIS: pack_state for '{}' = {:?}", pack_name, pack_state);
1228
1229            if pack_state == Some(&OwnershipState::Moved) {
1230                errors.push(format!(
1231                    "Use after move: cannot use pack '{}' because it has been moved",
1232                    pack_name
1233                ));
1234                return;
1235            }
1236
1237            // Apply operation-specific semantics
1238            match operation.as_str() {
1239                "move" | "forward" => {
1240                    // Move or forward consumes the pack
1241                    debug_println!("DEBUG ANALYSIS: Pack '{}' is being moved/forwarded", pack_name);
1242                    ownership_tracker.set_ownership(pack_name.clone(), OwnershipState::Moved);
1243                }
1244                "use" => {
1245                    // Regular use creates implicit immutable borrows
1246                    // (packs are pass-by-value, so this doesn't create lasting borrows)
1247                    debug_println!("DEBUG ANALYSIS: Pack '{}' is being used (immutable)", pack_name);
1248                    // No state change needed for use
1249                }
1250                _ => {
1251                    debug_println!("DEBUG ANALYSIS: Unknown pack operation '{}'", operation);
1252                }
1253            }
1254        }
1255
1256        // Phase 2: Handle CallExpr - detect return value borrows
1257        crate::ir::IrStatement::CallExpr { func, args, result } => {
1258            debug_println!("DEBUG ANALYSIS PHASE2: CallExpr func='{}', args={:?}, result={:?}", func, args, result);
1259
1260            // Skip if in unsafe block
1261            if ownership_tracker.is_in_unsafe_block() {
1262                return;
1263            }
1264
1265            // Skip if no result variable (void return)
1266            let result_var = match result {
1267                Some(r) => r,
1268                None => return,
1269            };
1270
1271            debug_println!("DEBUG ANALYSIS PHASE2: Processing call result '{}'", result_var);
1272
1273            // Phase 2: Detect return value borrows from lifetime annotations
1274            // Try to get the function signature from HeaderCache
1275            if let Some(signature) = header_cache.get_signature(func) {
1276                debug_println!("DEBUG ANALYSIS PHASE2: Found signature for function '{}'", func);
1277
1278                // Check if the function has lifetime annotations
1279                if !signature.param_lifetimes.is_empty() || signature.return_lifetime.is_some() {
1280                    debug_println!("DEBUG ANALYSIS PHASE2: Function '{}' has lifetime annotations", func);
1281
1282                    // Check if return type has a lifetime annotation
1283                    if let Some(ret_lifetime) = &signature.return_lifetime {
1284                        debug_println!("DEBUG ANALYSIS PHASE2: Return lifetime annotation found");
1285
1286                        // Find which parameter has a matching lifetime
1287                        for (param_idx, param_lifetime_opt) in signature.param_lifetimes.iter().enumerate() {
1288                            if let Some(param_lifetime) = param_lifetime_opt {
1289                                // Check if lifetimes match (compare lifetime names)
1290                                let ret_lifetime_name = match ret_lifetime {
1291                                    crate::parser::annotations::LifetimeAnnotation::Ref(name) |
1292                                    crate::parser::annotations::LifetimeAnnotation::MutRef(name) |
1293                                    crate::parser::annotations::LifetimeAnnotation::Lifetime(name) => Some(name),
1294                                    _ => None,
1295                                };
1296
1297                                let param_lifetime_name = match param_lifetime {
1298                                    crate::parser::annotations::LifetimeAnnotation::Ref(name) |
1299                                    crate::parser::annotations::LifetimeAnnotation::MutRef(name) |
1300                                    crate::parser::annotations::LifetimeAnnotation::Lifetime(name) => Some(name),
1301                                    _ => None,
1302                                };
1303
1304                                // If lifetimes match, the return value borrows from this parameter
1305                                if ret_lifetime_name.is_some() && ret_lifetime_name == param_lifetime_name {
1306                                    debug_println!("DEBUG ANALYSIS PHASE2: Found matching lifetime '{}' between return and param {}",
1307                                        ret_lifetime_name.unwrap(), param_idx);
1308
1309                                    // Get the parameter variable name from args
1310                                    if param_idx < args.len() {
1311                                        let borrowed_var = &args[param_idx];
1312                                        debug_println!("DEBUG ANALYSIS PHASE2: Return value '{}' borrows from parameter '{}'",
1313                                            result_var, borrowed_var);
1314
1315                                        // CROSS-FUNCTION LIFETIME CHECK: Detect temporaries
1316                                        // If the borrowed variable is a temporary (literal or expression),
1317                                        // the return value would be a dangling reference
1318                                        if borrowed_var.starts_with("_temp_literal_") || borrowed_var.starts_with("_temp_expr_") {
1319                                            debug_println!("DEBUG ANALYSIS: Detected dangling reference from temporary argument");
1320                                            errors.push(format!(
1321                                                "Dangling reference: function '{}' returns reference tied to temporary argument",
1322                                                func
1323                                            ));
1324                                            break;  // Don't process further
1325                                        }
1326
1327                                        // Determine borrow kind from RETURN annotation (not param)
1328                                        // If return annotation is &'a mut -> mutable, otherwise immutable
1329                                        // Also check the actual C++ variable type as fallback
1330                                        let borrow_kind = match ret_lifetime {
1331                                            crate::parser::annotations::LifetimeAnnotation::MutRef(_) => BorrowKind::Mutable,
1332                                            _ => {
1333                                                // Fallback: check the actual C++ variable type
1334                                                if let Some(var_info) = function.variables.get(result_var) {
1335                                                    if matches!(var_info.ty, crate::ir::VariableType::MutableReference(_)) {
1336                                                        BorrowKind::Mutable
1337                                                    } else {
1338                                                        BorrowKind::Immutable
1339                                                    }
1340                                                } else {
1341                                                    BorrowKind::Immutable
1342                                                }
1343                                            }
1344                                        };
1345
1346                                        // Record the borrow with MethodReturnValue source
1347                                        let borrow_source = BorrowSource::MethodReturnValue {
1348                                            method: func.clone(),
1349                                            receiver: borrowed_var.clone(),
1350                                        };
1351
1352                                        // Phase 2 FIX: Only create borrow if return type isn't "owned"
1353                                        // The lifetime annotation tells us whether a borrow exists, not the C++ type
1354                                        // Exception: For primitive types (int, bool, etc.) that are value types,
1355                                        // only create borrow if result variable is actually a reference
1356                                        debug_println!("DEBUG ANALYSIS PHASE2: Checking if should create borrow for '{}'", result_var);
1357                                        debug_println!("DEBUG ANALYSIS PHASE2: ret_lifetime.is_owned() = {}", ret_lifetime.is_owned());
1358
1359                                        let should_create_borrow = if !ret_lifetime.is_owned() {
1360                                            // Return type has a lifetime annotation - check if we should create borrow
1361                                            if let Some(var_info) = function.variables.get(result_var) {
1362                                                debug_println!("DEBUG ANALYSIS PHASE2: Found var_info for '{}', type = {:?}", result_var, var_info.ty);
1363                                                let is_ref = matches!(var_info.ty,
1364                                                    crate::ir::VariableType::Reference(_) |
1365                                                    crate::ir::VariableType::MutableReference(_));
1366                                                debug_println!("DEBUG ANALYSIS PHASE2: is_ref = {}", is_ref);
1367
1368                                                let is_complex = matches!(&var_info.ty, crate::ir::VariableType::Owned(type_name)
1369                                                    if !is_primitive_type(type_name));
1370                                                debug_println!("DEBUG ANALYSIS PHASE2: is_complex = {}", is_complex);
1371
1372                                                is_ref || is_complex
1373                                            } else {
1374                                                debug_println!("DEBUG ANALYSIS PHASE2: No var_info found for '{}', assuming complex", result_var);
1375                                                // Unknown variable - assume it could be a complex type
1376                                                true
1377                                            }
1378                                        } else {
1379                                            debug_println!("DEBUG ANALYSIS PHASE2: Return type is owned - no borrow");
1380                                            // Return type is "owned" - no borrow
1381                                            false
1382                                        };
1383
1384                                        debug_println!("DEBUG ANALYSIS PHASE2: should_create_borrow = {}", should_create_borrow);
1385
1386                                        if should_create_borrow {
1387                                            debug_println!("DEBUG ANALYSIS PHASE2: Adding {} borrow: '{}' -> '{}' (result is reference type)",
1388                                                if borrow_kind == BorrowKind::Mutable { "mutable" } else { "immutable" },
1389                                                borrowed_var, result_var);
1390
1391                                            // Phase 3: Check for borrow conflicts before creating the borrow
1392                                            if !check_borrow_conflicts(borrowed_var, &borrow_kind, ownership_tracker, errors) {
1393                                                debug_println!("DEBUG ANALYSIS PHASE3: Borrow conflict detected for '{}'", borrowed_var);
1394                                                break;  // Don't create the borrow
1395                                            }
1396
1397                                            let is_mutable = borrow_kind == BorrowKind::Mutable;
1398
1399                                            ownership_tracker.add_borrow_with_source(
1400                                                borrowed_var.clone(),
1401                                                result_var.clone(),
1402                                                borrow_kind,
1403                                                borrow_source
1404                                            );
1405
1406                                            // Mark result as a reference
1407                                            ownership_tracker.mark_as_reference(
1408                                                result_var.clone(),
1409                                                is_mutable
1410                                            );
1411                                        } else {
1412                                            debug_println!("DEBUG ANALYSIS PHASE2: Skipping borrow creation for '{}' - result is value type, not reference",
1413                                                result_var);
1414                                        }
1415
1416                                        // Only process first matching lifetime
1417                                        break;
1418                                    }
1419                                }
1420                            }
1421                        }
1422                    }
1423                }
1424            } else {
1425                debug_println!("DEBUG ANALYSIS PHASE2: No signature found for function '{}'", func);
1426            }
1427        }
1428
1429        _ => {}
1430    }
1431}
1432
1433struct OwnershipTracker {
1434    ownership: HashMap<String, OwnershipState>,
1435    borrows: HashMap<String, BorrowInfo>,
1436    reference_info: HashMap<String, ReferenceInfo>,
1437    // Stack of scopes, each scope tracks borrows created in it
1438    scope_stack: Vec<ScopeInfo>,
1439    // Loop tracking
1440    loop_depth: usize,
1441    // Save state when entering a loop (for 2nd iteration checking)
1442    loop_entry_states: Vec<LoopEntryState>,
1443    // Track if we're in an unsafe block
1444    unsafe_depth: usize,
1445    // Track active borrows: which variables are currently borrowed from
1446    // Key: variable being borrowed from, Value: list of active borrows on it
1447    active_borrows: HashMap<String, Vec<ActiveBorrow>>,
1448    // NEW: Track field-level ownership state
1449    // Key: object name, Value: map of field name to ownership state
1450    field_ownership: HashMap<String, HashMap<String, OwnershipState>>,
1451    // NEW: Track field-level borrows (for partial borrow tracking)
1452    // Key: object name, Value: map of field name to borrow info
1453    field_borrows: HashMap<String, HashMap<String, BorrowInfo>>,
1454    // NEW: Track method context (are we in a method? is 'this' owned or borrowed?)
1455    this_context: Option<ThisContext>,
1456    // NEW: Liveness analysis - track last use of variables
1457    // Key: variable name, Value: statement index of last use
1458    last_use_map: HashMap<String, usize>,
1459}
1460
1461#[derive(Clone)]
1462struct TrackerState {
1463    ownership: HashMap<String, OwnershipState>,
1464    borrows: HashMap<String, BorrowInfo>,
1465    reference_info: HashMap<String, ReferenceInfo>,
1466    active_borrows: HashMap<String, Vec<ActiveBorrow>>,
1467    // NEW: Field-level ownership tracking
1468    field_ownership: HashMap<String, HashMap<String, OwnershipState>>,
1469    // NEW: Field-level borrow tracking
1470    field_borrows: HashMap<String, HashMap<String, BorrowInfo>>,
1471}
1472
1473#[derive(Clone)]
1474struct LoopEntryState {
1475    ownership: HashMap<String, OwnershipState>,
1476    #[allow(dead_code)]
1477    borrows: HashMap<String, BorrowInfo>,
1478}
1479
1480#[derive(Default, Clone)]
1481struct ScopeInfo {
1482    // Borrows created in this scope (to be cleaned up on exit)
1483    local_borrows: HashSet<String>,
1484}
1485
1486#[derive(Default, Clone)]
1487struct BorrowInfo {
1488    immutable_count: usize,
1489    has_mutable: bool,
1490    borrowers: HashSet<String>,
1491}
1492
1493#[derive(Clone)]
1494struct ReferenceInfo {
1495    is_reference: bool,
1496    is_mutable: bool,
1497}
1498
1499// Track active borrows: when a variable is borrowed by a reference,
1500// we need to prevent moving the borrowed variable
1501#[derive(Clone, Debug)]
1502struct ActiveBorrow {
1503    borrower: String,      // The reference variable that is borrowing (e.g., "ref")
1504    borrowed_from: String, // The variable being borrowed from (e.g., "ptr")
1505    kind: BorrowKind,
1506    scope: usize,          // Scope level where this borrow was created
1507    // Phase 2: Track HOW the borrow was created
1508    source: BorrowSource,
1509}
1510
1511// Phase 2: Represents how a borrow was created
1512#[derive(Clone, Debug, PartialEq)]
1513enum BorrowSource {
1514    DirectReference,          // T& ref = value;
1515    MethodReturnValue {       // auto x = obj.method();
1516        method: String,       // Method name (e.g., "as_ref", "as_mut")
1517        receiver: String,     // Object the method was called on
1518    },
1519    FieldAccess {             // auto& x = obj.field;
1520        object: String,
1521        field: String,
1522    },
1523}
1524
1525// Track method context: how is 'this' accessed in methods?
1526#[derive(Clone, Debug, PartialEq)]
1527enum ThisContext {
1528    Borrowed,       // Regular method - implicit &self
1529    MutBorrowed,    // Mutable method - implicit &mut self
1530    ConstBorrowed,  // Const method - const &self
1531    Consumed,       // Rvalue ref method - implicit &&self (owned)
1532}
1533
1534impl OwnershipTracker {
1535    fn new() -> Self {
1536        Self::with_liveness(HashMap::new())
1537    }
1538
1539    fn with_liveness(last_use_map: HashMap<String, usize>) -> Self {
1540        let mut tracker = Self {
1541            ownership: HashMap::new(),
1542            borrows: HashMap::new(),
1543            reference_info: HashMap::new(),
1544            scope_stack: Vec::new(),
1545            loop_depth: 0,
1546            loop_entry_states: Vec::new(),
1547            unsafe_depth: 0,
1548            active_borrows: HashMap::new(),
1549            field_ownership: HashMap::new(),  // NEW
1550            field_borrows: HashMap::new(),    // NEW: Partial borrow tracking
1551            this_context: None,                // NEW
1552            last_use_map,                      // NEW: Liveness analysis
1553        };
1554        // Start with a root scope
1555        tracker.scope_stack.push(ScopeInfo::default());
1556        tracker
1557    }
1558    
1559    fn is_in_unsafe_block(&self) -> bool {
1560        self.unsafe_depth > 0
1561    }
1562    
1563    fn set_ownership(&mut self, var: String, state: OwnershipState) {
1564        self.ownership.insert(var, state);
1565    }
1566    
1567    fn get_ownership(&self, var: &str) -> Option<&OwnershipState> {
1568        self.ownership.get(var)
1569    }
1570    
1571    fn get_borrows(&self, var: &str) -> BorrowInfo {
1572        self.borrows.get(var).cloned().unwrap_or_default()
1573    }
1574    
1575    // Phase 2: Enhanced add_borrow with source tracking
1576    fn add_borrow_with_source(&mut self, from: String, to: String, kind: BorrowKind, source: BorrowSource) {
1577        let borrow_info = self.borrows.entry(from.clone()).or_default();
1578        borrow_info.borrowers.insert(to.clone());
1579
1580        // Track this borrow in the current scope
1581        if let Some(current_scope) = self.scope_stack.last_mut() {
1582            current_scope.local_borrows.insert(to.clone());
1583        }
1584
1585        match kind {
1586            BorrowKind::Immutable => borrow_info.immutable_count += 1,
1587            BorrowKind::Mutable => borrow_info.has_mutable = true,
1588        }
1589
1590        // NEW: Record active borrow - track that 'from' is currently borrowed by 'to'
1591        let current_scope_level = self.scope_stack.len();
1592        let active_borrow = ActiveBorrow {
1593            borrower: to,
1594            borrowed_from: from.clone(),
1595            kind,
1596            scope: current_scope_level,
1597            source,  // Phase 2: Track borrow source
1598        };
1599        self.active_borrows.entry(from).or_default().push(active_borrow);
1600    }
1601
1602    // Convenience function for direct reference borrows (most common case)
1603    fn add_borrow(&mut self, from: String, to: String, kind: BorrowKind) {
1604        self.add_borrow_with_source(from, to, kind, BorrowSource::DirectReference);
1605    }
1606
1607    // NEW: Get active borrows for a variable
1608    fn get_active_borrows(&self, var: &str) -> Option<&Vec<ActiveBorrow>> {
1609        self.active_borrows.get(var)
1610    }
1611
1612    /// Phase 4: Check if a variable is transitively borrowed
1613    /// Returns true if the variable is directly borrowed OR if any of its borrowers are themselves borrowed
1614    /// This detects borrow chains like: s -> ref_opt -> opt
1615    fn is_transitively_borrowed(&self, var: &str) -> bool {
1616        // Check for direct borrows
1617        if let Some(borrows) = self.active_borrows.get(var) {
1618            if !borrows.is_empty() {
1619                // Variable is directly borrowed - check if any borrowers are themselves borrowed
1620                for borrow in borrows {
1621                    // If this borrower is also borrowed (creating a chain), we can't move
1622                    if self.is_transitively_borrowed(&borrow.borrower) {
1623                        return true;
1624                    }
1625                }
1626                // Has direct borrows but none of the borrowers are borrowed
1627                return true;
1628            }
1629        }
1630        // Not borrowed at all
1631        false
1632    }
1633
1634    /// Phase 4: Get all variables in the transitive borrow chain
1635    /// Returns a list of all borrowers in the chain, useful for error messages
1636    fn get_transitive_borrowers(&self, var: &str) -> Vec<String> {
1637        let mut result = Vec::new();
1638
1639        if let Some(borrows) = self.active_borrows.get(var) {
1640            for borrow in borrows {
1641                result.push(borrow.borrower.clone());
1642                // Recursively get borrowers of this borrower
1643                let nested = self.get_transitive_borrowers(&borrow.borrower);
1644                result.extend(nested);
1645            }
1646        }
1647
1648        result
1649    }
1650
1651    // NEW: Clear all borrows FROM a variable (for liveness analysis)
1652    // This clears borrows where 'var' is the borrower (e.g., a reference that's now dead)
1653    fn clear_borrows_from(&mut self, var: &str) {
1654        debug_println!("LIVENESS: Clearing borrows from '{}'", var);
1655
1656        // Remove all borrows where this variable is the borrower
1657        for (_borrowed_var, borrows) in &mut self.active_borrows {
1658            borrows.retain(|b| {
1659                if b.borrower == var {
1660                    debug_println!("LIVENESS: Removing borrow: '{}' → '{}'", var, _borrowed_var);
1661                    false
1662                } else {
1663                    true
1664                }
1665            });
1666        }
1667
1668        // Clean up empty borrow lists
1669        self.active_borrows.retain(|_, borrows| !borrows.is_empty());
1670    }
1671
1672    // NEW: Check if any variable reached its last use at this statement index
1673    // If so, clear its borrows (the variable is now dead)
1674    fn check_and_clear_last_uses(&mut self, statement_idx: usize) {
1675        let vars_to_clear: Vec<String> = self.last_use_map.iter()
1676            .filter(|(_, &last_use_idx)| last_use_idx == statement_idx)
1677            .map(|(var, _)| var.clone())
1678            .collect();
1679
1680        for var in vars_to_clear {
1681            debug_println!("LIVENESS: Variable '{}' reached its last use at statement {}", var, statement_idx);
1682            self.clear_borrows_from(&var);
1683        }
1684    }
1685
1686    // NEW: Helper methods for field-level ownership tracking
1687
1688    /// Get ownership state of a specific field
1689    fn get_field_ownership(&self, object: &str, field: &str) -> OwnershipState {
1690        self.field_ownership
1691            .get(object)
1692            .and_then(|fields| fields.get(field))
1693            .cloned()
1694            .unwrap_or(OwnershipState::Owned)
1695    }
1696
1697    /// Mark field as moved
1698    fn mark_field_moved(&mut self, object: String, field: String) {
1699        self.field_ownership
1700            .entry(object)
1701            .or_default()
1702            .insert(field, OwnershipState::Moved);
1703    }
1704
1705    /// Check if object has any moved fields (including nested paths)
1706    /// For object "o", checks if "o" has direct moved fields,
1707    /// and also checks if any "o.X" has moved fields (nested)
1708    fn has_moved_fields(&self, object: &str) -> bool {
1709        // Check direct moved fields
1710        if self.field_ownership
1711            .get(object)
1712            .map(|fields| fields.values().any(|s| *s == OwnershipState::Moved))
1713            .unwrap_or(false) {
1714            return true;
1715        }
1716
1717        // Check nested paths: if object is "o", look for "o.X" keys that have moved fields
1718        let prefix = format!("{}.", object);
1719        for (key, fields) in &self.field_ownership {
1720            if key.starts_with(&prefix) {
1721                if fields.values().any(|s| *s == OwnershipState::Moved) {
1722                    return true;
1723                }
1724            }
1725        }
1726
1727        false
1728    }
1729
1730    /// Get list of moved fields (including nested paths)
1731    fn get_moved_fields(&self, object: &str) -> Vec<String> {
1732        let mut result = Vec::new();
1733
1734        // Get direct moved fields
1735        if let Some(fields) = self.field_ownership.get(object) {
1736            for (field, state) in fields.iter() {
1737                if *state == OwnershipState::Moved {
1738                    result.push(field.clone());
1739                }
1740            }
1741        }
1742
1743        // Get nested moved fields: if object is "o", look for "o.X" keys
1744        let prefix = format!("{}.", object);
1745        for (key, fields) in &self.field_ownership {
1746            if key.starts_with(&prefix) {
1747                for (field, state) in fields.iter() {
1748                    if *state == OwnershipState::Moved {
1749                        // Return the full nested path relative to object
1750                        // e.g., for object="o", key="o.inner", field="data" -> "inner.data"
1751                        let nested_path = &key[prefix.len()..];
1752                        result.push(format!("{}.{}", nested_path, field));
1753                    }
1754                }
1755            }
1756        }
1757
1758        result
1759    }
1760
1761    // NEW: Field-level borrow tracking methods
1762
1763    /// Get borrow info for a specific field
1764    fn get_field_borrows(&self, object: &str, field: &str) -> BorrowInfo {
1765        self.field_borrows
1766            .get(object)
1767            .and_then(|fields| fields.get(field))
1768            .cloned()
1769            .unwrap_or_default()
1770    }
1771
1772    /// Add a field borrow
1773    fn add_field_borrow(&mut self, object: String, field: String, borrower: String, kind: BorrowKind) {
1774        let field_map = self.field_borrows.entry(object).or_default();
1775        let borrow_info = field_map.entry(field).or_default();
1776        borrow_info.borrowers.insert(borrower.clone());
1777
1778        match kind {
1779            BorrowKind::Immutable => borrow_info.immutable_count += 1,
1780            BorrowKind::Mutable => borrow_info.has_mutable = true,
1781        }
1782
1783        // Track in current scope for cleanup
1784        if let Some(current_scope) = self.scope_stack.last_mut() {
1785            current_scope.local_borrows.insert(borrower);
1786        }
1787    }
1788
1789    /// Check if any field of the object is borrowed
1790    fn has_any_field_borrowed(&self, object: &str) -> bool {
1791        if let Some(fields) = self.field_borrows.get(object) {
1792            for borrow_info in fields.values() {
1793                if borrow_info.immutable_count > 0 || borrow_info.has_mutable {
1794                    return true;
1795                }
1796            }
1797        }
1798        false
1799    }
1800
1801    /// Get list of borrowed fields for an object
1802    fn get_borrowed_fields(&self, object: &str) -> Vec<(String, bool)> {
1803        let mut result = Vec::new();
1804        if let Some(fields) = self.field_borrows.get(object) {
1805            for (field, borrow_info) in fields {
1806                if borrow_info.has_mutable {
1807                    result.push((field.clone(), true)); // is_mutable = true
1808                } else if borrow_info.immutable_count > 0 {
1809                    result.push((field.clone(), false)); // is_mutable = false
1810                }
1811            }
1812        }
1813        result
1814    }
1815
1816    /// Check if can move from 'this' in current context
1817    fn can_move_from_this(&self) -> bool {
1818        match &self.this_context {
1819            Some(ThisContext::Consumed) => true,  // && method - can move
1820            Some(_) => false,  // All other methods - cannot move
1821            None => true,  // Not in method - OK (though this shouldn't happen)
1822        }
1823    }
1824
1825    /// Set method context
1826    fn set_this_context(&mut self, context: Option<ThisContext>) {
1827        self.this_context = context;
1828    }
1829
1830    fn enter_scope(&mut self) {
1831        self.scope_stack.push(ScopeInfo::default());
1832    }
1833    
1834    fn exit_scope(&mut self) {
1835        if let Some(scope) = self.scope_stack.pop() {
1836            let _current_scope_level = self.scope_stack.len() + 1; // +1 because we just popped
1837
1838            // Clean up all borrows created in this scope
1839            for borrow_name in &scope.local_borrows {
1840                // Remove from reference info
1841                self.reference_info.remove(borrow_name);
1842
1843                // Remove from all borrow tracking
1844                for borrow_info in self.borrows.values_mut() {
1845                    borrow_info.borrowers.remove(borrow_name);
1846                    // Note: In a more complete implementation, we'd also
1847                    // decrement counts based on the borrow kind
1848                }
1849
1850                // NEW: Remove from active borrows
1851                // Remove any active borrow where this variable is the borrower
1852                for active_borrows in self.active_borrows.values_mut() {
1853                    active_borrows.retain(|b| &b.borrower != borrow_name);
1854                }
1855
1856                // NEW: Remove from field borrows (Partial Borrow Tracking)
1857                // When a borrower goes out of scope, remove it from field borrow tracking
1858                for field_map in self.field_borrows.values_mut() {
1859                    for borrow_info in field_map.values_mut() {
1860                        if borrow_info.borrowers.remove(borrow_name) {
1861                            // If this borrower was removed, update the counts
1862                            // We need to track if this was a mutable or immutable borrow
1863                            // For simplicity, we'll reset counts based on remaining borrowers
1864                            // This is conservative - a more complete impl would track borrow kinds per borrower
1865                            if borrow_info.borrowers.is_empty() {
1866                                borrow_info.has_mutable = false;
1867                                borrow_info.immutable_count = 0;
1868                            }
1869                        }
1870                    }
1871                }
1872            }
1873
1874            // Clean up empty borrow entries
1875            self.borrows.retain(|_, info| !info.borrowers.is_empty());
1876
1877            // NEW: Clean up empty active borrow entries
1878            self.active_borrows.retain(|_, borrows| !borrows.is_empty());
1879
1880            // NEW: Clean up empty field borrow entries
1881            for field_map in self.field_borrows.values_mut() {
1882                field_map.retain(|_, info| !info.borrowers.is_empty());
1883            }
1884            self.field_borrows.retain(|_, fields| !fields.is_empty());
1885        }
1886    }
1887    
1888    fn mark_as_reference(&mut self, var: String, is_mutable: bool) {
1889        self.reference_info.insert(var, ReferenceInfo {
1890            is_reference: true,
1891            is_mutable,
1892        });
1893    }
1894    
1895    fn is_reference(&self, var: &str) -> bool {
1896        self.reference_info
1897            .get(var)
1898            .map(|info| info.is_reference)
1899            .unwrap_or(false)
1900    }
1901    
1902    fn is_mutable_reference(&self, var: &str) -> bool {
1903        self.reference_info
1904            .get(var)
1905            .map(|info| info.is_reference && info.is_mutable)
1906            .unwrap_or(false)
1907    }
1908    
1909    fn enter_loop(&mut self) {
1910        // Save current state when entering a loop
1911        // This state represents the state at the END of the first iteration
1912        // which is what we'll use to check the BEGINNING of the second iteration
1913        self.loop_entry_states.push(LoopEntryState {
1914            ownership: self.ownership.clone(),
1915            borrows: self.borrows.clone(),
1916        });
1917        self.loop_depth += 1;
1918    }
1919    
1920    fn exit_loop(&mut self) {
1921        if self.loop_depth > 0 {
1922            self.loop_depth -= 1;
1923            
1924            // When exiting a loop, we simulate having run it twice
1925            // The current state is after one iteration
1926            // We saved the state at loop entry, now apply the second iteration effects
1927            if let Some(entry_state) = self.loop_entry_states.pop() {
1928                // The key insight: variables that were moved in the loop body
1929                // will be moved at the START of the second iteration
1930                // So check if any variables that are currently Moved
1931                // were NOT moved at loop entry
1932                for (var, current_state) in &self.ownership {
1933                    if *current_state == OwnershipState::Moved {
1934                        // If this variable was Owned at loop entry,
1935                        // it means it was moved during the loop body
1936                        // On second iteration, it would already be Moved
1937                        if let Some(entry_ownership) = entry_state.ownership.get(var) {
1938                            if *entry_ownership == OwnershipState::Owned {
1939                                // Keep it as Moved - this correctly represents
1940                                // the state after 2 iterations
1941                                // The error will be caught if the variable is used
1942                                // in the loop body (which we already processed)
1943                            }
1944                        }
1945                    }
1946                }
1947            }
1948        }
1949    }
1950    
1951    fn clone_state(&self) -> TrackerState {
1952        TrackerState {
1953            ownership: self.ownership.clone(),
1954            borrows: self.borrows.clone(),
1955            reference_info: self.reference_info.clone(),
1956            active_borrows: self.active_borrows.clone(),
1957            field_ownership: self.field_ownership.clone(),  // NEW
1958            field_borrows: self.field_borrows.clone(),      // NEW: Partial borrow tracking
1959        }
1960    }
1961
1962    fn restore_state(&mut self, state: &TrackerState) {
1963        self.ownership = state.ownership.clone();
1964        self.borrows = state.borrows.clone();
1965        self.reference_info = state.reference_info.clone();
1966        self.active_borrows = state.active_borrows.clone();
1967        self.field_ownership = state.field_ownership.clone();  // NEW
1968        self.field_borrows = state.field_borrows.clone();      // NEW: Partial borrow tracking
1969    }
1970    
1971    fn merge_states(&mut self, then_state: &TrackerState, else_state: &TrackerState) {
1972        // Merge ownership states aggressively (matching Rust's behavior)
1973        // A variable is considered moved if moved in ANY branch
1974        for (var, then_ownership) in &then_state.ownership {
1975            if let Some(else_ownership) = else_state.ownership.get(var) {
1976                if *then_ownership == OwnershipState::Moved || *else_ownership == OwnershipState::Moved {
1977                    // Moved in at least one branch - mark as moved (Rust's aggressive approach)
1978                    // This is sound: if any path moves the variable, it's unsafe to use after
1979                    self.ownership.insert(var.clone(), OwnershipState::Moved);
1980                } else {
1981                    // Not moved in either branch - use the common state
1982                    self.ownership.insert(var.clone(), then_ownership.clone());
1983                }
1984            }
1985        }
1986        
1987        // Merge borrows - a borrow exists only if it exists in BOTH branches
1988        // This is conservative: if a borrow doesn't exist in one branch, it's not guaranteed after the if
1989        self.borrows.clear();
1990        for (var, then_borrow) in &then_state.borrows {
1991            if let Some(else_borrow) = else_state.borrows.get(var) {
1992                // Borrow exists in both branches - keep it
1993                let mut merged_borrow = then_borrow.clone();
1994                // Keep only common borrowers
1995                merged_borrow.borrowers.retain(|b| else_borrow.borrowers.contains(b));
1996                // Use minimum counts (conservative)
1997                merged_borrow.immutable_count = merged_borrow.immutable_count.min(else_borrow.immutable_count);
1998                merged_borrow.has_mutable = merged_borrow.has_mutable && else_borrow.has_mutable;
1999                
2000                if !merged_borrow.borrowers.is_empty() {
2001                    self.borrows.insert(var.clone(), merged_borrow);
2002                }
2003            }
2004            // If borrow doesn't exist in else branch, don't include it
2005        }
2006        
2007        // Also clear reference info for references that don't exist in both branches
2008        let mut refs_to_keep = HashSet::new();
2009        for (var, _) in &then_state.reference_info {
2010            if else_state.reference_info.contains_key(var) {
2011                refs_to_keep.insert(var.clone());
2012            }
2013        }
2014        self.reference_info.retain(|var, _| refs_to_keep.contains(var));
2015
2016        // Merge active borrows - keep only borrows that exist in BOTH branches
2017        // This is conservative: if a borrow doesn't exist in one branch, it's not guaranteed after the if
2018        self.active_borrows.clear();
2019        for (var, then_borrows) in &then_state.active_borrows {
2020            if let Some(else_borrows) = else_state.active_borrows.get(var) {
2021                // Borrow exists in both branches - keep common borrows
2022                let then_borrowers: HashSet<String> = then_borrows.iter().map(|b| b.borrower.clone()).collect();
2023                let else_borrowers: HashSet<String> = else_borrows.iter().map(|b| b.borrower.clone()).collect();
2024
2025                let common_borrowers: Vec<ActiveBorrow> = then_borrows.iter()
2026                    .filter(|b| else_borrowers.contains(&b.borrower))
2027                    .cloned()
2028                    .collect();
2029
2030                if !common_borrowers.is_empty() {
2031                    self.active_borrows.insert(var.clone(), common_borrowers);
2032                }
2033            }
2034        }
2035
2036        // NEW: Merge field ownership - field moved in EITHER branch is marked as moved
2037        self.field_ownership.clear();
2038        // Collect all objects that have field ownership in either branch
2039        let mut all_objects: HashSet<String> = HashSet::new();
2040        all_objects.extend(then_state.field_ownership.keys().cloned());
2041        all_objects.extend(else_state.field_ownership.keys().cloned());
2042
2043        for object in all_objects {
2044            let then_fields = then_state.field_ownership.get(&object);
2045            let else_fields = else_state.field_ownership.get(&object);
2046
2047            match (then_fields, else_fields) {
2048                (Some(then_f), Some(else_f)) => {
2049                    // Object has fields in both branches
2050                    let mut merged_fields = HashMap::new();
2051
2052                    // Collect all field names
2053                    let mut all_field_names: HashSet<String> = HashSet::new();
2054                    all_field_names.extend(then_f.keys().cloned());
2055                    all_field_names.extend(else_f.keys().cloned());
2056
2057                    for field in all_field_names {
2058                        let then_state = then_f.get(&field);
2059                        let else_state = else_f.get(&field);
2060
2061                        match (then_state, else_state) {
2062                            (Some(t), Some(e)) => {
2063                                // Field exists in both - moved if moved in either
2064                                if *t == OwnershipState::Moved || *e == OwnershipState::Moved {
2065                                    merged_fields.insert(field, OwnershipState::Moved);
2066                                } else {
2067                                    merged_fields.insert(field, t.clone());
2068                                }
2069                            }
2070                            (Some(t), None) | (None, Some(t)) => {
2071                                // Field only in one branch - use that state
2072                                merged_fields.insert(field, t.clone());
2073                            }
2074                            (None, None) => unreachable!(),
2075                        }
2076                    }
2077
2078                    if !merged_fields.is_empty() {
2079                        self.field_ownership.insert(object, merged_fields);
2080                    }
2081                }
2082                (Some(fields), None) | (None, Some(fields)) => {
2083                    // Object only has fields in one branch - keep those fields
2084                    self.field_ownership.insert(object, fields.clone());
2085                }
2086                (None, None) => unreachable!(),
2087            }
2088        }
2089
2090        // NEW: Merge field borrows - keep borrows that exist in BOTH branches (conservative)
2091        self.field_borrows.clear();
2092        // Collect all objects that have field borrows in either branch
2093        let mut all_borrow_objects: HashSet<String> = HashSet::new();
2094        all_borrow_objects.extend(then_state.field_borrows.keys().cloned());
2095        all_borrow_objects.extend(else_state.field_borrows.keys().cloned());
2096
2097        for object in all_borrow_objects {
2098            let then_fields = then_state.field_borrows.get(&object);
2099            let else_fields = else_state.field_borrows.get(&object);
2100
2101            match (then_fields, else_fields) {
2102                (Some(then_f), Some(else_f)) => {
2103                    // Object has field borrows in both branches - keep only common
2104                    let mut merged_fields = HashMap::new();
2105
2106                    for (field, then_borrow) in then_f {
2107                        if let Some(else_borrow) = else_f.get(field) {
2108                            // Field borrow exists in both branches
2109                            let mut merged = then_borrow.clone();
2110                            merged.borrowers.retain(|b| else_borrow.borrowers.contains(b));
2111                            merged.immutable_count = merged.immutable_count.min(else_borrow.immutable_count);
2112                            merged.has_mutable = merged.has_mutable && else_borrow.has_mutable;
2113
2114                            if !merged.borrowers.is_empty() || merged.immutable_count > 0 || merged.has_mutable {
2115                                merged_fields.insert(field.clone(), merged);
2116                            }
2117                        }
2118                    }
2119
2120                    if !merged_fields.is_empty() {
2121                        self.field_borrows.insert(object, merged_fields);
2122                    }
2123                }
2124                _ => {
2125                    // Borrow only in one branch - don't keep it (conservative)
2126                }
2127            }
2128        }
2129    }
2130
2131    fn clear_loop_locals(&mut self, loop_locals: &HashSet<String>) {
2132        // Clear borrows for loop-local variables
2133        for local_var in loop_locals {
2134            // Remove from reference info
2135            self.reference_info.remove(local_var);
2136            
2137            // Remove from all borrow tracking
2138            for borrow_info in self.borrows.values_mut() {
2139                borrow_info.borrowers.remove(local_var);
2140                // We should also decrement counts, but need to track the kind
2141                // For simplicity, we'll rebuild the counts
2142            }
2143            
2144            // Remove the ownership entry for loop-local variables
2145            self.ownership.remove(local_var);
2146        }
2147        
2148        // Clean up empty borrow entries and recalculate counts
2149        for (_, borrow_info) in self.borrows.iter_mut() {
2150            // Reset counts based on remaining borrowers
2151            // This is a simplification - in a real implementation we'd track
2152            // the kind of each borrow
2153            if borrow_info.borrowers.is_empty() {
2154                borrow_info.immutable_count = 0;
2155                borrow_info.has_mutable = false;
2156            }
2157        }
2158        
2159        // Remove empty entries
2160        self.borrows.retain(|_, info| !info.borrowers.is_empty());
2161    }
2162}
2163
2164#[cfg(test)]
2165mod tests {
2166    use super::*;
2167    use crate::ir::{IrProgram, IrFunction, BasicBlock, IrStatement};
2168    use petgraph::graph::DiGraph;
2169
2170    fn create_test_program() -> IrProgram {
2171        IrProgram {
2172            functions: vec![],
2173            ownership_graph: DiGraph::new(),
2174            user_defined_raii_types: std::collections::HashSet::new(),
2175        }
2176    }
2177
2178    fn create_test_function(name: &str) -> IrFunction {
2179        let mut cfg = DiGraph::new();
2180        let block = BasicBlock {
2181            id: 0,
2182            statements: vec![],
2183            terminator: None,
2184        };
2185        cfg.add_node(block);
2186        
2187        IrFunction {
2188            name: name.to_string(),
2189            cfg,
2190            variables: HashMap::new(),
2191            return_type: "void".to_string(),
2192            source_file: "test.cpp".to_string(),
2193            is_method: false,
2194            method_qualifier: None,
2195            class_name: None,
2196            template_parameters: vec![],
2197            lifetime_params: HashMap::new(),
2198            param_lifetimes: Vec::new(),
2199            return_lifetime: None,
2200            lifetime_constraints: Vec::new(),
2201        }
2202    }
2203
2204    #[test]
2205    fn test_empty_program_passes() {
2206        let program = create_test_program();
2207        let result = check_borrows(program);
2208        
2209        assert!(result.is_ok());
2210        let errors = result.unwrap();
2211        assert_eq!(errors.len(), 0);
2212    }
2213
2214    #[test]
2215    fn test_ownership_tracker_initialization() {
2216        let mut tracker = OwnershipTracker::new();
2217        tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2218        
2219        assert_eq!(tracker.get_ownership("x"), Some(&OwnershipState::Owned));
2220        assert_eq!(tracker.get_ownership("y"), None);
2221    }
2222
2223    #[test]
2224    fn test_ownership_state_transitions() {
2225        let mut tracker = OwnershipTracker::new();
2226        
2227        // Start with owned
2228        tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2229        assert_eq!(tracker.get_ownership("x"), Some(&OwnershipState::Owned));
2230        
2231        // Move to another variable
2232        tracker.set_ownership("x".to_string(), OwnershipState::Moved);
2233        tracker.set_ownership("y".to_string(), OwnershipState::Owned);
2234        
2235        assert_eq!(tracker.get_ownership("x"), Some(&OwnershipState::Moved));
2236        assert_eq!(tracker.get_ownership("y"), Some(&OwnershipState::Owned));
2237    }
2238
2239    #[test]
2240    fn test_borrow_tracking() {
2241        let mut tracker = OwnershipTracker::new();
2242        tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2243        
2244        // Add immutable borrow
2245        tracker.add_borrow("x".to_string(), "ref1".to_string(), BorrowKind::Immutable);
2246        let borrows = tracker.get_borrows("x");
2247        assert_eq!(borrows.immutable_count, 1);
2248        assert!(!borrows.has_mutable);
2249        
2250        // Add another immutable borrow
2251        tracker.add_borrow("x".to_string(), "ref2".to_string(), BorrowKind::Immutable);
2252        let borrows = tracker.get_borrows("x");
2253        assert_eq!(borrows.immutable_count, 2);
2254        assert!(!borrows.has_mutable);
2255    }
2256
2257    #[test]
2258    fn test_mutable_borrow_tracking() {
2259        let mut tracker = OwnershipTracker::new();
2260        tracker.set_ownership("x".to_string(), OwnershipState::Owned);
2261        
2262        // Add mutable borrow
2263        tracker.add_borrow("x".to_string(), "mut_ref".to_string(), BorrowKind::Mutable);
2264        let borrows = tracker.get_borrows("x");
2265        assert_eq!(borrows.immutable_count, 0);
2266        assert!(borrows.has_mutable);
2267    }
2268
2269    #[test]
2270    fn test_use_after_move_detection() {
2271        let mut program = create_test_program();
2272        let mut func = create_test_function("test");
2273        
2274        // Add variables
2275        func.variables.insert(
2276            "x".to_string(),
2277            crate::ir::VariableInfo {
2278                name: "x".to_string(),
2279                ty: crate::ir::VariableType::Owned("int".to_string()),
2280                ownership: OwnershipState::Owned,
2281                lifetime: None,
2282                is_parameter: false,
2283                is_static: false,
2284                scope_level: 0,
2285                has_destructor: false,
2286                declaration_index: 0,
2287            },
2288        );
2289        
2290        func.variables.insert(
2291            "y".to_string(),
2292            crate::ir::VariableInfo {
2293                name: "y".to_string(),
2294                ty: crate::ir::VariableType::Owned("int".to_string()),
2295                ownership: OwnershipState::Owned,
2296                lifetime: None,
2297                is_parameter: false,
2298                is_static: false,
2299                scope_level: 0,
2300                has_destructor: false,
2301                declaration_index: 0,
2302            },
2303        );
2304        
2305        // Add statements: move x to y, then try to use x
2306        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2307        block.statements.push(IrStatement::Move {
2308            from: "x".to_string(),
2309            to: "y".to_string(),
2310        });
2311        
2312        // Try to move x again (should fail)
2313        block.statements.push(IrStatement::Move {
2314            from: "x".to_string(),
2315            to: "z".to_string(),
2316        });
2317        
2318        program.functions.push(func);
2319        
2320        let result = check_borrows(program);
2321        assert!(result.is_ok());
2322        
2323        let errors = result.unwrap();
2324        assert_eq!(errors.len(), 1);
2325        assert!(errors[0].contains("Use after move"));
2326    }
2327
2328    #[test]
2329    fn test_multiple_immutable_borrows_allowed() {
2330        let mut program = create_test_program();
2331        let mut func = create_test_function("test");
2332        
2333        func.variables.insert(
2334            "x".to_string(),
2335            crate::ir::VariableInfo {
2336                name: "x".to_string(),
2337                ty: crate::ir::VariableType::Owned("int".to_string()),
2338                ownership: OwnershipState::Owned,
2339                lifetime: None,
2340                is_parameter: false,
2341                is_static: false,
2342                scope_level: 0,
2343                has_destructor: false,
2344                declaration_index: 0,
2345            },
2346        );
2347        
2348        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2349        block.statements.push(IrStatement::Borrow {
2350            from: "x".to_string(),
2351            to: "ref1".to_string(),
2352            kind: BorrowKind::Immutable,
2353        });
2354        
2355        block.statements.push(IrStatement::Borrow {
2356            from: "x".to_string(),
2357            to: "ref2".to_string(),
2358            kind: BorrowKind::Immutable,
2359        });
2360        
2361        program.functions.push(func);
2362        
2363        let result = check_borrows(program);
2364        assert!(result.is_ok());
2365        
2366        let errors = result.unwrap();
2367        assert_eq!(errors.len(), 0); // Multiple immutable borrows are OK
2368    }
2369
2370    #[test]
2371    fn test_mutable_borrow_while_immutable_fails() {
2372        let mut program = create_test_program();
2373        let mut func = create_test_function("test");
2374        
2375        func.variables.insert(
2376            "x".to_string(),
2377            crate::ir::VariableInfo {
2378                name: "x".to_string(),
2379                ty: crate::ir::VariableType::Owned("int".to_string()),
2380                ownership: OwnershipState::Owned,
2381                lifetime: None,
2382                is_parameter: false,
2383                is_static: false,
2384                scope_level: 0,
2385                has_destructor: false,
2386                declaration_index: 0,
2387            },
2388        );
2389        
2390        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2391        
2392        // First, immutable borrow
2393        block.statements.push(IrStatement::Borrow {
2394            from: "x".to_string(),
2395            to: "ref1".to_string(),
2396            kind: BorrowKind::Immutable,
2397        });
2398        
2399        // Then try mutable borrow (should fail)
2400        block.statements.push(IrStatement::Borrow {
2401            from: "x".to_string(),
2402            to: "mut_ref".to_string(),
2403            kind: BorrowKind::Mutable,
2404        });
2405        
2406        program.functions.push(func);
2407        
2408        let result = check_borrows(program);
2409        assert!(result.is_ok());
2410        
2411        let errors = result.unwrap();
2412        assert_eq!(errors.len(), 1);
2413        assert!(errors[0].contains("Cannot"));
2414        assert!(errors[0].contains("mutable"));
2415    }
2416
2417    #[test]
2418    fn test_const_reference_cannot_modify() {
2419        let mut program = create_test_program();
2420        let mut func = create_test_function("test");
2421        
2422        // Add a value and a const reference to it
2423        func.variables.insert(
2424            "value".to_string(),
2425            crate::ir::VariableInfo {
2426                name: "value".to_string(),
2427                ty: crate::ir::VariableType::Owned("int".to_string()),
2428                ownership: OwnershipState::Owned,
2429                lifetime: None,
2430                is_parameter: false,
2431                is_static: false,
2432                scope_level: 0,
2433                has_destructor: false,
2434                declaration_index: 0,
2435            },
2436        );
2437        
2438        func.variables.insert(
2439            "const_ref".to_string(),
2440            crate::ir::VariableInfo {
2441                name: "const_ref".to_string(),
2442                ty: crate::ir::VariableType::Reference("int".to_string()),
2443                ownership: OwnershipState::Borrowed(BorrowKind::Immutable),
2444                lifetime: None,
2445                is_parameter: false,
2446                is_static: false,
2447                scope_level: 0,
2448                has_destructor: false,
2449                declaration_index: 0,
2450            },
2451        );
2452        
2453        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2454        
2455        // Create const reference
2456        block.statements.push(IrStatement::Borrow {
2457            from: "value".to_string(),
2458            to: "const_ref".to_string(),
2459            kind: BorrowKind::Immutable,
2460        });
2461        
2462        // Try to modify through const reference (should fail)
2463        block.statements.push(IrStatement::Assign {
2464            lhs: "const_ref".to_string(),
2465            rhs: crate::ir::IrExpression::Variable("other".to_string()),
2466        });
2467        
2468        program.functions.push(func);
2469        
2470        let result = check_borrows(program);
2471        assert!(result.is_ok());
2472        
2473        let errors = result.unwrap();
2474        assert_eq!(errors.len(), 1);
2475        assert!(errors[0].contains("Cannot assign"));
2476        assert!(errors[0].contains("const reference"));
2477    }
2478
2479    #[test]
2480    fn test_mutable_reference_can_modify() {
2481        let mut program = create_test_program();
2482        let mut func = create_test_function("test");
2483        
2484        // Add a value and a mutable reference to it
2485        func.variables.insert(
2486            "value".to_string(),
2487            crate::ir::VariableInfo {
2488                name: "value".to_string(),
2489                ty: crate::ir::VariableType::Owned("int".to_string()),
2490                ownership: OwnershipState::Owned,
2491                lifetime: None,
2492                is_parameter: false,
2493                is_static: false,
2494                scope_level: 0,
2495                has_destructor: false,
2496                declaration_index: 0,
2497            },
2498        );
2499        
2500        func.variables.insert(
2501            "mut_ref".to_string(),
2502            crate::ir::VariableInfo {
2503                name: "mut_ref".to_string(),
2504                ty: crate::ir::VariableType::MutableReference("int".to_string()),
2505                ownership: OwnershipState::Borrowed(BorrowKind::Mutable),
2506                lifetime: None,
2507                is_parameter: false,
2508                is_static: false,
2509                scope_level: 0,
2510                has_destructor: false,
2511                declaration_index: 0,
2512            },
2513        );
2514        
2515        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2516        
2517        // Create mutable reference
2518        block.statements.push(IrStatement::Borrow {
2519            from: "value".to_string(),
2520            to: "mut_ref".to_string(),
2521            kind: BorrowKind::Mutable,
2522        });
2523        
2524        // Modify through mutable reference (should succeed)
2525        block.statements.push(IrStatement::Assign {
2526            lhs: "mut_ref".to_string(),
2527            rhs: crate::ir::IrExpression::Variable("other".to_string()),
2528        });
2529        
2530        program.functions.push(func);
2531        
2532        let result = check_borrows(program);
2533        assert!(result.is_ok());
2534        
2535        let errors = result.unwrap();
2536        assert_eq!(errors.len(), 0); // Should succeed
2537    }
2538
2539    #[test]
2540    fn test_cannot_move_from_reference() {
2541        let mut program = create_test_program();
2542        let mut func = create_test_function("test");
2543        
2544        // Add a reference variable
2545        func.variables.insert(
2546            "ref_var".to_string(),
2547            crate::ir::VariableInfo {
2548                name: "ref_var".to_string(),
2549                ty: crate::ir::VariableType::Reference("int".to_string()),
2550                ownership: OwnershipState::Borrowed(BorrowKind::Immutable),
2551                lifetime: None,
2552                is_parameter: false,
2553                is_static: false,
2554                scope_level: 0,
2555                has_destructor: false,
2556                declaration_index: 0,
2557            },
2558        );
2559        
2560        func.variables.insert(
2561            "dest".to_string(),
2562            crate::ir::VariableInfo {
2563                name: "dest".to_string(),
2564                ty: crate::ir::VariableType::Owned("int".to_string()),
2565                ownership: OwnershipState::Owned,
2566                lifetime: None,
2567                is_parameter: false,
2568                is_static: false,
2569                scope_level: 0,
2570                has_destructor: false,
2571                declaration_index: 0,
2572            },
2573        );
2574        
2575        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2576        
2577        // Try to move from reference (should fail)
2578        block.statements.push(IrStatement::Move {
2579            from: "ref_var".to_string(),
2580            to: "dest".to_string(),
2581        });
2582        
2583        program.functions.push(func);
2584        
2585        let result = check_borrows(program);
2586        assert!(result.is_ok());
2587        
2588        let errors = result.unwrap();
2589        assert_eq!(errors.len(), 1);
2590        assert!(errors[0].contains("Cannot move"));
2591        assert!(errors[0].contains("reference"));
2592    }
2593
2594    #[test]
2595    fn test_multiple_const_references_allowed() {
2596        let mut program = create_test_program();
2597        let mut func = create_test_function("test");
2598        
2599        func.variables.insert(
2600            "value".to_string(),
2601            crate::ir::VariableInfo {
2602                name: "value".to_string(),
2603                ty: crate::ir::VariableType::Owned("int".to_string()),
2604                ownership: OwnershipState::Owned,
2605                lifetime: None,
2606                is_parameter: false,
2607                is_static: false,
2608                scope_level: 0,
2609                has_destructor: false,
2610                declaration_index: 0,
2611            },
2612        );
2613        
2614        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2615        
2616        // Create multiple const references (should succeed)
2617        block.statements.push(IrStatement::Borrow {
2618            from: "value".to_string(),
2619            to: "const_ref1".to_string(),
2620            kind: BorrowKind::Immutable,
2621        });
2622        
2623        block.statements.push(IrStatement::Borrow {
2624            from: "value".to_string(),
2625            to: "const_ref2".to_string(),
2626            kind: BorrowKind::Immutable,
2627        });
2628        
2629        block.statements.push(IrStatement::Borrow {
2630            from: "value".to_string(),
2631            to: "const_ref3".to_string(),
2632            kind: BorrowKind::Immutable,
2633        });
2634        
2635        program.functions.push(func);
2636        
2637        let result = check_borrows(program);
2638        assert!(result.is_ok());
2639        
2640        let errors = result.unwrap();
2641        assert_eq!(errors.len(), 0); // Multiple const references are allowed
2642    }
2643
2644    #[test]
2645    fn test_cannot_borrow_moved_value() {
2646        let mut program = create_test_program();
2647        let mut func = create_test_function("test");
2648        
2649        func.variables.insert(
2650            "value".to_string(),
2651            crate::ir::VariableInfo {
2652                name: "value".to_string(),
2653                ty: crate::ir::VariableType::UniquePtr("int".to_string()),
2654                ownership: OwnershipState::Owned,
2655                lifetime: None,
2656                is_parameter: false,
2657                is_static: false,
2658                scope_level: 0,
2659                has_destructor: false,
2660                declaration_index: 0,
2661            },
2662        );
2663        
2664        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2665        
2666        // Move the value
2667        block.statements.push(IrStatement::Move {
2668            from: "value".to_string(),
2669            to: "other".to_string(),
2670        });
2671        
2672        // Try to create reference to moved value (should fail)
2673        block.statements.push(IrStatement::Borrow {
2674            from: "value".to_string(),
2675            to: "ref".to_string(),
2676            kind: BorrowKind::Immutable,
2677        });
2678        
2679        program.functions.push(func);
2680        
2681        let result = check_borrows(program);
2682        assert!(result.is_ok());
2683        
2684        let errors = result.unwrap();
2685        assert!(errors.len() > 0);
2686        assert!(errors.iter().any(|e| e.contains("moved")));
2687    }
2688
2689    #[test]
2690    fn test_multiple_functions_with_references() {
2691        let mut program = create_test_program();
2692        
2693        // First function with valid const refs
2694        let mut func1 = create_test_function("func1");
2695        func1.variables.insert(
2696            "x".to_string(),
2697            crate::ir::VariableInfo {
2698                name: "x".to_string(),
2699                ty: crate::ir::VariableType::Owned("int".to_string()),
2700                ownership: OwnershipState::Owned,
2701                lifetime: None,
2702                is_parameter: false,
2703                is_static: false,
2704                scope_level: 0,
2705                has_destructor: false,
2706                declaration_index: 0,
2707            },
2708        );
2709        
2710        let block1 = &mut func1.cfg[petgraph::graph::NodeIndex::new(0)];
2711        block1.statements.push(IrStatement::Borrow {
2712            from: "x".to_string(),
2713            to: "ref1".to_string(),
2714            kind: BorrowKind::Immutable,
2715        });
2716        block1.statements.push(IrStatement::Borrow {
2717            from: "x".to_string(),
2718            to: "ref2".to_string(),
2719            kind: BorrowKind::Immutable,
2720        });
2721        
2722        // Second function with invalid refs
2723        let mut func2 = create_test_function("func2");
2724        func2.variables.insert(
2725            "y".to_string(),
2726            crate::ir::VariableInfo {
2727                name: "y".to_string(),
2728                ty: crate::ir::VariableType::Owned("int".to_string()),
2729                ownership: OwnershipState::Owned,
2730                lifetime: None,
2731                is_parameter: false,
2732                is_static: false,
2733                scope_level: 0,
2734                has_destructor: false,
2735                declaration_index: 0,
2736            },
2737        );
2738        
2739        let block2 = &mut func2.cfg[petgraph::graph::NodeIndex::new(0)];
2740        block2.statements.push(IrStatement::Borrow {
2741            from: "y".to_string(),
2742            to: "mut1".to_string(),
2743            kind: BorrowKind::Mutable,
2744        });
2745        block2.statements.push(IrStatement::Borrow {
2746            from: "y".to_string(),
2747            to: "mut2".to_string(),
2748            kind: BorrowKind::Mutable,
2749        });
2750        
2751        program.functions.push(func1);
2752        program.functions.push(func2);
2753        
2754        let result = check_borrows(program);
2755        assert!(result.is_ok());
2756        
2757        let errors = result.unwrap();
2758        assert_eq!(errors.len(), 1); // Only func2 should have errors
2759        assert!(errors[0].contains("already mutably borrowed"));
2760    }
2761
2762    #[test]
2763    fn test_complex_borrow_chain() {
2764        let mut program = create_test_program();
2765        let mut func = create_test_function("test");
2766        
2767        // Create variables
2768        func.variables.insert(
2769            "a".to_string(),
2770            crate::ir::VariableInfo {
2771                name: "a".to_string(),
2772                ty: crate::ir::VariableType::Owned("int".to_string()),
2773                ownership: OwnershipState::Owned,
2774                lifetime: None,
2775                is_parameter: false,
2776                is_static: false,
2777                scope_level: 0,
2778                has_destructor: false,
2779                declaration_index: 0,
2780            },
2781        );
2782        
2783        func.variables.insert(
2784            "b".to_string(),
2785            crate::ir::VariableInfo {
2786                name: "b".to_string(),
2787                ty: crate::ir::VariableType::Owned("int".to_string()),
2788                ownership: OwnershipState::Owned,
2789                lifetime: None,
2790                is_parameter: false,
2791                is_static: false,
2792                scope_level: 0,
2793                has_destructor: false,
2794                declaration_index: 0,
2795            },
2796        );
2797        
2798        let block = &mut func.cfg[petgraph::graph::NodeIndex::new(0)];
2799        
2800        // Create multiple immutable refs to 'a'
2801        block.statements.push(IrStatement::Borrow {
2802            from: "a".to_string(),
2803            to: "ref_a1".to_string(),
2804            kind: BorrowKind::Immutable,
2805        });
2806        block.statements.push(IrStatement::Borrow {
2807            from: "a".to_string(),
2808            to: "ref_a2".to_string(),
2809            kind: BorrowKind::Immutable,
2810        });
2811        
2812        // Create mutable ref to 'b'
2813        block.statements.push(IrStatement::Borrow {
2814            from: "b".to_string(),
2815            to: "mut_b".to_string(),
2816            kind: BorrowKind::Mutable,
2817        });
2818        
2819        // Try to create another ref to 'b' (should fail)
2820        block.statements.push(IrStatement::Borrow {
2821            from: "b".to_string(),
2822            to: "ref_b".to_string(),
2823            kind: BorrowKind::Immutable,
2824        });
2825        
2826        program.functions.push(func);
2827        
2828        let result = check_borrows(program);
2829        assert!(result.is_ok());
2830        
2831        let errors = result.unwrap();
2832        assert_eq!(errors.len(), 1);
2833        assert!(errors[0].contains("'b'"));
2834        assert!(errors[0].contains("already mutably borrowed"));
2835    }
2836}
2837#[cfg(test)]
2838mod scope_tests {
2839    use super::*;
2840    use crate::ir::{BasicBlock, IrFunction, IrProgram, IrStatement, BorrowKind};
2841    use petgraph::graph::Graph;
2842    use std::collections::HashMap;
2843
2844    fn create_test_function_with_statements(statements: Vec<IrStatement>) -> IrFunction {
2845        let mut cfg = Graph::new();
2846        let block = BasicBlock {
2847            id: 0,
2848            statements,
2849            terminator: None,
2850        };
2851        cfg.add_node(block);
2852
2853        IrFunction {
2854            name: "test".to_string(),
2855            cfg,
2856            variables: HashMap::new(),
2857            return_type: "void".to_string(),
2858            source_file: "test.cpp".to_string(),
2859            is_method: false,
2860            method_qualifier: None,
2861            class_name: None,
2862            template_parameters: vec![],
2863            lifetime_params: HashMap::new(),
2864            param_lifetimes: Vec::new(),
2865            return_lifetime: None,
2866            lifetime_constraints: Vec::new(),
2867        }
2868    }
2869
2870    #[test]
2871    fn test_scope_cleanup_simple() {
2872        let statements = vec![
2873            IrStatement::EnterScope,
2874            IrStatement::Borrow {
2875                from: "value".to_string(),
2876                to: "ref1".to_string(),
2877                kind: BorrowKind::Mutable,
2878            },
2879            IrStatement::ExitScope,
2880            // After scope exit, should be able to borrow again
2881            IrStatement::Borrow {
2882                from: "value".to_string(),
2883                to: "ref2".to_string(),
2884                kind: BorrowKind::Mutable,
2885            },
2886        ];
2887        
2888        let func = create_test_function_with_statements(statements);
2889        let mut program = IrProgram {
2890            functions: vec![func],
2891            ownership_graph: petgraph::graph::DiGraph::new(),
2892            user_defined_raii_types: std::collections::HashSet::new(),
2893        };
2894        
2895        let result = check_borrows(program);
2896        assert!(result.is_ok());
2897        let errors = result.unwrap();
2898        assert_eq!(errors.len(), 0, "Should not report errors for borrows in different scopes");
2899    }
2900
2901    #[test]
2902    fn test_nested_scopes() {
2903        let statements = vec![
2904            IrStatement::EnterScope,
2905            IrStatement::Borrow {
2906                from: "value".to_string(),
2907                to: "ref1".to_string(),
2908                kind: BorrowKind::Immutable,
2909            },
2910            IrStatement::EnterScope,
2911            // Nested scope - should be able to have another immutable borrow
2912            IrStatement::Borrow {
2913                from: "value".to_string(),
2914                to: "ref2".to_string(),
2915                kind: BorrowKind::Immutable,
2916            },
2917            IrStatement::ExitScope,
2918            // ref2 is gone, but ref1 still exists
2919            IrStatement::ExitScope,
2920            // Now both are gone
2921            IrStatement::Borrow {
2922                from: "value".to_string(),
2923                to: "ref3".to_string(),
2924                kind: BorrowKind::Mutable,
2925            },
2926        ];
2927        
2928        let func = create_test_function_with_statements(statements);
2929        let mut program = IrProgram {
2930            functions: vec![func],
2931            ownership_graph: petgraph::graph::DiGraph::new(),
2932            user_defined_raii_types: std::collections::HashSet::new(),
2933        };
2934        
2935        let result = check_borrows(program);
2936        assert!(result.is_ok());
2937        let errors = result.unwrap();
2938        assert_eq!(errors.len(), 0, "Nested scopes should work correctly");
2939    }
2940
2941    #[test]
2942    fn test_scope_doesnt_affect_moves() {
2943        let statements = vec![
2944            IrStatement::EnterScope,
2945            IrStatement::Move {
2946                from: "x".to_string(),
2947                to: "y".to_string(),
2948            },
2949            IrStatement::ExitScope,
2950            // x is still moved even after scope exit
2951            IrStatement::Move {
2952                from: "x".to_string(),
2953                to: "z".to_string(),
2954            },
2955        ];
2956        
2957        let func = create_test_function_with_statements(statements);
2958        let mut program = IrProgram {
2959            functions: vec![func],
2960            ownership_graph: petgraph::graph::DiGraph::new(),
2961            user_defined_raii_types: std::collections::HashSet::new(),
2962        };
2963        
2964        let result = check_borrows(program);
2965        assert!(result.is_ok());
2966        let errors = result.unwrap();
2967        assert!(errors.len() > 0, "Should still detect use-after-move across scopes");
2968        assert!(errors[0].contains("already been moved") || errors[0].contains("Use after move"));
2969    }
2970
2971    #[test]
2972    fn test_multiple_sequential_scopes() {
2973        let statements = vec![
2974            // First scope
2975            IrStatement::EnterScope,
2976            IrStatement::Borrow {
2977                from: "value".to_string(),
2978                to: "ref1".to_string(),
2979                kind: BorrowKind::Mutable,
2980            },
2981            IrStatement::ExitScope,
2982            
2983            // Second scope
2984            IrStatement::EnterScope,
2985            IrStatement::Borrow {
2986                from: "value".to_string(),
2987                to: "ref2".to_string(),
2988                kind: BorrowKind::Mutable,
2989            },
2990            IrStatement::ExitScope,
2991            
2992            // Third scope
2993            IrStatement::EnterScope,
2994            IrStatement::Borrow {
2995                from: "value".to_string(),
2996                to: "ref3".to_string(),
2997                kind: BorrowKind::Mutable,
2998            },
2999            IrStatement::ExitScope,
3000        ];
3001        
3002        let func = create_test_function_with_statements(statements);
3003        let mut program = IrProgram {
3004            functions: vec![func],
3005            ownership_graph: petgraph::graph::DiGraph::new(),
3006            user_defined_raii_types: std::collections::HashSet::new(),
3007        };
3008        
3009        let result = check_borrows(program);
3010        assert!(result.is_ok());
3011        let errors = result.unwrap();
3012        assert_eq!(errors.len(), 0, "Sequential scopes should not conflict");
3013    }
3014
3015    #[test]
3016    fn test_error_still_caught_in_same_scope() {
3017        let statements = vec![
3018            IrStatement::EnterScope,
3019            IrStatement::Borrow {
3020                from: "value".to_string(),
3021                to: "ref1".to_string(),
3022                kind: BorrowKind::Mutable,
3023            },
3024            // This should error - same scope
3025            IrStatement::Borrow {
3026                from: "value".to_string(),
3027                to: "ref2".to_string(),
3028                kind: BorrowKind::Mutable,
3029            },
3030            IrStatement::ExitScope,
3031        ];
3032        
3033        let func = create_test_function_with_statements(statements);
3034        let mut program = IrProgram {
3035            functions: vec![func],
3036            ownership_graph: petgraph::graph::DiGraph::new(),
3037            user_defined_raii_types: std::collections::HashSet::new(),
3038        };
3039        
3040        let result = check_borrows(program);
3041        assert!(result.is_ok());
3042        let errors = result.unwrap();
3043        assert!(errors.len() > 0, "Should still catch errors within the same scope");
3044        assert!(errors[0].contains("already mutably borrowed"));
3045    }
3046}