빌림과 참조 문서의 검증 코드다.

//! 빌림과 참조 문서의 주장을 확인한다: 공유 참조 여럿 또는 가변 참조 하나 규칙의 오류 코드(E0499·E0502·E0596·E0506·E0505),
//! 빌림이 마지막 사용에서 끝나는 것(NLL), 다시 빌리기(reborrow), 두 단계 빌림, 필드별 빌림, 참조의 크기.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/07_borrowing-references_verify.rs"

use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};

static SNIPPET: AtomicUsize = AtomicUsize::new(0);

/// 코드 조각을 rustc로 컴파일해 stderr를 돌려준다.
fn compile_errors(code: &str) -> String {
    let n = SNIPPET.fetch_add(1, Ordering::SeqCst);
    let dir = std::env::temp_dir().join(format!("vr07_{}_{n}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let file = dir.join("snippet.rs");
    std::fs::write(&file, code).unwrap();
    let out = Command::new("rustc")
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .arg(&file)
        .output()
        .expect("rustc 실행");
    let _ = std::fs::remove_dir_all(&dir);
    String::from_utf8_lossy(&out.stderr).into_owned()
}

fn expect_error(code: &str, err: &str, phrase: &str) {
    let stderr = compile_errors(code);
    assert!(
        stderr.contains(&format!("error[{err}]")),
        "{err} 기대, 실제:\n{stderr}"
    );
    assert!(stderr.contains(phrase), "'{phrase}' 기대, 실제:\n{stderr}");
    println!("{err} 확인: {phrase}");
}

fn expect_ok(code: &str) {
    let stderr = compile_errors(code);
    assert!(
        !stderr.contains("error"),
        "컴파일 성공 기대, 실제:\n{stderr}"
    );
}

fn compile_checks() {
    // 가변 참조 둘을 동시에
    expect_error(
        r#"pub fn f(){ let mut s=String::new(); let a=&mut s; let b=&mut s; a.push('x'); b.push('y'); }"#,
        "E0499",
        "cannot borrow `s` as mutable more than once at a time",
    );
    // 공유 참조가 살아 있는데 가변으로 빌림
    expect_error(
        r#"pub fn f(){ let mut v=vec![1]; let first=&v[0]; v.push(2); println!("{first}"); }"#,
        "E0502",
        "cannot borrow `v` as mutable because it is also borrowed as immutable",
    );
    // 공유 참조로 고치기
    expect_error(
        r#"pub fn f(s:&String){ s.push_str("x"); }"#,
        "E0596",
        "cannot borrow `*s` as mutable, as it is behind a `&` reference",
    );
    // 빌려 준 동안 원래 변수에 대입
    expect_error(
        r#"pub fn f(){ let mut x=1; let r=&x; x=5; println!("{r} {x}"); }"#,
        "E0506",
        "cannot assign to `x` because it is borrowed",
    );
    // 빌려 준 동안 원래 값을 옮김
    expect_error(
        r#"pub fn f(){ let s=String::new(); let r=&s; let t=s; println!("{r} {t}"); }"#,
        "E0505",
        "cannot move out of `s` because it is borrowed",
    );
    // 반복하는 동안 같은 Vec에 넣기
    expect_error(
        r#"pub fn f(){ let mut v=vec![1,2]; for x in &v { if *x>0 { v.push(*x); } } }"#,
        "E0502",
        "cannot borrow `v` as mutable because it is also borrowed as immutable",
    );
    // Nomicon의 compute(&x, &mut x)는 부를 수 없다
    expect_error(
        r#"fn compute(input:&u32, output:&mut u32){ if *input>10 { *output=1; } if *input>5 { *output*=2; } }
           pub fn f(){ let mut x=20; compute(&x, &mut x); }"#,
        "E0502",
        "cannot borrow `x` as mutable because it is also borrowed as immutable",
    );
    // NLL: 마지막 사용 뒤에는 가변으로 빌릴 수 있다
    expect_ok(
        r#"pub fn f(){ let mut s=String::from("a"); let r1=&s; let r2=&s; println!("{r1}{r2}"); let r3=&mut s; r3.push('b'); }"#,
    );
    // 함수에 &mut를 넘기면 자동으로 다시 빌린다: 같은 r를 두 번 넘길 수 있다
    expect_ok(
        r#"fn add(v:&mut Vec<i32>){v.push(1);} pub fn f(){ let mut v=vec![]; let r=&mut v; add(r); add(r); r.push(3); }"#,
    );
    // let으로 그냥 옮기면 &mut는 이동한다(Copy가 아니다)
    expect_error(
        r#"pub fn f(){ let mut v=vec![1]; let r=&mut v; let r2=r; r2.push(1); r.push(2); }"#,
        "E0382",
        "borrow of moved value: `r`",
    );
    // 공유 참조는 Copy라서 대입해도 둘 다 쓸 수 있다
    expect_ok(r#"pub fn f(){ let v=vec![1]; let r=&v; let r2=r; println!("{r:?}{r2:?}"); }"#);
    // 명시적으로 다시 빌린 r2를 다 쓰고 나면 r를 다시 쓸 수 있다
    expect_ok(
        r#"pub fn f(){ let mut v=vec![1]; let r=&mut v; let r2=&mut *r; r2.push(1); r.push(2); }"#,
    );
    // r2가 아직 살아 있는데 r를 쓰면 E0499
    expect_error(
        r#"pub fn f(){ let mut v=vec![1]; let r=&mut v; let r2=&mut *r; r.push(2); r2.push(1); }"#,
        "E0499",
        "cannot borrow `*r` as mutable more than once at a time",
    );
    // 두 단계 빌림: v.push(v.len())는 된다
    expect_ok(r#"pub fn f(){ let mut v=vec![1usize]; v.push(v.len()); }"#);
    // 서로 다른 필드는 따로 가변으로 빌릴 수 있다
    expect_ok(
        r#"pub struct P{pub x:i32,pub y:i32} pub fn f(p:&mut P){ let a=&mut p.x; let b=&mut p.y; *a+=1; *b+=1; }"#,
    );
    // 메서드로 빌리면 self 전체를 빌린 것이 된다
    expect_error(
        r#"pub struct P{x:i32,y:i32} impl P{ fn xm(&mut self)->&mut i32{&mut self.x} fn ym(&mut self)->&mut i32{&mut self.y} }
           pub fn f(p:&mut P){ let a=p.xm(); let b=p.ym(); *a+=1; *b+=1; }"#,
        "E0499",
        "cannot borrow `*p` as mutable more than once at a time",
    );
}

fn lint_checks() {
    let n = SNIPPET.fetch_add(1, Ordering::SeqCst);
    let dir = std::env::temp_dir().join(format!("vr07c_{}_{n}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let file = dir.join("snippet.rs");
    std::fs::write(
        &file,
        "fn len(s: &str) -> usize { s.len() }\npub fn f(s: &str) -> usize { len(&s) }",
    )
    .unwrap();
    let out = Command::new("clippy-driver")
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .arg(&file)
        .output()
        .expect("clippy-driver 실행");
    let _ = std::fs::remove_dir_all(&dir);
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(stderr.contains("clippy::needless_borrow"), "{stderr}");
    println!("clippy needless_borrow 기본 경고 확인");
}

fn add_one(v: &mut Vec<i32>) {
    v.push(1);
}

fn runtime_checks() {
    // 참조는 주소 하나 크기다
    assert_eq!(std::mem::size_of::<&String>(), std::mem::size_of::<usize>());
    assert_eq!(
        std::mem::size_of::<&mut i32>(),
        std::mem::size_of::<usize>()
    );

    // NLL 예
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    let both = format!("{r1} and {r2}");
    let r3 = &mut s;
    r3.push('!');
    assert_eq!((both.as_str(), s.as_str()), ("hello and hello", "hello!"));

    // 자동 다시 빌리기
    let mut v = Vec::new();
    let r = &mut v;
    add_one(r);
    add_one(r);
    r.push(3);
    assert_eq!(v, [1, 1, 3]);

    // 한 슬라이스의 두 칸을 동시에 가변으로: split_at_mut
    let mut a = [10, 20, 30, 40];
    let (left, right) = a.split_at_mut(2);
    std::mem::swap(&mut left[0], &mut right[1]);
    assert_eq!(a, [40, 20, 30, 10]);

    // 두 단계 빌림
    let mut w = vec![7usize];
    w.push(w.len());
    assert_eq!(w, [7, 1]);
    println!("runtime: NLL, reborrow, split_at_mut, two-phase ok");
}

fn main() {
    compile_checks();
    lint_checks();
    runtime_checks();
    println!("ALL CHECKS PASSED");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reborrow_lets_us_pass_twice() {
        let mut v = Vec::new();
        let r = &mut v;
        add_one(r);
        add_one(r);
        assert_eq!(v.len(), 2);
    }
}