Skip to content

bun_runtime: deny unnameable_types and narrow every bare pub item to pub(crate) - #44415

Draft
robobun wants to merge 2 commits into
mainfrom
robobun/a0812589/remove-unused-body-tag
Draft

robobun wants to merge 2 commits into
mainfrom
robobun/a0812589/remove-unused-body-tag

Conversation

@robobun

@robobun robobun commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Behaviour change: none

Problem

  • bun_runtime exports nothing, but 271 of its items are bare pub. unreachable_pub reports none, and dead_code skips what they reach.
  • rustc counts a pub type as exported when an impl of a foreign trait for a foreign type names it. strum::IntoStaticStr emits one: impl From<T> for &'static str.
  • The largest chain starts at the unused body::Tag (src/runtime/webcore/Body.rs:588) and hides 172 items.

Fix

  • Add #![deny(unnameable_types)] to src/runtime/lib.rs. It reports each such type: 97 on main, 0 here.
  • Narrow every bare pub item to pub(crate). Delete body::Tag and the EnumTag derive on body::Value.
  • Delete the dead code this exposes, or gate it with #[cfg] when one platform uses it. The Notes name each item.
  • Verified: bun run rust:check-all, 12 of 12 targets. The Notes list the other checks and the tests.

Background

  • unreachable_pub reports a pub item no other crate can reach. dead_code skips an item that rustc counts as exported.
  • Nominal visibility caps that count: a pub(crate) type is never exported.
  • Weighed: fix only the body::Tag chain (55 of 97 types stay exported), or a source-lint test (an extra CI pass).

Downsides

  • A bare pub type that an impl exports is now a hard error with no automatic fix.
  • 27 new #[cfg] lines gate 9 enum variants and 2 enums to one platform.
  • Not covered: pub written inside another crate's macro, and the other workspace crates.
Notes

Order. #42819 (Remove libuv on Windows) deletes Writable::Pipe, SourceHandle::ShellWritable and MultiPartUpload.global_this. This PR must land after it. It stays a draft until then. After the rebase, the #[cfg(windows)] gates for those items go away. Related: #36895 constructs Writable::Pipe on POSIX again, #42266 is a dead-code sweep in the same files.

Deleted (nothing reads them on any target).

  • webcore::body::Tag, and Value::tag() with the derive that emits it (0 callers).
  • streams::IntoArray.value and its 11 initializers in ByteStream.rs, FileReader.rs, ByteBlobLoader.rs.
  • MultiPartUpload.global_this and its 2 initializers in s3/client.rs.
  • The payload of shell::subproc::Writable::Fd (now a unit variant, like Readable::Fd).
  • shell::subproc::WritableInitError. Writable::init returns Writable.
  • The payload of AsyncState::Done (shell/states/Async.rs).
  • Error::eol, Error::fmt, Error::InvalidCharacter and their name() arms. Only cpus_impl_linux built them, and From<crate::Error> for OsError dropped the variant. The helpers return OsError::Any directly, and that From impl is deleted with them.

Gated (one platform uses them).

  • #[cfg(windows)]: Writable::Pipe with Writable::on_close, the stdin wiring block, the finalize arm and the FileSink import. SourceHandle::ShellWritable with its 4 match arms. Error::FailedToInitPipe, Error::FailedToBindPipe. cron::TaskXmlError.
  • #[cfg(target_os = "macos")]: Error::FailedToCreateCoreFoudationSourceLoop, Error::FailedToSpawnFSEventsThread, cron::CalendarError.
  • #[cfg(not(windows))]: Error::WatchFailed, CreatePtyError::NotSupported, InitError::NotSupported and their arms.

Narrowed by hand (no rustc lint reports these). 34 pub tokens inside bitflags!, opaque_ffi!, jsc_host_abi!, jsc_promise_handler!, multi_array_columns! and impl_field_parent! invocations, 2 object_pool! aliases, and 4 items in the shell_builtins! macro body. The #[no_mangle] host functions among them keep their symbols: 11 host functions in this crate are already private.

Executed code that changes, and why the result is the same.

  • ByteStream::on_data: two let v = self.value(); become self.pending_value.with_mut(|pv| pv.clear_without_deallocation()). value() did that clear as a side effect, and nothing read v after it was stored.
  • POSIX Writable::init: Writable::Fd(result.unwrap()) becomes Writable::Fd. The unwrap() cannot fail: Stdio::Fd(fd) maps to PosixStdio::Pipe(fd), and spawn_process always sets the spawned stdin for it.
  • Windows Writable::init: when start_with_current_pipe() fails, the same panic!("unexpected error while creating stdin") now runs in init. Before, init returned the one-variant error and its only caller panicked with that message.

Not changed.

  • The EnumTag derive still emits impl From<&Data> for Tag. It has 0 callers in the workspace. Once this crate is capped it hides nothing, so it is a separate cleanup.
  • The other 12 #[enum_tag(existing = ..)] sites. Each has real .tag() callers.
  • strum::IntoStaticStr enums in the other crates. Many are real exports. The attribute is not in [workspace.lints] for that reason.

Measurements.

  • lint matrix: 0 new diagnostics over 12 triples x dev/release, --all-targets, clippy, and the bun_asan + bun_debug + socket_fault_injection and bun_codegen_embed cfg sets (0 triples skipped).
  • unnameable_types reports in bun_runtime (linux x64 / windows x64 / darwin arm64): 97 / 98 / 98 on main, 0 / 0 / 0 here.
  • bare pub item declarations in src/runtime: 271 lines + 2 object_pool! aliases on main, 0 + 0 here.
  • guard probe: a new #[derive(strum::IntoStaticStr)] pub enum gives 1 unnameable_types error and 0 others. As pub(crate) it gives 0 and 1 dead_code.
  • per-platform gates: +27 #[cfg] lines under 3 predicates (15 windows, 7 not(windows), 5 target_os = "macos").
  • regenerated codegen: 0 files differ.
  • mordant: 0 (lint, file) pairs above the baseline.
  • release binary (linux x64, llvm-size): file 80,873,032 -> 80,868,936 bytes (-4,096), .text 58,187,957 -> 58,185,141 bytes (-2,816), every other section equal. Debug binary: body::Tag symbols 2 -> 0 (535 B -> 0 B).
  • dynamic exports: 0 added / 0 removed (692). bun_runtime rlib global symbols: 12,417 -> 12,292. C-ABI (unmangled) names in the rlib: 2,513 -> 2,513, same list.
  • type sizes (-Zprint-type-sizes): IntoArray 16 -> 8 bytes, MultiPartUpload 344 -> 336 bytes, streams::Result 48 -> 48 bytes.
  • stream pull functions (release symbol sizes): ByteStream::on_data 3,420 -> 3,272 B, FileReader::on_read_chunk 2,264 -> 2,127 B. CPU time of a fixed stream-read script, 15 interleaved runs: -0.75% median against an A/A spread of +0.71%.
  • rustc CPU time for the bun_runtime unit with and without the attribute, 8 paired non-incremental runs: +1.4% median, inside the same-variant spread of 5.0%.
  • Not available in this environment: perf, valgrind and bloaty. So there are no instruction counts, and sizes come from llvm-size and llvm-nm.

Tests (debug + ASAN build, linux x64). test/js/web/streams/streams.test.js, test/js/web/fetch/body-stream.test.ts, body.test.ts, fetch.test.ts, test/js/bun/spawn/spawn.test.ts, test/js/bun/shell/bunshell.test.ts, file-io.test.ts, test/js/bun/s3/, test/js/bun/terminal/, test/js/bun/cron/, test/js/node/os/os.test.js, test/js/workerd/html-rewriter.test.js, test/js/bun/http/serve.test.ts, test/internal/source-lints/. Each failure also fails with a build of the merge-base on the same machine (5 s timeouts under load, root user, no IPv6).

@github-actions github-actions Bot added the claude label Oct 2, 2026
@robobun

robobun commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Status: draft, waiting for #42819 to land first.

How to reproduce on main:

  • Add #![deny(unnameable_types)] after line 5 of src/runtime/lib.rs and run cargo check -p bun_runtime. It reports 97 pub types that rustc counts as exported (linux x64).
  • Or delete body::Tag and the two derive lines on body::Value in src/runtime/webcore/Body.rs and run the same check. It reports 172 unreachable_pub and 4 dead_code errors.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant