제네릭과 트레이트 경계 문서의 검증 코드다.

//! 17 제네릭과 트레이트 경계: 경계가 없으면 연산을 못 쓰는 것(E0369), where 절, 여러 경계, const 제네릭,
//! 단형화(monomorphization)로 타입마다 함수 사본이 생기는 것(LLVM IR에서 정의 개수 세기),
//! 무거운 몸통을 제네릭이 아닌 안쪽 함수로 빼면 사본이 바깥에만 생기는 것, `P: AsRef<Path>` 경계,
//! 타입 매개변수의 기본 Sized 경계와 ?Sized(E0277)를 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/17_generics-trait-bounds_verify.rs"

use std::fmt::{Debug, Display};
use std::sync::atomic::{AtomicUsize, Ordering};

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

fn snippet_dir() -> std::path::PathBuf {
    let n = SNIPPET.fetch_add(1, Ordering::Relaxed);
    let dir = std::env::temp_dir().join(format!("vr17_{}_{}", std::process::id(), n));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

fn compile_errors(code: &str) -> String {
    let dir = snippet_dir();
    let file = dir.join("snippet.rs");
    std::fs::write(&file, code).unwrap();
    let out = std::process::Command::new("rustc")
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .arg(&file)
        .output()
        .expect("rustc 실행");
    String::from_utf8_lossy(&out.stderr).into_owned()
}

/// 최적화를 켜고 LLVM IR을 만든 뒤, 이름에 `name`이 들어간 함수 정의 줄 수를 센다.
fn count_definitions(code: &str, name: &str) -> usize {
    let dir = snippet_dir();
    let file = dir.join("snippet.rs");
    std::fs::write(&file, code).unwrap();
    let out = std::process::Command::new("rustc")
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "-C",
            "opt-level=2",
            "--emit=llvm-ir",
            "--out-dir",
        ])
        .arg(&dir)
        .arg(&file)
        .output()
        .expect("rustc 실행");
    assert!(
        out.status.success(),
        "{}",
        String::from_utf8_lossy(&out.stderr)
    );
    let ir = std::fs::read_to_string(dir.join("snippet.ll")).unwrap();
    ir.lines()
        .filter(|l| l.starts_with("define") && l.contains(name))
        .count()
}

// 1. 트레이트 경계: 비교(PartialOrd)와 복사(Copy)를 할 수 있는 타입만 받는다.
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut best = list[0];
    for &item in list {
        if item > best {
            best = item;
        }
    }
    best
}

// 2. where 절: 경계가 길 때 시그니처를 읽기 쉽게 한다.
fn describe<T, U>(t: &T, u: &U) -> String
where
    T: Display + Clone,
    U: Clone + Debug,
{
    format!("{t} / {u:?}")
}

// 3. 경계를 조건으로 메서드 붙이기: T가 Display일 때만 show가 생긴다.
struct Pair<T> {
    a: T,
    b: T,
}

impl<T> Pair<T> {
    fn new(a: T, b: T) -> Self {
        Pair { a, b }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn show_larger(&self) -> String {
        if self.a >= self.b {
            format!("큰 쪽 a = {}", self.a)
        } else {
            format!("큰 쪽 b = {}", self.b)
        }
    }
}

// 4. const 제네릭: 배열 길이를 타입의 일부로 받는다.
fn sum_array<const N: usize>(xs: [i32; N]) -> i32 {
    xs.iter().sum()
}

struct Matrix<const R: usize, const C: usize> {
    cells: [[i32; C]; R],
}

impl<const R: usize, const C: usize> Matrix<R, C> {
    fn transpose(&self) -> Matrix<C, R> {
        let mut out = [[0; R]; C];
        for (i, row) in self.cells.iter().enumerate() {
            for (j, &v) in row.iter().enumerate() {
                out[j][i] = v;
            }
        }
        Matrix { cells: out }
    }
}

// 6. ?Sized: 크기를 모르는 타입(str, dyn Display)도 참조 뒤에서 받는다.
fn shout<T: Display + ?Sized>(x: &T) -> String {
    format!("{x}!")
}

// 1-1. P: AsRef<Path> 경계
fn path_len<P: AsRef<std::path::Path>>(p: P) -> usize {
    p.as_ref().as_os_str().len()
}

fn main() {
    // 1. 경계
    assert_eq!(largest(&[34, 50, 25, 100, 65]), 100);
    assert_eq!(largest(&['y', 'm', 'a', 'q']), 'y');
    assert_eq!(largest(&[1.5, -2.0, 0.25]), 1.5);
    println!(
        "largest: {} {} {}",
        largest(&[34, 50, 25, 100, 65]),
        largest(&['y', 'm', 'a', 'q']),
        largest(&[1.5, -2.0, 0.25])
    );
    let e = compile_errors(
        "pub fn largest<T>(list: &[T]) -> &T {
             let mut best = &list[0];
             for item in list { if item > best { best = item; } }
             best
         }",
    );
    println!(
        "[경계 없이 >] {}",
        e.lines().find(|l| l.starts_with("error")).unwrap()
    );
    assert!(e.contains("error[E0369]"));
    let e = compile_errors(
        "pub fn largest<T: PartialOrd>(list: &[T]) -> T {
             let mut best = list[0];
             for &item in list { if item > best { best = item; } }
             best
         }",
    );
    println!(
        "[Copy 경계 없이 값 꺼내기] {}",
        e.lines().find(|l| l.starts_with("error")).unwrap()
    );
    assert!(e.contains("error[E0508]") || e.contains("error[E0507]"));
    // 참조를 돌려주면 Copy 없이도 된다.
    let e = compile_errors(
        "pub fn largest<T: PartialOrd>(list: &[T]) -> &T {
             let mut best = &list[0];
             for item in list { if item > best { best = item; } }
             best
         }",
    );
    assert!(!e.contains("error"), "{e}");

    // 1-1. File::open과 같은 모양의 경계: 경로로 볼 수 있는 것은 모두 받는다.
    let path_buf = std::path::PathBuf::from("dir/c.txt");
    assert_eq!(path_len("a.txt"), 5);
    assert_eq!(path_len(String::from("b/b.txt")), 7);
    assert_eq!(path_len(&path_buf), 9);
    assert_eq!(path_len(path_buf), 9);

    // 2. where 절
    assert_eq!(describe(&"hi", &vec![1, 2]), "hi / [1, 2]");

    // 3. 조건부 메서드
    let p = Pair::new(3, 9);
    assert_eq!(p.show_larger(), "큰 쪽 b = 9");
    let e = compile_errors(
        "pub struct Pair<T> { a: T, b: T }
         impl<T: std::fmt::Display + PartialOrd> Pair<T> { pub fn show_larger(&self) -> bool { self.a > self.b } }
         pub struct NoDisplay;
         pub fn f(p: Pair<NoDisplay>) -> bool { p.show_larger() }",
    );
    println!(
        "[경계를 못 채운 타입에서 조건부 메서드] {}",
        e.lines().find(|l| l.starts_with("error")).unwrap()
    );
    assert!(e.contains("error[E0599]"));

    // 4. const 제네릭
    assert_eq!(sum_array([1, 2, 3]), 6);
    assert_eq!(sum_array([10; 5]), 50);
    let m = Matrix {
        cells: [[1, 2, 3], [4, 5, 6]],
    };
    let t = m.transpose();
    assert_eq!(t.cells, [[1, 4], [2, 5], [3, 6]]);
    println!("transpose 2x3 -> 3x2: {:?}", t.cells);
    let e = compile_errors(
        "pub struct Matrix<const R: usize, const C: usize> { pub cells: [[i32; C]; R] }
         pub fn f(a: Matrix<2, 3>) -> Matrix<3, 2> { a }",
    );
    println!(
        "[길이가 다른 행렬] {}",
        e.lines().find(|l| l.starts_with("error")).unwrap()
    );
    assert!(e.contains("error[E0308]"));

    // 5. 단형화: 쓴 타입 수만큼 함수 사본이 생긴다.
    let generic_one = "
        #[inline(never)]
        fn sum_sq<T: Copy + Into<f64>>(xs: &[T]) -> f64 {
            xs.iter().map(|&x| { let v: f64 = x.into(); v * v }).sum()
        }
        pub fn use_all(a: &[u8]) -> f64 { sum_sq(a) }";
    let generic_six = "
        #[inline(never)]
        fn sum_sq<T: Copy + Into<f64>>(xs: &[T]) -> f64 {
            xs.iter().map(|&x| { let v: f64 = x.into(); v * v }).sum()
        }
        pub fn use_all(a: &[u8], b: &[u16], c: &[u32], d: &[i8], e: &[i16], f: &[i32]) -> f64 {
            sum_sq(a) + sum_sq(b) + sum_sq(c) + sum_sq(d) + sum_sq(e) + sum_sq(f)
        }";
    let one = count_definitions(generic_one, "sum_sq");
    let six = count_definitions(generic_six, "sum_sq");
    println!("sum_sq 정의 개수: 타입 1개로 부름 -> {one}, 타입 6개로 부름 -> {six}");
    assert_eq!(one, 1);
    assert_eq!(six, 6);
    let generic_repeat = "
        #[inline(never)]
        fn sum_sq<T: Copy + Into<f64>>(xs: &[T]) -> f64 {
            xs.iter().map(|&x| { let v: f64 = x.into(); v * v }).sum()
        }
        pub fn use_all(a: &[u8], b: &[u16], c: &[u8]) -> f64 { sum_sq(a) + sum_sq(b) + sum_sq(c) }";
    let repeat = count_definitions(generic_repeat, "sum_sq");
    println!("sum_sq 정의 개수: u8, u16, u8로 세 번 부름 -> {repeat}");
    assert_eq!(repeat, 2);

    // 바깥은 제네릭, 무거운 몸통은 제네릭이 아닌 안쪽 함수 하나로: 사본은 얇은 바깥만 늘어난다.
    let thin_outer = "
        #[inline(never)]
        fn word_count<S: AsRef<str>>(s: S) -> usize {
            #[inline(never)]
            fn inner(s: &str) -> usize {
                s.split_whitespace().filter(|w| w.len() > 3).count() + s.lines().count()
            }
            inner(s.as_ref())
        }
        pub fn use_all(a: &str, b: String, c: &String, d: Box<str>,
                       e: std::borrow::Cow<'static, str>, f: std::rc::Rc<str>) -> usize {
            word_count(a) + word_count(b) + word_count(c) + word_count(d) + word_count(e) + word_count(f)
        }";
    let inner = count_definitions(thin_outer, "5inner");
    let outer = count_definitions(thin_outer, "10word_count") - inner;
    println!("word_count 바깥 사본 {outer}개, 안쪽 inner {inner}개");
    assert_eq!((outer, inner), (6, 1));

    // 6. ?Sized
    let d: &dyn Display = &5;
    assert_eq!(shout("hi"), "hi!");
    assert_eq!(shout(d), "5!");
    println!("shout: {} {}", shout("hi"), shout(d));
    let e = compile_errors(
        "pub fn byte_len<T: AsRef<[u8]>>(x: &T) -> usize { x.as_ref().len() }
         pub fn g() -> usize { byte_len(\"hi\") }",
    );
    println!(
        "[?Sized 없이 str] {}",
        e.lines().find(|l| l.starts_with("error")).unwrap()
    );
    assert!(e.contains("error[E0277]"));
    let e = compile_errors(
        "pub fn byte_len<T: AsRef<[u8]> + ?Sized>(x: &T) -> usize { x.as_ref().len() }
         pub fn g() -> usize { byte_len(\"hi\") }",
    );
    assert!(!e.contains("error"), "{e}");

    println!("ALL CHECKS PASSED");
}