feat(lints): Emit unused_dependencies lint#16600
Conversation
|
r? @ehuss rustbot has assigned @ehuss. Use Why was this reviewer chosen?The reviewer was selected based on:
|
| [WARNING] unused dependency | ||
| --> bar/Cargo.toml:9:1 | ||
| | | ||
| 9 | build-dep.workspace = true | ||
| | ^^^^^^^^^ | ||
| | | ||
| = [NOTE] `cargo::unused_dependencies` is set to `warn` by default | ||
| [HELP] remove the dependency | ||
| | | ||
| 9 - build-dep.workspace = true | ||
| 9 + .workspace = true |
There was a problem hiding this comment.
Our selecting of key-values that use workspace inheritance is not great
There was a problem hiding this comment.
I tried to improve the information provided by toml but it caused other problems, see toml-rs/toml#1119
| [WARNING] unused dependency | ||
| --> Cargo.toml:9:13 | ||
| | | ||
| 9 | unused = "0.1.0" | ||
| | ^^^^^^^^^^^^^^^^ | ||
| | | ||
| = [NOTE] `cargo::unused_dependencies` is set to `warn` by default | ||
| [HELP] remove the dependency | ||
| | | ||
| 9 - unused = "0.1.0" | ||
| | | ||
| [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s |
There was a problem hiding this comment.
As this happens during compilation, ideally we tie into the existing warning/error count and work with build.warnings but haven't gotten all of that to work yet.
| [WARNING] unused dependency | ||
| --> Cargo.toml:9:13 | ||
| | | ||
| 9 | unused = "0.1.0" | ||
| | ^^^^^^^^^^^^^^^^ | ||
| | | ||
| = [NOTE] `cargo::unused_dependencies` is set to `warn` by default | ||
| [HELP] remove the dependency | ||
| | | ||
| 9 - unused = "0.1.0" | ||
| | | ||
| [UPDATING] `dummy-registry` index | ||
| [LOCKING] 1 package to latest compatible version | ||
| [DOWNLOADING] crates ... | ||
| [DOWNLOADED] unused v0.1.0 (registry `dummy-registry`) | ||
| [CHECKING] foo v0.1.0 ([ROOT]/foo) | ||
| [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s |
There was a problem hiding this comment.
Note that this looks different than the others because there is no build.rs so there is no Unit to report the message, so we lint this during the regular lint pass.
| [dependencies] | ||
| unused = "0.1.0" | ||
| lib_used = "0.1.0" | ||
| bins_used = "0.1.0" |
There was a problem hiding this comment.
side note: I would love to find a way to tell that we should lint that bins_used should be moved to a feature that the bins require so we can help people cut down on accidentally including bin deps with their libs.
There was a problem hiding this comment.
If we're going to advise people on how to structure things, we may want to advise them to create a workspace and put the bin and lib in separate workspace members. (Assuming they don't have backwards-compatibility considerations in the form of both people depending on the bin being in package xyz and people depending on the lib being in the same package xyz.)
| [dependencies] | ||
| used_dev = "0.1.0" | ||
|
|
||
| [lints.cargo] | ||
| unused_dependencies = "warn" | ||
| "#, | ||
| ) | ||
| .file( | ||
| "src/main.rs", | ||
| r#" | ||
| fn main() {} | ||
| "#, | ||
| ) | ||
| .file( | ||
| "tests/foo.rs", | ||
| r#" | ||
| #[test] | ||
| fn foo { | ||
| use used_dev as _; | ||
| } | ||
| "#, |
There was a problem hiding this comment.
Ideally this would provide a hint that used_dev should be moved to dev-dependencies. Based on the code, it seems like #8437 supported that but haven't dug into what all it takes to report it.
| [HELP] remove the dependency | ||
| | | ||
| 11 - bar.path = "../bar" | ||
| 11 + .path = "../bar" | ||
| | |
There was a problem hiding this comment.
More problems with how we get spans
|
CC @est31 |
| use crate::lints::get_key_value_span; | ||
| use crate::lints::rel_cwd_manifest_path; | ||
|
|
||
| pub const LINT: Lint = Lint { |
There was a problem hiding this comment.
I didn't do anything in particular for artifact dependencies. I assume that I would just put it on the artifact-deps tracking issue as something to investigate.
| use crate::lints::get_key_value_span; | ||
| use crate::lints::rel_cwd_manifest_path; | ||
|
|
||
| pub const LINT: Lint = Lint { |
There was a problem hiding this comment.
One false positive I found is with cargo itself: we don't directly use libgit2-sys but we indirectly use it and activate a feature of it. We do that with our features table but that could just as well be done by just setting a feature on the dependency
There was a problem hiding this comment.
For a potential workaround, see https://github.com/rust-lang/cargo/pull/16600/changes#r2776252783
There was a problem hiding this comment.
We talked about this in the cargo team meeting.
Options
- A lint config
- Doesn't compose well with workspace inheritance without the ability to extend what we inherit (Packages overriding inherited lints in Cargo.toml + adding lints #13157)
- distant from the affected data
_prefix- Matches rust
- We were concerned about using
_to mark hidden features (Don't display "hidden" features incargo add#10794) - Gets weird with kebab-case dependencies
- Affects sort order
- A
used = truefield with maybeused.reason = "feature activation"- Similar to
#[allow]or#[expect]in keeping it local - TOML syntax however doesn't allow general lint control
- Extending our schema for general lint control could be messy
- Question is whether having one-off lint control for this is justified
- Similar to
There was a problem hiding this comment.
https://github.com/est31/cargo-udeps?tab=readme-ov-file#ignoring-some-of-the-dependencies
[package.metadata.cargo-udeps.ignore]
normal = ["if_chain"]
#development = []
#build = []
[dependencies]
if_chain = "1.0.0" # Used only in doc-tests, which `cargo-udeps` cannot check.There was a problem hiding this comment.
https://github.com/bnjbvr/cargo-machete?tab=readme-ov-file#false-positives
[dependencies]
prost = "0.10" # Used in code generated by build.rs output, which cargo-machete cannot check
# in an individual package Cargo.toml
[package.metadata.cargo-machete]
ignored = ["prost"]
# in a workspace Cargo.toml
[workspace.metadata.cargo-machete]
ignored = ["prost"]There was a problem hiding this comment.
https://github.com/Boshen/cargo-shear
[package.metadata.cargo-shear]
ignored = ["crate-name"]| continue; | ||
| } | ||
|
|
||
| for (ext, dependency) in &state.externs { |
There was a problem hiding this comment.
#8437 would automatically skip any dependency of the form:
_foo = { package = "foo", ...}I removed that but we can consider adding it back in.
There was a problem hiding this comment.
See #16600 (comment) for a potential use case
| let toml_lints = pkg | ||
| .manifest() | ||
| .normalized_toml() | ||
| .lints | ||
| .clone() | ||
| .map(|lints| lints.lints) | ||
| .unwrap_or(manifest::TomlLints::default()); | ||
| let cargo_lints = toml_lints | ||
| .get("cargo") | ||
| .cloned() | ||
| .unwrap_or(manifest::TomlToolLints::default()); |
There was a problem hiding this comment.
During our regular lint pass, we calculate this and then pass it into each lint rule. Here, we have to re-calculate it and would likely need to do it again for each additional lint.
|
side-question (if is acceptable): rust-lang/rust#143856 (comment) |
b60a278 to
bb24b5e
Compare
Fixes rust-lang#15813 Built-on rust-lang#8437 This does nothing for dev-dependencies because there isn't really a way to select all targets today without - tracking selected dep kinds to check on a per-package basis - checking the status of every bench to see if it can work as a test because `cargo test` (no args) with benches set to test is the only command today that can exercise all dev-dependencies as it is the only one that will compile tests and doctests. See also - https://blog.rust-lang.org/inside-rust/2024/10/01/this-development-cycle-in-cargo-1.82/#detecting-unused-dependencies - https://blog.rust-lang.org/inside-rust/2024/10/01/this-development-cycle-in-cargo-1.82/#all-targets-and-doc-tests - https://blog.rust-lang.org/inside-rust/2024/10/31/this-development-cycle-in-cargo-1.83/#target-and-target
|
I've gone ahead and removed the Draft status. I've added lint config for false positives: [lints.cargo]
unused_dependencies = { level = "warn", ignore = ["dep_name"] }First off, this is a two-way door because Even if it is, we would need to work out the exact compatibility guarantees around lints since some evolving is allowed. I'm inclined to go with this solution and postpone rust-lang/rfcs#3920 after looking at a list of potential dependency lints and thinking about how to handle exceptions for them. My biggest concern is with workspace inheritance
I do wonder if |
There was a problem hiding this comment.
Thanks! I'm happy to move forward with this as-is and iterate if needed later.
Can you open an issue (or multiple issues) to track unresolved questions? For example, the two false positives you mentioned and whether we should try to address those, and bikeshedding the field name or if it instead should be used = true (RFC#3920).
I'd also like to see people try to test it out and report any issues. I don't know if you feel like it is ready to call publicly for that, yet.
|
I added it to the tracking issue #12235 |
Cargo submodule update 11 commits in a357df4c26fc14514e66aae2a269456b5545c7db..101549dddbd2b08e806f50154e3aa4cb3374cc21 2026-04-03 16:47:15 +0000 to 2026-04-08 12:51:20 +0000 - Never include use extra-filename in build scripts (rust-lang/cargo#16855) - fix(toml): Force script edition warnings on quiet (rust-lang/cargo#16848) - GitHub fast path uses `http_async` (rust-lang/cargo#16847) - feat(manifest): allow git dependency alongside alternate registry (rust-lang/cargo#16810) - fix(auth): add auth scheme hint to token rejected error for alt registries (rust-lang/cargo#16794) - Warn on invalid jobserver file descriptors (rust-lang/cargo#16843) - docs(unstable): List the minimum required MSRV for 'public' field (rust-lang/cargo#16841) - feat(lints): Emit unused_dependencies lint (rust-lang/cargo#16600) - fix(tree): clarify error message when `-i` is used without a package name (rust-lang/cargo#16818) - fix: Typo in target.<cfg>.linker (rust-lang/cargo#16839) - Send Content-Type header with cargo publish requests (rust-lang/cargo#16832) r? ghost
Cargo submodule update 11 commits in a357df4c26fc14514e66aae2a269456b5545c7db..101549dddbd2b08e806f50154e3aa4cb3374cc21 2026-04-03 16:47:15 +0000 to 2026-04-08 12:51:20 +0000 - Never include use extra-filename in build scripts (rust-lang/cargo#16855) - fix(toml): Force script edition warnings on quiet (rust-lang/cargo#16848) - GitHub fast path uses `http_async` (rust-lang/cargo#16847) - feat(manifest): allow git dependency alongside alternate registry (rust-lang/cargo#16810) - fix(auth): add auth scheme hint to token rejected error for alt registries (rust-lang/cargo#16794) - Warn on invalid jobserver file descriptors (rust-lang/cargo#16843) - docs(unstable): List the minimum required MSRV for 'public' field (rust-lang/cargo#16841) - feat(lints): Emit unused_dependencies lint (rust-lang/cargo#16600) - fix(tree): clarify error message when `-i` is used without a package name (rust-lang/cargo#16818) - fix: Typo in target.<cfg>.linker (rust-lang/cargo#16839) - Send Content-Type header with cargo publish requests (rust-lang/cargo#16832) r? ghost
### What does this PR try to resolve? This changes `unused_dependencies` lint to reduce the chance of false positives while we work out the story of how we want people to response to false positives (rust-lang/rfcs#3920). ### How to test and review this PR? My assumption is that I will revert the `ignore` lint control added in rust-lang#16600 after this. Looking at the known cases of false positives, my short term plan is: - transitive version constraint: don't lint - transitive feature activation: don't lint - dynamically used dep (e.g. `build.rs` in `curl`): users must `allow` Longer term, I'd like to replace this change with either - `lib = false` from artifact deps - `cfg(resolver = "versions")`, [#t-cargo > Additional dependency tables @ 💬](https://rust-lang.zulipchat.com/#narrow/channel/246057-t-cargo/topic/Additional.20dependency.20tables/near/590208010) We can evolve this because the exact nature of warning-by-default lints is not stable.
View all comments
What does this PR try to resolve?
Fixes #15813
This checks only build and normal dependencies (see below for more details). I would expect we'd open a new issue to track dev-dependencies. I don't consider this a false-positive because there technically isn't a way to ask for everything that uses dev-dependencies to be built.
This only checks selected packages, and not all local packages, and only if the build target selection flags are exhaustive without consideration for the selected packages. See #16600 (comment)
Known false positives:
target.cfg(false).dependencies)To handle false positives,
ignoredbut I felt likeignorefit in better withallowudepscares about dep kinds but not target cfgs), these shouldn't be too common, and using dep names gives users control over uniquenessusedrfcs#3920How to test and review this PR?
Built-on #8437
This does nothing for dev-dependencies because there isn't really a way
to select all targets today without
because
cargo test(no args) with benches set to test is the onlycommand today that can exercise all dev-dependencies as it is the only
one that will compile tests and doctests.
See also
As for the commits, I did something unusual for myself in that I made changes with dead code so it is easier to understand what goes into one step in this process without seeing the entire process at once and getting confused.