use std::fs;
use std::path::Path;
use std::process::Command;
use rust_split::{OutputFile, SplitOutput, explode, split_bin, split_mod};
const CORE_MOD: &str = r#"//! Synthetic resolver core: one struct and four banner-separated impl blocks.
mod model;
use model::*;
use std::collections::BTreeMap;
const EXTENTS: usize = 4;
/// Resolver state shared by every phase.
pub struct Core {
decls: BTreeMap<String, Decl>,
links: Vec<(String, String)>,
}
// ---- Syntax helpers ----
impl Core {
fn is_ident(text: &str) -> bool {
let mut chars = text.chars();
match chars.next() {
Some(first) if first.is_alphabetic() || first == '_' => {}
_ => return false,
}
chars.all(|c| c.is_alphanumeric() || c == '_')
}
fn split_decl(line: &str) -> Option<(&str, Option<&str>)> {
let mut parts = line.splitn(EXTENTS, '=');
let name = parts.next()?.trim();
let target = parts.next().map(str::trim);
Some((name, target))
}
}
// ---- Collection ----
impl Core {
pub fn new() -> Self {
Self {
decls: BTreeMap::new(),
links: Vec::new(),
}
}
pub fn collect(&mut self, lines: &[&str]) {
for line in lines {
if let Some((name, target)) = Self::split_decl(line) {
if Self::is_ident(name) {
self.decls.insert(
name.to_owned(),
Decl {
name: name.to_owned(),
target: target.map(str::to_owned),
},
);
}
}
}
}
}
// ---- Linking ----
impl Core {
fn link(&mut self) {
for decl in self.decls.values() {
if let Some(target) = &decl.target {
if self.decls.contains_key(target) {
self.links.push((decl.name.clone(), target.clone()));
}
}
}
}
}
// ---- Dump ----
impl Core {
pub fn dump(&self) -> String {
let mut out = String::with_capacity(core::mem::size_of::<usize>() * self.links.len());
for (from, to) in &self.links {
out.push_str(from);
out.push_str(" -> ");
out.push_str(to);
out.push('\n');
}
out
}
}
pub fn run(lines: &[&str]) -> String {
let mut core = Core::new();
core.collect(lines);
core.link();
core.dump()
}
"#;
const MODEL_RS: &str = "/// One collected declaration.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Decl {
pub name: String,
pub target: Option<String>,
}
";
const LIB_RS: &str = "pub mod foo;
#[cfg(test)]
mod tests {
#[test]
fn run_links_declared_names() {
assert_eq!(crate::foo::run(&[\"a = b\", \"b\", \"c = missing\"]), \"a -> b\\n\");
}
}
";
const MAX_LOC: usize = 50;
fn split_core() -> SplitOutput {
split_mod(&explode(CORE_MOD).unwrap(), MAX_LOC, "mod")
}
fn file<'a>(out: &'a SplitOutput, path: &str) -> &'a OutputFile {
out.files
.iter()
.find(|f| f.path == path)
.unwrap_or_else(|| panic!("no {path} in {:?}", paths(out)))
}
fn file_with<'a>(out: &'a SplitOutput, needle: &str) -> &'a OutputFile {
out.files
.iter()
.find(|f| f.contents.contains(needle))
.unwrap_or_else(|| panic!("no file holds {needle:?}"))
}
fn paths(out: &SplitOutput) -> Vec<&str> {
out.files.iter().map(|f| f.path.as_str()).collect()
}
fn declared_modules(root: &str) -> Vec<&str> {
root.lines()
.filter_map(|l| l.strip_prefix("mod ")?.strip_suffix(';'))
.collect()
}
fn compile_module_split(out: &SplitOutput, lib_rs: &str, extra: &[(&str, &str)]) -> String {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path();
fs::write(dir.join("lib.rs"), lib_rs).unwrap();
for f in &out.files {
let path = dir.join("foo").join(&f.path);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, &f.contents).unwrap();
}
for (path, contents) in extra {
fs::write(dir.join(path), contents).unwrap();
}
let mut diagnostics = rustc(dir, &["--crate-type", "lib", "-o", "lib.rlib"]);
diagnostics.push_str(&rustc(dir, &["--test", "-o", "harness"]));
let tests = Command::new(dir.join("harness")).output().unwrap();
assert!(
tests.status.success(),
"moved code must still behave:\n{}",
String::from_utf8_lossy(&tests.stdout)
);
diagnostics
}
fn rustc(dir: &Path, args: &[&str]) -> String {
let output = Command::new("rustc")
.current_dir(dir)
.args(["--edition", "2024", "--crate-name", "hygiene", "lib.rs"])
.args(args)
.output()
.expect("rustc must be runnable (it ships with cargo)");
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
assert!(output.status.success(), "rustc {args:?} failed:\n{stderr}");
stderr
}
#[test]
fn core_mod_compiles_before_the_split() {
let out = SplitOutput {
files: vec![
OutputFile {
path: "mod.rs".to_owned(),
contents: CORE_MOD.to_owned(),
loc: 0,
},
OutputFile {
path: "model.rs".to_owned(),
contents: MODEL_RS.to_owned(),
loc: 0,
},
],
still_oversized: Vec::new(),
};
compile_module_split(&out, LIB_RS, &[]);
}
#[test]
fn issue1_each_banner_heads_the_impl_it_names() {
let out = split_core();
for (banner, method) in [
("// ---- Syntax helpers ----", "fn is_ident("),
("// ---- Collection ----", "fn collect("),
("// ---- Linking ----", "fn link("),
("// ---- Dump ----", "fn dump("),
] {
let f = file_with(&out, banner);
let below = &f.contents[f.contents.find(banner).unwrap() + banner.len()..];
assert!(
below.trim_start().starts_with("impl Core {") && below.contains(method),
"`{banner}` must sit directly above the impl holding `{method}` in {}:\n{}",
f.path,
f.contents
);
}
for f in &out.files {
let last = f.contents.trim_end().lines().last().unwrap_or_default();
assert!(
!last.starts_with("// ----"),
"{} ends with a stray banner:\n{}",
f.path,
f.contents
);
}
}
#[test]
fn issue2_modules_are_named_after_their_section_banners() {
let out = split_core();
for (path, method) in [
("syntax_helpers.rs", "fn is_ident("),
("collection.rs", "fn collect("),
("linking.rs", "fn link("),
("dump.rs", "fn dump("),
] {
assert!(
file(&out, path).contents.contains(method),
"{path} must hold `{method}`"
);
}
}
#[test]
fn issue2_generated_names_never_shadow_a_crate_or_keyword() {
let src = "pub struct Core;\n\npub struct Std;\n\n\
pub fn alloc() -> usize {\n core::mem::size_of::<u64>()\n}\n\n\
pub struct Type;\n\npub fn proc_macro() {}\n";
let out = split_mod(&explode(src).unwrap(), MAX_LOC, "mod");
let root = file(&out, "mod.rs");
let modules = declared_modules(&root.contents);
for reserved in ["core", "std", "alloc", "proc_macro", "type"] {
assert!(
!modules.contains(&reserved),
"generated `mod {reserved};`:\n{}",
root.contents
);
}
assert!(modules.contains(&"core_"), "{modules:?}");
compile_module_split(&out, "pub mod foo;\n", &[]);
}
#[test]
fn issue2_the_core_struct_part_does_not_shadow_the_core_crate() {
let out = split_core();
let modules = declared_modules(&file(&out, "mod.rs").contents);
assert!(!modules.contains(&"core"), "{modules:?}");
}
#[test]
fn issue3_module_split_widens_only_to_pub_super() {
let out = split_core();
for f in &out.files {
assert!(
!f.contents.contains("pub(crate)"),
"{} widens to pub(crate):\n{}",
f.path,
f.contents
);
}
for expected in [
" decls: BTreeMap<String, Decl>,",
"pub(super) const EXTENTS: usize = 4;",
" pub(super) fn is_ident(text: &str) -> bool {",
" pub(super) fn link(&mut self) {",
"pub struct Core {",
" pub fn dump(&self) -> String {",
] {
file_with(&out, expected);
}
}
#[test]
fn issue3_a_private_only_part_is_reexported_with_a_private_glob() {
let src = "pub fn api() -> i32 {\n 1\n}\n\n\
fn helper() -> i32 {\n let base = leaf();\n base + 1\n}\n\n\
fn leaf() -> i32 {\n 2\n}\n\n\
pub fn total() -> i32 {\n 3\n}\n";
let out = split_mod(&explode(src).unwrap(), MAX_LOC, "mod");
let root = file(&out, "mod.rs");
assert!(
root.contents.lines().any(|l| l == "use helper::*;"),
"private-only part needs only a private glob:\n{}",
root.contents
);
assert!(root.contents.contains("pub use api::*;"));
let lib = "pub mod foo;\n\npub fn sum() -> i32 {\n foo::api() + foo::total()\n}\n";
let mut with_user = out.clone();
with_user.files[0]
.contents
.push_str("\npub fn uses_helper() -> i32 {\n helper()\n}\n");
let diagnostics = compile_module_split(&with_user, lib, &[]);
assert!(
!diagnostics.contains("public enough"),
"re-export visibility must match the items:\n{diagnostics}"
);
}
#[test]
fn issue3_crate_root_split_still_reexports_crate_wide() {
let src = "fn helper() -> i32 {\n 1\n}\n";
let out = split_bin(&explode(src).unwrap(), MAX_LOC, "lib");
assert!(
file(&out, "lib.rs")
.contents
.contains("pub(crate) use helper::*;")
);
assert!(
file(&out, "helper.rs")
.contents
.contains("pub(crate) fn helper")
);
}
#[test]
fn issue3_nested_split_groups_use_pub_super() {
let mut inner = String::from(" fn helper() -> i32 {\n 1\n }\n");
for i in 0..30 {
inner.push_str(&format!(
"\n #[test]\n fn t{i}() {{\n assert_eq!(helper(), 1);\n }}\n"
));
}
let src = format!("pub fn api() -> i32 {{\n 1\n}}\n\nmod tests {{\n{inner}}}\n");
let out = split_bin(&explode(&src).unwrap(), 60, "lib");
let groups: Vec<&OutputFile> = out
.files
.iter()
.filter(|f| f.path.starts_with("tests/g"))
.collect();
assert!(!groups.is_empty(), "{:?}", paths(&out));
for g in groups {
assert!(
!g.contents.contains("pub(crate) fn"),
"{} widens to pub(crate):\n{}",
g.path,
g.contents
);
}
file_with(&out, "pub(super) fn helper()");
}
#[test]
fn issue4_type_definition_stays_in_the_root_module() {
let out = split_core();
let root = file(&out, "mod.rs");
assert!(
root.contents
.contains("pub struct Core {\n decls: BTreeMap<String, Decl>,"),
"type definition left the root:\n{}",
root.contents
);
assert!(root.contents.contains("use std::collections::BTreeMap;"));
assert!(root.contents.contains("use model::*;"));
assert!(!root.contents.contains("use core_::*;"));
for path in [
"syntax_helpers.rs",
"collection.rs",
"linking.rs",
"dump.rs",
] {
assert!(!file(&out, path).contents.contains("struct Core"));
}
assert_eq!(
out.files
.iter()
.filter(|f| f.contents.contains("pub struct Core {"))
.count(),
1
);
}
#[test]
fn issue5_child_imports_reach_local_modules_through_super() {
let out = split_core();
let mut checked = 0;
for f in out.files.iter().filter(|f| f.path != "mod.rs") {
assert!(
!f.contents.lines().any(|l| l.trim() == "use model::*;"),
"{} relies on the sibling glob for `model`:\n{}",
f.path,
f.contents
);
if f.contents.contains("use super::model::*;") {
checked += 1;
}
}
assert!(checked > 0, "no part imports `super::model::*`");
}
#[test]
fn issue5_crate_root_parts_anchor_local_enums_at_crate() {
let src = "use Kind::*;\n\npub enum Kind {\n A,\n B,\n}\n\n\
pub fn first() -> Kind {\n A\n}\n\npub fn other() -> i32 {\n 1\n}\n";
let out = split_bin(&explode(src).unwrap(), MAX_LOC, "lib");
let part = file_with(&out, "fn first");
assert!(
part.contents.contains("use crate::Kind::*;"),
"{}:\n{}",
part.path,
part.contents
);
}
#[test]
fn field_shaped_module_split_compiles_and_runs() {
let out = split_core();
assert!(out.still_oversized.is_empty(), "{:?}", out.still_oversized);
for f in &out.files {
assert!(f.loc < MAX_LOC, "{} over budget: {}", f.path, f.loc);
}
let diagnostics = compile_module_split(&out, LIB_RS, &[("foo/model.rs", MODEL_RS)]);
assert!(!diagnostics.contains("public enough"), "{diagnostics}");
}