rust-split 0.2.2

A parser-based god-file splitter for Rust source.
Documentation
//! Counterexamples from the peer-blind T6 acceptance reviews.
use std::{collections::BTreeSet, fs, path::Path, process::Command};

use rust_split::{SplitOutput, explode, split_bin, split_mod};

fn compile(out: &SplitOutput, module: bool, check_private: bool) {
    let temp = tempfile::tempdir().unwrap();
    let dir = temp.path();
    let mut paths = BTreeSet::new();
    for file in &out.files {
        assert!(paths.insert(&file.path), "duplicate path: {}", file.path);
        let path = dir.join(&file.path);
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        fs::write(path, &file.contents).unwrap();
    }
    if module {
        fs::write(dir.join("lib.rs"), "pub mod foo;\n").unwrap();
    }
    rustc(
        dir,
        &[
            "--crate-type=lib",
            "--crate-name=checked",
            "lib.rs",
            "-o",
            "libchecked.rlib",
        ],
    );
    let prefix = if module { "checked::foo" } else { "checked" };
    fs::write(
        dir.join("main.rs"),
        format!("fn main() {{ assert_eq!({prefix}::run(), \"7\"); }}"),
    )
    .unwrap();
    let exe = format!("harness{}", std::env::consts::EXE_SUFFIX);
    rustc(
        dir,
        &["main.rs", "--extern", "checked=libchecked.rlib", "-o", &exe],
    );
    assert!(Command::new(dir.join(exe)).status().unwrap().success());
    if check_private {
        fs::write(
            dir.join("outside.rs"),
            format!("fn main() {{ let _ = {prefix}::State::new(); }}"),
        )
        .unwrap();
        let denied = Command::new("rustc")
            .current_dir(dir)
            .args([
                "--edition=2024",
                "outside.rs",
                "--extern",
                "checked=libchecked.rlib",
            ])
            .output()
            .unwrap();
        assert!(!denied.status.success());
        assert!(String::from_utf8_lossy(&denied.stderr).contains("E0624"));
    }
}

fn rustc(dir: &Path, args: &[&str]) {
    let result = Command::new("rustc")
        .current_dir(dir)
        .arg("--edition=2024")
        .args(args)
        .output()
        .unwrap();
    assert!(
        result.status.success(),
        "{}",
        String::from_utf8_lossy(&result.stderr)
    );
}

fn both(src: &str, ceiling: usize, mut check: impl FnMut(&SplitOutput, bool)) {
    let exploded = explode(src).unwrap();
    assert_eq!(
        exploded
            .chunks
            .iter()
            .map(|c| c.text.as_str())
            .collect::<String>(),
        src
    );
    for module in [false, true] {
        let out = if module {
            split_mod(&exploded, ceiling, "foo")
        } else {
            split_bin(&exploded, ceiling, "lib")
        };
        assert!(out.still_oversized.is_empty(), "{:?}", out.still_oversized);
        for _ in 0..5 {
            let again = if module {
                split_mod(&exploded, ceiling, "foo")
            } else {
                split_bin(&exploded, ceiling, "lib")
            };
            assert_eq!(out.files, again.files);
        }
        check(&out, module);
    }
}

#[test]
fn retained_owners_keep_bound_where_clause_and_default_imports() {
    for (decl, constructor, read) in [
        (
            "pub struct State<T: Copy + Debug = Byte> { value: T }",
            "Self { value }",
            "state.value",
        ),
        (
            "pub struct State<T = Byte> where T: Copy + Debug { value: T }",
            "Self { value }",
            "state.value",
        ),
        (
            "pub enum State<T: Copy + Debug = Byte> { Value(T) }",
            "Self::Value(value)",
            "match state { State::Value(value) => value }",
        ),
        (
            "pub union State<T: Copy + Debug = Byte> { value: T }",
            "Self { value }",
            "unsafe { state.value }",
        ),
    ] {
        let src = format!(
            "use std::fmt::Debug;\nuse std::primitive::u8 as Byte;\n{decl}\n\
            impl<T: Copy + Debug> State<T> {{\n\
                pub fn new(value: T) -> Self {{ let _: &dyn Debug = &value; {constructor} }}\n\
            }}\npub fn run() -> String {{ let state = State::new(7u8); format!(\"{{}}\", {read}) }}\n"
        );
        both(&src, 100, |out, module| {
            let root = &out.files[0].contents;
            assert!(root.contains(decl));
            assert!(root.contains("use std::fmt::Debug;"));
            assert!(root.contains("use std::primitive::u8 as Byte;"));
            compile(out, module, false);
        });
    }
}

#[test]
fn owner_local_methods_preserve_scope_and_visibility_comments() {
    for restriction in ["pub(self)", "pub(in self)", "pub(/* owner scope */ self)"] {
        let src = format!(
            "pub struct State;\nimpl State {{ {restriction} fn new() -> Self {{ Self }} }}\n\
            pub fn run() -> String {{ let _ = State::new(); 7.to_string() }}\n"
        );
        both(&src, 100, |out, module| {
            assert!(out.files[0].contents.contains("pub struct State;"));
            assert!(out.files[0].contents.contains(restriction));
            if restriction.contains("/*") {
                assert!(
                    out.files
                        .iter()
                        .any(|f| f.contents.contains("/* owner scope */"))
                );
            }
            compile(out, module, true);
        });
    }
}

const MACRO_OWNER: &str = "macro_rules! field { () => { u8 }; }\n\
    struct State { value: field!() }\n\
    impl State { fn new() -> Self { Self { value: 7 } } }\n\
    pub fn run() -> String { State::new().value.to_string() }\n";

#[test]
fn retained_owner_keeps_its_local_type_macro_in_both_topologies() {
    both(MACRO_OWNER, 100, |out, module| {
        let root = &out.files[0].contents;
        assert!(root.contains("struct State { value: field!() }"));
        assert!(root.find("macro_rules! field").unwrap() < root.find("struct State").unwrap());
        compile(out, module, false);
    });
}

#[test]
fn retained_owner_local_impl_keeps_its_lexical_dependencies() {
    let src = "macro_rules! value { () => { 7 }; }\n\
        pub struct State;\n\
        impl State { pub(self) fn new() -> Self { assert_eq!(value!(), 7); Self } }\n\
        pub fn run() -> String { let _ = State::new(); 7.to_string() }\n";
    both(src, 100, |out, module| compile(out, module, true));
}

#[test]
fn local_macro_dependencies_and_redefinitions_keep_original_textual_consumers() {
    let src = "macro_rules! scalar { () => { u8 }; }\n\
        macro_rules! field { () => { scalar!() }; }\n\
        fn before() -> usize { std::mem::size_of::<field!()>() }\n\
        struct State { value: field!() }\n\
        impl State { fn new() -> Self { Self { value: 7 } } }\n\
        macro_rules! scalar { () => { u64 }; }\n\
        fn after() -> usize { std::mem::size_of::<field!()>() }\n\
        pub fn run() -> String {\n\
            assert_eq!(before(), 1);\n\
            assert_eq!(after(), 8);\n\
            State::new().value.to_string()\n\
        }\n";
    both(src, 100, |out, module| compile(out, module, false));
}

#[test]
fn recursive_owner_keeps_its_lexical_macro() {
    let inner = MACRO_OWNER.replace("pub fn run()", "pub fn execute()");
    let padding = "let _ = 0;\n".repeat(20);
    let src = format!(
        "mod inner {{\n{inner}\nfn pad_a() {{\n{padding}}}\nfn pad_b() {{\n{padding}}}\n}}\n\
        pub fn run() -> String {{ inner::execute() }}\n"
    );
    both(&src, 40, |out, module| {
        let root = out
            .files
            .iter()
            .find(|f| f.path.ends_with("inner/mod.rs"))
            .unwrap();
        assert!(root.contents.contains("struct State { value: field!() }"));
        assert!(root.contents.contains("macro_rules! field"));
        compile(out, module, false);
    });
}

#[test]
fn recursive_groups_avoid_retained_types_and_fixed_module_names() {
    let padding = "let _ = 0;\n".repeat(20);
    let src = format!(
        "mod inner {{\n#![allow(non_camel_case_types)]\n\
        struct g00;\nimpl g00 {{}}\n\
        struct g00_;\nimpl g00_ {{}}\n\
        mod g01 {{}}\n\
        fn pad_a() {{\n{padding}}}\nfn pad_b() {{\n{padding}}}\n\
        pub fn execute() -> String {{ 7.to_string() }}\n}}\n\
        pub fn run() -> String {{ inner::execute() }}\n"
    );
    both(&src, 40, |out, module| {
        let root = out
            .files
            .iter()
            .find(|f| f.path.ends_with("inner/mod.rs"))
            .unwrap();
        assert!(root.contents.contains("struct g00;"));
        assert!(root.contents.contains("struct g00_;"));
        assert!(root.contents.contains("mod g00__;"));
        compile(out, module, false);
    });
}

#[test]
fn oversized_lexical_dependencies_stay_in_order_and_report_parent_overflow() {
    let source = MACRO_OWNER.replace(
        "{ u8 }",
        &format!("{{\n{}u8\n}}", "// macro body\n".repeat(35)),
    );
    let exploded = explode(&source).unwrap();
    for module in [false, true] {
        let out = if module {
            split_mod(&exploded, 30, "foo")
        } else {
            split_bin(&exploded, 30, "lib")
        };
        assert!(!out.still_oversized.is_empty());
        assert!(out.files[0].loc >= 30);
        let root = &out.files[0].contents;
        assert!(root.find("macro_rules! field").unwrap() < root.find("struct State").unwrap());
        let mut indices = rust_split::plan_split(&exploded, 25).root_items;
        let plan = rust_split::plan_split(&exploded, 25);
        indices.extend(plan.header);
        indices.extend(plan.mods);
        indices.extend(plan.oversized.iter().map(|o| o.chunk_index));
        indices.extend(
            plan.parts
                .iter()
                .flat_map(|p| p.chunk_indices.iter().copied()),
        );
        indices.sort_unstable();
        assert_eq!(indices, (0..exploded.chunks.len()).collect::<Vec<_>>());
        compile(&out, module, false);
    }
}

#[test]
fn retained_aliases_reserve_generated_names_with_chained_collisions() {
    for banner_on_impl in [false, true] {
        let banner = "\n// ---- Worker ----\n\n";
        let src = format!(
            "#![allow(non_camel_case_types)]\n\
            macro_rules! field {{ () => {{ u8 }}; }}\n\
            type worker = field!();\ntype worker_ = field!();\n\
            struct State {{ value: field!() }}\n{}\
            impl State {{ fn new() -> Self {{ Self {{ value: 7 }} }} }}\n{}\
            pub fn run() -> String {{ let x: worker = State::new().value; let y: worker_ = x; y.to_string() }}\n",
            if banner_on_impl { banner } else { "" },
            if banner_on_impl { "" } else { banner }
        );
        both(&src, 100, |out, module| {
            let root = &out.files[0].contents;
            assert!(root.contains("type worker = field!();"));
            assert!(root.contains("type worker_ = field!();"));
            assert!(root.contains("mod worker__;"));
            compile(out, module, false);
        });
    }
}

#[test]
fn retained_traits_reserve_generated_module_names() {
    let src = "#![allow(non_camel_case_types)]\n\
        macro_rules! field { () => { u8 }; }\n\
        struct State { value: field!() }\n\
        trait worker { fn get(&self) -> field!(); }\n\
        impl worker for State { fn get(&self) -> u8 { self.value } }\n\n\
        // ---- Worker ----\n\n\
        impl State { fn new() -> Self { Self { value: 7 } } }\n\
        pub fn run() -> String { State::new().get().to_string() }\n";
    both(src, 100, |out, module| {
        assert!(out.files[0].contents.contains("trait worker"));
        assert!(out.files[0].contents.contains("mod worker_;"));
        compile(out, module, false);
    });
}