The best fuzzer for a refactoring tool isn’t a fixture file — it’s a real, gnarly codebase. Splitting the oxisqlite-core/oxisql sqlite3-parser crate found eight distinct ways SplitRS’s generated code could fail to compile.
Today we released SplitRS 0.3.5 — another pure correctness release, and like 0.3.4 it has an empty Added section. Every one of its eight fixes traces back to a real compiler error hit while splitting a real crate, and every one of them is fixed.
SplitRS is the AST-based Rust refactoring tool that splits oversized source files into clean, compilable modules: it parses with syn, clusters methods and types, infers visibility, and writes the imports and mod.rs facades for you. No C, no external formatter shelling out to system tools — it links prettyplease in-process and compiles to a single static binary.
Why 0.3.5 matters
Running a splitter against a large, real-world parser crate exercises code shapes a hand-written test suite doesn’t always think to cover — file-backed submodules, cross-chunk method calls, macro-defined types, trait-bound imports. 0.3.5 fixes the eight failure modes that dogfooding on sqlite3-parser actually found:
- File-backed
mod x;declarations stay put. A realpub mod check;/mod fmt;line with no inline body used to get swept into a generated bucket file like any other item. NewFileAnalyzer::file_backed_modskeeps it declared verbatim in the regenerated rootmod.rs, andrewrite_pinned_mod_refs_in_userewrites any forwardedusethat pointed at it with asuper::prefix — closingE0583(wrong sibling-file lookup) andE0432(broken absolute-path references). - Cross-chunk method calls under
--split-impl-blocksresolve again. When oneimpl-block chunk called a private method that only lived in a siblingmethod_groupchunk, the generator didn’t recognize that sibling as the method’s definition site.FileAnalyzer::module_defined_callablesnow does, fixingE0624(and a falseunconditional_recursionlint in some cases) — while a narrowerfree_fn_to_modulemap still stops such methods from being emitted as an invaliduse super::<mod>::<method>;import. - Cross-module
const/staticreferences trigger the right imports. Astatic TABLE: [u8; 256] = build_table();initializer that calls a helper function now correctly upgrades that helper’s visibility, and an extractedtests.rsthat names a sibling module’sconst/staticby name now gets theuse super::<module>::<NAME>;it needs. Previously this only surfaced asE0425undercargo test/nextest— invisible to a plaincargo check. bitflags::bitflags! { ... }invocations are recognized. The type(s) abitflags!macro call defines are now registered by name (bitflags_defined_idents/bitflags_defines_pub_type, scanning the raw token stream), fixingE0425/E0433on cross-module references and making sure such types are re-exported through the module’s public facade.Type::from_str(...)andwrite!/writeln!keep their trait imports. Import pruning no longer stripsstd::str::FromStrorstd::fmt::Write/std::io::Writeout from under a call site that still needs them, fixingE0599.use a::b::{self, *}pruning stopped over-keepingself. Theselfleaf now tracks the usage of its own enclosing path segment instead of automatically surviving alongside any kept sibling leaf, eliminating spuriousunused_importswarnings.- Grouped-import detection was rewritten on sturdier ground.
already_importedused to be a string-based scan that only looked inside the first{...}group; it’s now AST-based viacollect_use_bound_names, fixingE0252(“the name … is defined multiple times”) on multi-segment nested grouped imports likestd::{borrow::Cow, collections::{HashMap, VecDeque}}. - Comments survive visibility upgrades in more places. Byte-verbatim slicing now covers extracted test modules and any impl-block/standalone item that needs a
pub(super)bump, via newupgrade_verbatim_item_visibility, which text-patches the prefix onto the original source instead of round-tripping throughprettyplease— a path that was silently dropping inline comments.
None of this changes the CLI surface — every flag works exactly as documented in 0.3.4. This release makes the code SplitRS generates compile (and keep its comments) more often.
Technical Deep Dive
FileAnalyzer::file_backed_modsscans the source formod x;items with no inline body (content: None) before chunking begins, producing a pinned set the chunker must never place into a generated bucket file.rewrite_pinned_mod_refs_in_usewalks every forwardedusetree the generator writes and, for any path segment matching a pinned file-backed mod, inserts thesuper::prefix so the reference still resolves from its new location.FileAnalyzer::module_defined_callablesextends the “who defines this callable” analysis to look inside individualmethod_groupchunks, not just whole impl blocks, so a call from a sibling chunk resolves to the correct definition site instead of being assumed private-and-inaccessible.free_fn_to_moduleis a narrower, free-function-only companion map: it stops the (invalid)use super::<mod>::<method>;shape from ever being emitted for a method, since only free functions can legally be imported that way.bitflags_defined_idents/bitflags_defines_pub_typescan the raw macro token stream of everybitflags::bitflags! { ... }invocation — rather than trying to expand the macro — to recover the type name(s) it defines, feeding them into the same cross-module import/re-export machinery as an ordinarystruct/enum.collect_use_bound_namesreplaces a first-{...}-only string scan insidealready_importedwith a real AST walk, so nested multi-segment grouped imports are parsed correctly instead of pattern-matched.upgrade_verbatim_item_visibilitytext-patches apub(super)prefix directly onto a byte-verbatim-sliced item — correctly skipping past leading attributes and////* */comments to find the insertion point — so a visibility upgrade no longer forces aprettypleaseround-trip that drops comments.generate_tests_rs_full/generate_tests_rs_with_imports_deep, plustest_module_splitter::generate_per_test_file/generate_fallback_tests_rs, all gained anoriginal_source: Option<&str>parameter, threading the original file text down to wherever verbatim slicing needs it.
Getting Started
Install the latest SplitRS:
cargo install splitrs
Two of this release’s fixes (cross-chunk method calls, cross-module const/static imports) show up specifically under impl-block splitting, so it’s worth running with that flag on:
splitrs \
--input src/large_file.rs \
--output src/large_file/ \
--split-impl-blocks \
--max-impl-lines 200
If you hit E0583, E0624, E0425, E0433, E0599, or E0252 against generated output on 0.3.4 or earlier, upgrading to 0.3.5 and re-running the same command should resolve it without any config changes.
What’s New in 0.3.5
- File-backed
mod x;declarations: no longer bucketed into a generated file; stay pinned to the rootmod.rs, fixingE0583/E0432. --split-impl-blockscross-chunk calls: private methods defined only in a sibling chunk now resolve, fixingE0624.- Cross-module
const/staticreferences: helper calls inside initializers, and sibling-module constants referenced from extractedtests.rs, now get the imports they need, fixingE0425. bitflags!type exports: bitflags-defined types are now recognized for cross-module imports and public-facade re-exports, fixingE0425/E0433.FromStr/Writetrait imports: no longer pruned out from underType::from_str(...),write!, andwriteln!call sites, fixingE0599.use a::b::{self, *}pruning:selfno longer survives spuriously alongside a kept sibling glob, removing falseunused_importswarnings.- Grouped-import detection rewrite:
already_importedis now AST-based viacollect_use_bound_names, fixingE0252on nested multi-segment grouped imports. - Byte-verbatim comment preservation: extended to extracted test modules and any visibility-upgraded item, via
upgrade_verbatim_item_visibility. - Test suite grew to 608 tests passing with
--all-features(537 with default features, 1 skipped), up from 570 in 0.3.4 — new regression coverage intests/cross_module_visibility_tests.rsreproduces the real failures found while splitting theoxisqlite-core/oxisqlsqlite3-parsercrates.
Tips
- Hitting
E0252(“the name … is defined multiple times”) in a generatedmod.rs? That’s the exact grouped-import bug this release fixes — upgrade and re-run. The AST-basedcollect_use_bound_namesrewrite correctly handles nested multi-segment groups likestd::{borrow::Cow, collections::{HashMap, VecDeque}}. - If your crate declares real file-backed submodules (
mod check;/mod fmt;, body in a sibling file, not inline) alongside the code you’re splitting, 0.3.5 is the first version safe to point at that file — earlier versions could bucket the declaration itself, breaking bothE0583sibling-file lookup and any absolute path referencing it. - Using
--split-impl-blockson a type whose impls call each other’s methods? This release is required — 0.3.4 and earlier could wrongly mark those cross-chunk calls private (E0624) or emit an invaliduse super::<mod>::<method>;import. - Splitting a file that uses
bitflags::bitflags! { ... }? Upgrade first — earlier versions didn’t know the macro defined a type at all, so any cross-module reference to it failed withE0425/E0433. - Losing inline comments on visibility-upgraded items in extracted test modules? Fixed here —
upgrade_verbatim_item_visibilitytext-patchespub(super)onto the original source instead of reformatting throughprettyplease. - Always run
cargo test/nextestafter a split, not justcargo check. The cross-module const/static fix in this release exists precisely because that import gap only showed up once test code exercised the split modules.
This is the foundation
SplitRS remains the development workhorse behind the COOLJAPAN policy of keeping every file under 2,000 lines, used across the ecosystem’s Rust codebases — OxiZ, NumRS2, SciRS2, and dozens of oxi* crates — whenever a module outgrows its budget. Fittingly, 0.3.5’s entire fix list came from dogfooding: splitting the oxisqlite-core/oxisql sqlite3-parser crate for real is what surfaced all eight of these bugs, not a synthetic test fixture.
Repository: https://github.com/cool-japan/splitrs
Star the repo if you want a refactoring tool that treats its own real-world failures as bugs worth a dedicated release. Pure Rust refactoring is here — fast, safe, and getting more battle-tested with every crate it splits.
— KitaSan at COOLJAPAN OÜ July 14, 2026