소유권과 이동 문서의 검증 코드다.

//! 소유권과 이동 문서의 주장을 확인한다: 이동 뒤 원래 변수 사용 금지(오류 코드), 이동해도 힙 주소가 그대로인 것,
//! clone은 힙을 새로 잡는 것, String의 크기, Copy·Clone 규칙, drop 순서(선언의 역순, 필드는 선언 순서, 부분 이동).
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/05_ownership-move_verify.rs"

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

static SNIPPET: AtomicUsize = AtomicUsize::new(0);
static LOG: Mutex<Vec<String>> = Mutex::new(Vec::new());

/// 코드 조각을 rustc로 컴파일해 stderr를 돌려준다.
fn compile_errors(code: &str) -> String {
    let n = SNIPPET.fetch_add(1, Ordering::SeqCst);
    let dir = std::env::temp_dir().join(format!("vr05_{}_{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}"
    );
}

/// clippy-driver로 코드 조각을 검사해 stderr를 돌려준다.
fn clippy(code: &str, flags: &[&str]) -> String {
    let n = SNIPPET.fetch_add(1, Ordering::SeqCst);
    let dir = std::env::temp_dir().join(format!("vr05c_{}_{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("clippy-driver")
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .args(flags)
        .arg(&file)
        .output()
        .expect("clippy-driver 실행");
    let _ = std::fs::remove_dir_all(&dir);
    String::from_utf8_lossy(&out.stderr).into_owned()
}

fn lint_checks() {
    // clone_on_copy: 기본으로 경고
    let c = clippy("pub fn n(x: i32) -> i32 { x.clone() + 1 }", &[]);
    assert!(c.contains("clippy::clone_on_copy"), "{c}");
    // needless_pass_by_value: pedantic에서만
    let code = "pub fn n(s: String) -> usize { s.len() }";
    // 기본 실행이 실제로 컴파일을 마쳤는지("error" 없음)도 함께 본다. 호출이 실패하면 린트가 없어도 그만이다.
    let d = clippy(code, &[]);
    assert!(
        !d.contains("error") && !d.contains("needless_pass_by_value"),
        "{d}"
    );
    assert!(clippy(code, &["-W", "clippy::pedantic"]).contains("clippy::needless_pass_by_value"));
    // redundant_clone: nursery에서만
    let code = "pub fn n(s: &str) -> usize { let t = s.to_string(); let u = t.clone(); u.len() }";
    let d = clippy(code, &[]);
    assert!(
        !d.contains("error") && !d.contains("redundant_clone"),
        "{d}"
    );
    assert!(clippy(code, &["-W", "clippy::nursery"]).contains("clippy::redundant_clone"));
    println!(
        "clippy: clone_on_copy 기본, needless_pass_by_value pedantic, redundant_clone nursery"
    );
}

fn log(s: &str) {
    LOG.lock().unwrap().push(s.to_string());
}

fn take_log() -> Vec<String> {
    std::mem::take(&mut *LOG.lock().unwrap())
}

struct Noisy(&'static str);

impl Drop for Noisy {
    fn drop(&mut self) {
        log(&format!("drop({})", self.0));
    }
}

struct Pair {
    first: Noisy,
    second: Noisy,
}

fn consume(n: Noisy) {
    log(&format!("consume {}", n.0));
} // n은 여기서 drop된다

fn give_back(n: Noisy) -> Noisy {
    n
}

fn compile_checks() {
    // 이동 뒤 원래 변수를 읽으면 E0382
    expect_error(
        r#"pub fn f(){ let s1=String::from("hi"); let s2=s1; println!("{s1} {s2}"); }"#,
        "E0382",
        "borrow of moved value: `s1`",
    );
    // 함수 인자로 넘기면 이동한다
    expect_error(
        r#"fn take(_s:String){} pub fn f(){ let s=String::from("hi"); take(s); println!("{s}"); }"#,
        "E0382",
        "value moved here",
    );
    // 반복문 안에서 이동하면 두 번째 바퀴에서 E0382
    expect_error(
        r#"pub fn f(){ let s=String::from("a"); for _ in 0..2 { drop(s); } }"#,
        "E0382",
        "in previous iteration of loop",
    );
    // Vec의 칸에서 꺼내 옮길 수 없다
    expect_error(
        r#"pub fn f(){ let v=vec![String::from("a")]; let s=v[0]; drop(s); }"#,
        "E0507",
        "cannot move out of index of `Vec<String>`",
    );
    // Drop을 구현한 타입의 필드는 꺼낼 수 없다
    expect_error(
        r#"pub struct G{pub name:String} impl Drop for G{fn drop(&mut self){}} pub fn f(g:G)->String{ g.name }"#,
        "E0509",
        "which implements the `Drop` trait",
    );
    // 부분 이동 뒤 전체를 쓰면 E0382
    expect_error(
        r#"pub struct P{pub a:String,pub b:String} pub fn f(p:P){ let a=p.a; drop(a); let q=p; drop(q); }"#,
        "E0382",
        "use of partially moved value: `p`",
    );
    // 부분 이동 뒤 남은 필드는 따로 쓸 수 있다
    expect_ok(
        r#"pub struct P{pub a:String,pub b:String} pub fn f(p:P)->String{ let a=p.a; let b=p.b; a+&b }"#,
    );
    // 옮긴 변수에 새 값을 넣으면 다시 쓸 수 있다
    expect_ok(
        r#"pub fn f(){ let mut s=String::from("a"); drop(s); s=String::from("b"); println!("{s}"); }"#,
    );
    // String 필드가 있으면 Copy가 될 수 없다
    expect_error(
        r#"#[derive(Clone,Copy)] pub struct S{pub s:String}"#,
        "E0204",
        "the trait `Copy` cannot be implemented for this type",
    );
    // Drop과 Copy는 함께 못 쓴다
    expect_error(
        r#"#[derive(Clone,Copy)] pub struct S; impl Drop for S{fn drop(&mut self){}}"#,
        "E0184",
        "the trait `Copy` cannot be implemented for this type; the type has a destructor",
    );
    // drop 메서드를 직접 부를 수 없다
    expect_error(
        r#"pub struct S; impl Drop for S{fn drop(&mut self){}} pub fn f(){ let s=S; s.drop(); }"#,
        "E0040",
        "explicit use of destructor method",
    );
}

fn heap_checks() {
    // String은 포인터·길이·용량 세 칸이다
    assert_eq!(
        std::mem::size_of::<String>(),
        3 * std::mem::size_of::<usize>()
    );
    println!("size_of::<String>() = {}", std::mem::size_of::<String>());

    // 이동: 스택의 세 칸만 옮기고 힙 주소는 그대로다
    let s1 = String::from("hello");
    let heap = s1.as_ptr();
    let s2 = s1;
    assert_eq!(s2.as_ptr(), heap);
    assert_eq!((s2.len(), s2.capacity()), (5, 5));

    // clone: 힙을 새로 잡아 내용을 베낀다
    let s3 = s2.clone();
    assert_ne!(s3.as_ptr(), s2.as_ptr());
    assert_eq!(s3, s2);

    // Copy: 정수와 Copy인 것만 담은 튜플은 대입해도 원래 변수를 쓸 수 있다
    let x = 5;
    let y = x;
    let t = (1, 2.5);
    let u = t;
    assert_eq!((x, y, t, u), (5, 5, (1, 2.5), (1, 2.5)));
    println!("move keeps heap ptr, clone makes a new one");
}

fn drop_order_checks() {
    // 지역 변수는 선언의 역순, 튜플 칸은 앞에서부터
    {
        let _a = Noisy("a");
        let _b = Noisy("b");
        let _t = (Noisy("t0"), Noisy("t1"));
    }
    assert_eq!(take_log(), ["drop(t0)", "drop(t1)", "drop(b)", "drop(a)"]);

    // 구조체 필드는 선언 순서
    {
        let p = Pair {
            second: Noisy("second"),
            first: Noisy("first"),
        };
        log(&format!("{} {}", p.first.0, p.second.0));
    }
    assert_eq!(take_log(), ["first second", "drop(first)", "drop(second)"]);

    // 문서의 추적 문제: 함수로 옮긴 값은 함수 끝에서 drop된다
    {
        let _a = Noisy("a");
        let b = Noisy("b");
        let _pair = (Noisy("p0"), Noisy("p1"));
        consume(b);
        log("end");
    }
    let trace = take_log();
    println!("trace: {}", trace.join(" / "));
    assert_eq!(
        trace,
        [
            "consume b",
            "drop(b)",
            "end",
            "drop(p0)",
            "drop(p1)",
            "drop(a)"
        ]
    );

    // 돌려받은 값은 받은 쪽 변수가 주인이 된다
    {
        let c = Noisy("c");
        let c2 = give_back(c);
        log("got back");
        drop(c2);
        log("after drop");
    }
    assert_eq!(take_log(), ["got back", "drop(c)", "after drop"]);

    // 새 값을 대입하면 옛 값은 바로 drop된다
    {
        let mut v = Noisy("old");
        log(&format!("before {}", v.0));
        v = Noisy("new");
        log("after");
        let _ = &v;
    }
    assert_eq!(
        take_log(),
        ["before old", "drop(old)", "after", "drop(new)"]
    );

    // 부분 이동: 남은 필드만 범위 끝에서 drop된다
    {
        let p = Pair {
            first: Noisy("pf"),
            second: Noisy("ps"),
        };
        let moved = p.first;
        log("moved first");
        drop(moved);
        log("end of scope");
    }
    assert_eq!(
        take_log(),
        ["moved first", "drop(pf)", "end of scope", "drop(ps)"]
    );

    // Vec 원소는 앞에서부터
    {
        let _v = Vec::from([Noisy("v0"), Noisy("v1"), Noisy("v2")]);
        let _arr = [Noisy("a0"), Noisy("a1")];
    }
    assert_eq!(
        take_log(),
        ["drop(a0)", "drop(a1)", "drop(v0)", "drop(v1)", "drop(v2)"]
    );
}

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

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

    #[test]
    fn move_keeps_pointer_and_drops_once() {
        {
            let a = Noisy("x");
            let b = a;
            log(b.0);
        }
        assert_eq!(take_log(), ["x", "drop(x)"]);
    }
}